diff --git a/Cargo.lock b/Cargo.lock index 4e03bc4..18fdf9c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -105,15 +105,6 @@ dependencies = [ ] [[package]] -name = "async-lock" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" -dependencies = [ - "event-listener", -] - -[[package]] name = "async-trait" version = "0.1.74" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -215,12 +206,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" [[package]] -name = "bytecount" -version = "0.6.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1e5f035d16fc623ae5f74981db80a439803888314e3a555fd6f04acd51a3205" - -[[package]] name = "byteorder" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -233,37 +218,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" [[package]] -name = "camino" -version = "1.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c59e92b5a388f549b863a7bea62612c09f24c8393560709a54558a9abdfb3b9c" -dependencies = [ - "serde", -] - -[[package]] -name = "cargo-platform" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12024c4645c97566567129c204f65d5815a8c9aecf30fcbe682b2fe034996d36" -dependencies = [ - "serde", -] - -[[package]] -name = "cargo_metadata" -version = "0.14.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4acbb09d9ee8e23699b9634375c72795d095bf268439da88562cf9b501f181fa" -dependencies = [ - "camino", - "cargo-platform", - "semver", - "serde", - "serde_json", -] - -[[package]] name = "cc" version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -351,29 +305,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" [[package]] -name = "crossbeam-channel" -version = "0.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" -dependencies = [ - "cfg-if", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" -dependencies = [ - "autocfg", - "cfg-if", - "crossbeam-utils", - "memoffset", - "scopeguard", -] - -[[package]] name = "crossbeam-queue" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -533,15 +464,6 @@ dependencies = [ ] [[package]] -name = "error-chain" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc" -dependencies = [ - "version_check", -] - -[[package]] name = "etcetera" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -780,24 +702,6 @@ dependencies = [ ] [[package]] -name = "giterated-cache" -version = "0.1.0" -dependencies = [ - "anyhow", - "async-trait", - "bincode", - "futures-util", - "giterated-models", - "giterated-stack", - "moka", - "serde", - "serde_json", - "thiserror", - "tokio", - "tracing", -] - -[[package]] name = "giterated-daemon" version = "0.1.0" dependencies = [ @@ -922,28 +826,6 @@ dependencies = [ ] [[package]] -name = "giterated-stack" -version = "0.1.0" -dependencies = [ - "anyhow", - "async-trait", - "bincode", - "futures-util", - "giterated-models", - "serde", - "serde_json", - "thiserror", - "tokio", - "tracing", -] - -[[package]] -name = "glob" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" - -[[package]] name = "h2" version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1316,15 +1198,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] -name = "mach2" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d0d1830bcd151a6fc4aea1369af235b36c1528fe976b8ff678683c9995eade8" -dependencies = [ - "libc", -] - -[[package]] name = "md-5" version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1341,15 +1214,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" [[package]] -name = "memoffset" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" -dependencies = [ - "autocfg", -] - -[[package]] name = "mime" version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1382,30 +1246,6 @@ dependencies = [ ] [[package]] -name = "moka" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8017ec3548ffe7d4cef7ac0e12b044c01164a74c0f3119420faeaf13490ad8b" -dependencies = [ - "async-lock", - "async-trait", - "crossbeam-channel", - "crossbeam-epoch", - "crossbeam-utils", - "futures-util", - "once_cell", - "parking_lot", - "quanta", - "rustc_version", - "skeptic", - "smallvec", - "tagptr", - "thiserror", - "triomphe", - "uuid", -] - -[[package]] name = "native-tls" version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1721,33 +1561,6 @@ dependencies = [ ] [[package]] -name = "pulldown-cmark" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a1a2f1f0a7ecff9c31abbe177637be0e97a0aef46cf8738ece09327985d998" -dependencies = [ - "bitflags 1.3.2", - "memchr", - "unicase", -] - -[[package]] -name = "quanta" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a17e662a7a8291a865152364c20c7abc5e60486ab2001e8ec10b24862de0b9ab" -dependencies = [ - "crossbeam-utils", - "libc", - "mach2", - "once_cell", - "raw-cpuid", - "wasi", - "web-sys", - "winapi", -] - -[[package]] name = "quote" version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1787,15 +1600,6 @@ dependencies = [ ] [[package]] -name = "raw-cpuid" -version = "10.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c297679cb867470fa8c9f67dbba74a78d78e3e98d7cf2b08d6d71540f797332" -dependencies = [ - "bitflags 1.3.2", -] - -[[package]] name = "redox_syscall" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1890,15 +1694,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] -name = "rustc_version" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" -dependencies = [ - "semver", -] - -[[package]] name = "rustix" version = "0.38.21" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1918,15 +1713,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" [[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - -[[package]] name = "schannel" version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2098,21 +1884,6 @@ dependencies = [ ] [[package]] -name = "skeptic" -version = "0.13.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16d23b015676c90a0f01c197bfdc786c20342c73a0afdda9025adb0bc42940a8" -dependencies = [ - "bytecount", - "cargo_metadata", - "error-chain", - "glob", - "pulldown-cmark", - "tempfile", - "walkdir", -] - -[[package]] name = "slab" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2444,12 +2215,6 @@ dependencies = [ ] [[package]] -name = "tagptr" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" - -[[package]] name = "tempfile" version = "3.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2714,12 +2479,6 @@ dependencies = [ ] [[package]] -name = "triomphe" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee8098afad3fb0c54a9007aab6804558410503ad676d4633f9c2559a00ac0f" - -[[package]] name = "try-lock" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2751,15 +2510,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] -name = "unicase" -version = "2.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" -dependencies = [ - "version_check", -] - -[[package]] name = "unicode-bidi" version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2827,15 +2577,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" [[package]] -name = "uuid" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88ad59a7560b41a70d191093a945f0b87bc1deeda46fb237479708a1d6b6cdfc" -dependencies = [ - "getrandom", -] - -[[package]] name = "valuable" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2854,16 +2595,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] -name = "walkdir" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" -dependencies = [ - "same-file", - "winapi-util", -] - -[[package]] name = "want" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2977,15 +2708,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] -name = "winapi-util" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" -dependencies = [ - "winapi", -] - -[[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/Cargo.toml b/Cargo.toml index d6cb209..d222f38 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,12 +2,10 @@ members = [ "giterated-daemon", "giterated-models", - "giterated-stack", - "giterated-cache", - "giterated-plugins/giterated-plugin", - "giterated-plugins/giterated-plugin-sys", - "giterated-plugins/example-plugin", - "giterated-plugins/giterated-backend", - "giterated-plugins/giterated-issues", - "giterated-plugins/giterated-protocol" + "giterated-plugin", + "giterated-plugin/giterated-plugin-sys", + "plugins/example-plugin", + "plugins/giterated-backend", + "plugins/giterated-issues", + "plugins/giterated-protocol" ] \ No newline at end of file diff --git a/giterated-cache/Cargo.toml b/giterated-cache/Cargo.toml deleted file mode 100644 index 4151ddb..0000000 --- a/giterated-cache/Cargo.toml +++ /dev/null @@ -1,20 +0,0 @@ -[package] -name = "giterated-cache" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -giterated-models = { path = "../giterated-models" } -async-trait = "0.1" -serde = { version = "1.0.188", features = [ "derive" ]} -serde_json = "1.0" -bincode = "1.3" -futures-util = "0.3" -tracing = "0.1" -tokio = { version = "1.32", features = [ "full" ] } -anyhow = "1" -thiserror = "1" -giterated-stack = { path = "../giterated-stack" } -moka = { version = "0.12.0", features = ["future"] } diff --git a/giterated-cache/src/cache_get.rs b/giterated-cache/src/cache_get.rs deleted file mode 100644 index 6fe0e47..0000000 --- a/giterated-cache/src/cache_get.rs +++ /dev/null @@ -1,51 +0,0 @@ -// use std::sync::Arc; - -// use giterated_models::error::OperationError; -// use giterated_stack::GiteratedStack; -// use serde_json::Value; - -// use crate::{cache_update::AnyObject, CacheSubstack}; - -// pub async fn try_value_get( -// object: AnyObject<'_>, -// value_name: &str, -// cache: CacheSubstack, -// stack: Arc, -// ) -> Result> { -// todo!() -// } - -use giterated_models::error::OperationError; -use giterated_stack::{AnyObject, AnyValue, GiteratedStack}; -use tracing::trace; - -use crate::{CacheKey, CacheSubstack}; - -pub async fn get_cached( - object: AnyObject, - value_kind: String, - cache: CacheSubstack, - stack: GiteratedStack, -) -> Result> { - let object_meta = stack - .metadata - .objects - .get(object.kind()) - .ok_or_else(|| OperationError::Unhandled)?; - let object_str = (object_meta.to_str)(object.clone()); - let cache_key = CacheKey { - object: object_str, - value_name: value_kind.clone(), - }; - - trace!("Check cache for {}::{}", object.kind(), value_kind); - - if let Some(cached_value) = cache.cache.get(&cache_key).await { - trace!("Found cached for {}::{}", object.kind(), value_kind); - - Ok(cached_value.clone()) - } else { - trace!("Nothing cached for {}::{}", object.kind(), value_kind); - Err(OperationError::Unhandled) - } -} diff --git a/giterated-cache/src/cache_update.rs b/giterated-cache/src/cache_update.rs deleted file mode 100644 index b83a020..0000000 --- a/giterated-cache/src/cache_update.rs +++ /dev/null @@ -1,39 +0,0 @@ -use giterated_models::error::OperationError; -use giterated_stack::{AnyObject, AnyValue, GiteratedStack}; -use tracing::trace; - -use crate::{CacheKey, CacheSubstack}; - -pub async fn cache_updated( - object: AnyObject, - value: AnyValue, - state: CacheSubstack, - stack: GiteratedStack, -) -> Result<(), OperationError> { - let object_meta = stack - .metadata - .objects - .get(object.kind()) - .ok_or_else(|| OperationError::Unhandled)?; - let object_str = (object_meta.to_str)(object.clone()); - let cache_key = CacheKey { - object: object_str, - value_name: value.kind().value_kind.to_string(), - }; - - let value_kind = value.kind().value_kind; - trace!( - "Beginning cache update for {}::{}", - object.kind(), - value_kind - ); - - state.cache.insert(cache_key, value).await; - - trace!( - "Completed cache update for {}::{}", - object.kind(), - value_kind - ); - Ok(()) -} diff --git a/giterated-cache/src/lib.rs b/giterated-cache/src/lib.rs deleted file mode 100644 index 4d8eb2d..0000000 --- a/giterated-cache/src/lib.rs +++ /dev/null @@ -1,42 +0,0 @@ -use cache_get::get_cached; -use giterated_stack::{AnyValue, StackOperationState, SubstackBuilder}; -use moka::future::Cache; - -use crate::cache_update::cache_updated; - -pub mod cache_get; -pub mod cache_update; - -// use giterated_stack::{ObjectValuePair, SubstackBuilder}; -// use moka::future::Cache; -// use serde_json::Value; - -#[derive(Hash, PartialEq, Eq)] -pub struct CacheKey { - object: String, - value_name: String, -} - -#[derive(Clone)] -pub struct CacheSubstack { - cache: Cache, -} - -impl Default for CacheSubstack { - fn default() -> Self { - Self { - cache: Cache::new(20_000), - } - } -} - -impl CacheSubstack { - pub fn into_substack(self) -> SubstackBuilder { - let mut stack = SubstackBuilder::new(self); - - stack.value_change(cache_updated); - stack.dynamic_value(get_cached); - - stack - } -} diff --git a/giterated-daemon/Cargo.toml b/giterated-daemon/Cargo.toml index 858e9b3..6cf1338 100644 --- a/giterated-daemon/Cargo.toml +++ b/giterated-daemon/Cargo.toml @@ -30,8 +30,8 @@ argon2 = "0.5" aes-gcm = "0.10" semver = {version = "1.0", features = ["serde"]} giterated-models = { path = "../giterated-models" } -giterated-plugin = { path = "../giterated-plugins/giterated-plugin" } -giterated-protocol = { path = "../giterated-plugins/giterated-protocol" } +giterated-plugin = { path = "../giterated-plugin" } +giterated-protocol = { path = "../plugins/giterated-protocol" } deadpool = "0.9" bincode = "1.3" tokio-util = {version = "0.7", features = ["rt"]} diff --git a/giterated-daemon/src/backend/discovery.rs b/giterated-daemon/src/backend/discovery.rs deleted file mode 100644 index a4d9a7a..0000000 --- a/giterated-daemon/src/backend/discovery.rs +++ /dev/null @@ -1,24 +0,0 @@ -use std::hash::Hash; - -use chrono::{DateTime, Utc}; -use serde::{Deserialize, Serialize}; -use sqlx::PgPool; - -pub struct GiteratedDiscoveryProtocol { - pub pool: PgPool, -} - -#[derive(Debug, Hash, Serialize, Deserialize, Clone, sqlx::Type)] -#[sqlx(type_name = "discovery_type", rename_all = "lowercase")] -pub enum DiscoveryType { - Instance, - Repository, -} - -#[derive(Debug, sqlx::FromRow, sqlx::Type)] -pub struct DiscoveriesRow { - discovery_hash: String, - discovery_time: DateTime, - discovery_type: DiscoveryType, - discovery: String, -} diff --git a/giterated-daemon/src/backend/git.rs b/giterated-daemon/src/backend/git.rs deleted file mode 100644 index 14450b1..0000000 --- a/giterated-daemon/src/backend/git.rs +++ /dev/null @@ -1,1061 +0,0 @@ -use anyhow::Error; -use async_trait::async_trait; - -use git2::BranchType; -use giterated_models::instance::{Instance, RepositoryCreateRequest}; - -use giterated_models::repository::{ - AccessList, Commit, DefaultBranch, Description, IssueLabel, Repository, RepositoryBranch, - RepositoryBranchesRequest, RepositoryChunkLine, RepositoryCommitBeforeRequest, - RepositoryCommitFromIdRequest, RepositoryDiff, RepositoryDiffFile, RepositoryDiffFileChunk, - RepositoryDiffFileInfo, RepositoryDiffFileStatus, RepositoryDiffPatchRequest, - RepositoryDiffRequest, RepositoryFile, RepositoryFileFromIdRequest, - RepositoryFileFromPathRequest, RepositoryFileInspectRequest, RepositoryIssue, - RepositoryIssueLabelsRequest, RepositoryIssuesCountRequest, RepositoryIssuesRequest, - RepositoryLastCommitOfFileRequest, RepositoryObjectType, RepositoryStatistics, - RepositoryStatisticsRequest, RepositoryTreeEntry, RepositoryVisibility, Visibility, -}; - -use giterated_models::user::User; - -use giterated_stack::{AuthenticatedUser, GiteratedStack}; - -use sqlx::PgPool; -use std::ops::Deref; -use std::{ - path::{Path, PathBuf}, - sync::Arc, -}; -use thiserror::Error; -use tokio::sync::OnceCell; - -use super::{IssuesBackend, RepositoryBackend}; - -// TODO: Handle this -//region database structures - -/// Repository in the database -#[derive(Debug, sqlx::FromRow)] -pub struct GitRepository { - #[sqlx(try_from = "String")] - pub owner_user: User, - pub name: String, - pub description: Option, - pub visibility: RepositoryVisibility, - pub default_branch: String, -} - -impl GitRepository { - // Separate function because "Private" will be expanded later - /// Checks if the user is allowed to view this repository - pub async fn can_user_view_repository( - &self, - our_instance: &Instance, - user: &Option, - stack: &GiteratedStack, - ) -> bool { - if matches!(self.visibility, RepositoryVisibility::Public) { - return true; - } - - // User must exist for any further checks to pass - let user = match user { - Some(user) => user, - None => return false, - }; - - if *user.deref() == self.owner_user { - // owner can always view - return true; - } - - if matches!(self.visibility, RepositoryVisibility::Private) { - // Check if the user can view\ - let access_list = stack - .new_get_setting::<_, AccessList>(&Repository { - owner: self.owner_user.clone(), - name: self.name.clone(), - instance: our_instance.clone(), - }) - .await - .unwrap(); - - access_list - .0 - .iter() - .any(|access_list_user| access_list_user == user.deref()) - } else { - false - } - } - - // This is in it's own function because I assume I'll have to add logic to this later - pub fn open_git2_repository( - &self, - repository_directory: &str, - ) -> Result { - match git2::Repository::open(format!( - "{}/{}/{}/{}", - repository_directory, self.owner_user.instance, self.owner_user.username, self.name - )) { - Ok(repository) => Ok(repository), - Err(err) => { - let err = GitBackendError::FailedOpeningFromDisk(err); - error!("Couldn't open a repository, this is bad! {:?}", err); - - Err(err) - } - } - } -} - -//endregion - -#[derive(Error, Debug)] -pub enum GitBackendError { - #[error("Failed creating repository")] - FailedCreatingRepository(git2::Error), - #[error("Failed inserting into the database")] - FailedInsertingIntoDatabase(sqlx::Error), - #[error("Failed finding repository {owner_user:?}/{name:?}")] - RepositoryNotFound { owner_user: String, name: String }, - #[error("Repository {owner_user:?}/{name:?} already exists")] - RepositoryAlreadyExists { owner_user: String, name: String }, - #[error("Repository couldn't be deleted from the disk")] - CouldNotDeleteFromDisk(std::io::Error), - #[error("Failed deleting repository from database")] - FailedDeletingFromDatabase(sqlx::Error), - #[error("Failed opening repository on disk")] - FailedOpeningFromDisk(git2::Error), - #[error("Couldn't find ref with name `{0}`")] - RefNotFound(String), - #[error("Couldn't find repository head")] - HeadNotFound, - #[error("Couldn't find path in repository `{0}`")] - PathNotFound(String), - #[error("Couldn't find commit for path `{0}`")] - LastCommitNotFound(String), - #[error("Object ID `{0}` is invalid")] - InvalidObjectId(String), - #[error("Blob with ID `{0}` not found")] - BlobNotFound(String), - #[error("Tree with ID `{0}` not found")] - TreeNotFound(String), - #[error("Commit with ID `{0}` not found")] - CommitNotFound(String), - #[error("Parent for commit with ID `{0}` not found")] - CommitParentNotFound(String), - #[error("Failed diffing tree with ID `{0}` to tree with ID `{1}`")] - FailedDiffing(String, String), -} - -pub struct GitBackend { - pub pg_pool: PgPool, - pub repository_folder: String, - pub instance: Instance, - pub stack: Arc>, -} - -impl GitBackend { - pub fn new( - pg_pool: &PgPool, - repository_folder: &str, - instance: impl ToOwned, - stack: Arc>, - ) -> Self { - let instance = instance.to_owned(); - - Self { - pg_pool: pg_pool.clone(), - repository_folder: repository_folder.to_string(), - instance, - stack, - } - } - - pub async fn find_by_owner_user_name( - &self, - user: &User, - repository_name: &str, - ) -> Result { - if let Ok(repository) = sqlx::query_as!(GitRepository, - r#"SELECT owner_user, name, description, visibility as "visibility: _", default_branch FROM repositories WHERE owner_user = $1 AND name = $2"#, - user.to_string(), repository_name) - .fetch_one(&self.pg_pool.clone()) - .await { - Ok(repository) - } else { - Err(GitBackendError::RepositoryNotFound { - owner_user: user.to_string(), - name: repository_name.to_string(), - }) - } - } - - pub async fn delete_by_owner_user_name( - &self, - user: &User, - repository_name: &str, - ) -> Result { - if let Err(err) = std::fs::remove_dir_all(PathBuf::from(format!( - "{}/{}/{}/{}", - self.repository_folder, user.instance, user.username, repository_name - ))) { - let err = GitBackendError::CouldNotDeleteFromDisk(err); - error!( - "Couldn't delete repository from disk, this is bad! {:?}", - err - ); - - return Err(err); - } - - // Delete the repository from the database - match sqlx::query!( - "DELETE FROM repositories WHERE owner_user = $1 AND name = $2", - user.to_string(), - repository_name - ) - .execute(&self.pg_pool.clone()) - .await - { - Ok(deleted) => Ok(deleted.rows_affected()), - Err(err) => Err(GitBackendError::FailedDeletingFromDatabase(err)), - } - } - - pub async fn open_repository_and_check_permissions( - &self, - owner: &User, - name: &str, - requester: &Option, - ) -> Result { - let repository = match self - .find_by_owner_user_name( - // &request.owner.instance.url, - owner, name, - ) - .await - { - Ok(repository) => repository, - Err(err) => return Err(err), - }; - - if let Some(requester) = requester { - if !repository - .can_user_view_repository( - &self.instance, - &Some(requester.clone()), - self.stack.get().unwrap(), - ) - .await - { - return Err(GitBackendError::RepositoryNotFound { - owner_user: repository.owner_user.to_string(), - name: repository.name.clone(), - }); - } - } else if matches!(repository.visibility, RepositoryVisibility::Private) { - // Unauthenticated users can never view private repositories - - return Err(GitBackendError::RepositoryNotFound { - owner_user: repository.owner_user.to_string(), - name: repository.name.clone(), - }); - } - - match repository.open_git2_repository(&self.repository_folder) { - Ok(git) => Ok(git), - Err(err) => Err(err), - } - } - - // TODO: Find where this fits - // TODO: Cache this and general repository tree and invalidate select files on push - // TODO: Find better and faster technique for this - pub fn get_last_commit_of_file( - path: &str, - git: &git2::Repository, - start_commit: &git2::Commit, - ) -> anyhow::Result { - trace!("Getting last commit for file: {}", path); - - let mut revwalk = git.revwalk()?; - revwalk.set_sorting(git2::Sort::TIME)?; - revwalk.push(start_commit.id())?; - - for oid in revwalk { - let oid = oid?; - let commit = git.find_commit(oid)?; - - // Merge commits have 2 or more parents - // Commits with 0 parents are handled different because we can't diff against them - if commit.parent_count() == 0 { - return Ok(commit.into()); - } else if commit.parent_count() == 1 { - let tree = commit.tree()?; - let last_tree = commit.parent(0)?.tree()?; - - // Get the diff between the current tree and the last one - let diff = git.diff_tree_to_tree(Some(&last_tree), Some(&tree), None)?; - - for dd in diff.deltas() { - // Get the path of the current file we're diffing against - let current_path = dd.new_file().path().unwrap(); - - // Path or directory - if current_path.eq(Path::new(&path)) || current_path.starts_with(path) { - return Ok(commit.into()); - } - } - } - } - - Err(GitBackendError::LastCommitNotFound(path.to_string()))? - } - - /// Gets the total amount of commits using revwalk - pub fn get_total_commit_count( - git: &git2::Repository, - start_commit: &git2::Commit, - ) -> anyhow::Result { - // TODO: There must be a better way - let mut revwalk = git.revwalk()?; - revwalk.set_sorting(git2::Sort::TIME)?; - revwalk.push(start_commit.id())?; - - Ok(revwalk.count()) - } - - pub fn get_oid_from_reference( - git: &git2::Repository, - rev: Option<&str>, - ) -> anyhow::Result { - // Try and parse the input as a reference and get the object ID - let mut tree_id = match rev { - None => { - if let Ok(head) = git.head() { - // TODO: Fix for symbolic references - head.target() - } else { - // Nothing in database, render empty tree. - return Err(GitBackendError::HeadNotFound.into()); - } - } - Some(rev_name) => { - // Find the reference, otherwise return GitBackendError - git.refname_to_id(rev_name).ok() - } - }; - - // If the reference wasn't found, try parsing it as a commit ID - if tree_id.is_none() { - if let Ok(oid) = git2::Oid::from_str(rev.as_ref().unwrap()) { - tree_id = Some(oid) - } - } - - // If the commit ID wasn't found, try parsing it as a branch and otherwise return error - if tree_id.is_none() { - match git.find_branch(rev.as_ref().unwrap(), BranchType::Local) { - Ok(branch) => tree_id = branch.get().target(), - Err(_) => { - return Err( - Box::new(GitBackendError::RefNotFound(rev.unwrap().to_string())).into(), - ) - } - } - } - - // Should be safe? - Ok(tree_id.unwrap()) - } - - /// Gets the last commit in a rev - pub fn get_last_commit_in_rev(git: &git2::Repository, rev: &str) -> anyhow::Result { - let oid = Self::get_oid_from_reference(git, Some(rev))?; - - // Walk through the repository commit graph starting at our rev - let mut revwalk = git.revwalk()?; - revwalk.set_sorting(git2::Sort::TIME)?; - revwalk.push(oid)?; - - if let Some(Ok(commit_oid)) = revwalk.next() { - if let Ok(commit) = git - .find_commit(commit_oid) - .map_err(|_| GitBackendError::CommitNotFound(commit_oid.to_string())) - { - return Ok(Commit::from(commit)); - } - } - - Err(GitBackendError::RefNotFound(oid.to_string()).into()) - } -} - -#[async_trait] -impl RepositoryBackend for GitBackend { - async fn exists( - &mut self, - requester: &Option, - repository: &Repository, - ) -> Result { - if let Ok(repository) = self - .find_by_owner_user_name(&repository.owner.clone(), &repository.name) - .await - { - Ok(repository - .can_user_view_repository(&self.instance, requester, self.stack.get().unwrap()) - .await) - } else { - Ok(false) - } - } - - async fn create_repository( - &mut self, - _user: &AuthenticatedUser, - request: &RepositoryCreateRequest, - ) -> Result { - // Check if repository already exists in the database - if let Ok(repository) = self - .find_by_owner_user_name(&request.owner, &request.name) - .await - { - let err = GitBackendError::RepositoryAlreadyExists { - owner_user: repository.owner_user.to_string(), - name: repository.name, - }; - error!("{:?}", err); - - return Err(err); - } - - // Insert the repository into the database - let _ = match sqlx::query_as!(GitRepository, - r#"INSERT INTO repositories VALUES ($1, $2, $3, $4, $5) RETURNING owner_user, name, description, visibility as "visibility: _", default_branch"#, - request.owner.to_string(), request.name, request.description, request.visibility as _, "master") - .fetch_one(&self.pg_pool.clone()) - .await { - Ok(repository) => repository, - Err(err) => { - let err = GitBackendError::FailedInsertingIntoDatabase(err); - error!("Failed inserting into the database! {:?}", err); - - return Err(err); - } - }; - - // Create bare (server side) repository on disk - match git2::Repository::init_bare(PathBuf::from(format!( - "{}/{}/{}/{}", - self.repository_folder, request.owner.instance, request.owner.username, request.name - ))) { - Ok(_) => { - debug!( - "Created new repository with the name {}/{}/{}", - request.owner.instance, request.owner.username, request.name - ); - - let stack = self.stack.get().unwrap(); - - let repository = Repository { - owner: request.owner.clone(), - name: request.name.clone(), - instance: request.instance.as_ref().unwrap_or(&self.instance).clone(), - }; - - stack - .write_setting( - &repository, - Description(request.description.clone().unwrap_or_default()), - ) - .await - .unwrap(); - - stack - .write_setting(&repository, Visibility(request.visibility.clone())) - .await - .unwrap(); - - stack - .write_setting(&repository, DefaultBranch(request.default_branch.clone())) - .await - .unwrap(); - - Ok(repository) - } - Err(err) => { - let err = GitBackendError::FailedCreatingRepository(err); - error!("Failed creating repository on disk!? {:?}", err); - - // Delete repository from database - self.delete_by_owner_user_name(&request.owner, request.name.as_str()) - .await?; - - // ??? - Err(err) - } - } - } - - async fn repository_file_inspect( - &mut self, - requester: &Option, - repository: &Repository, - request: &RepositoryFileInspectRequest, - ) -> Result, Error> { - let git = self - .open_repository_and_check_permissions(&repository.owner, &repository.name, requester) - .await?; - - let tree_id = Self::get_oid_from_reference(&git, request.rev.as_deref())?; - - // unwrap might be dangerous? - // Get the commit from the oid - let commit = git.find_commit(tree_id).unwrap(); - - // this is stupid - let rev = request.rev.clone().unwrap_or_else(|| "master".to_string()); - let mut current_path = rev.clone(); - - // Get the commit tree - let git_tree = if let Some(path) = &request.path { - // Add it to our full path string - current_path.push_str(format!("/{}", path).as_str()); - // Get the specified path, return an error if it wasn't found. - let entry = match commit - .tree() - .unwrap() - .get_path(&PathBuf::from(path)) - .map_err(|_| GitBackendError::PathNotFound(path.to_string())) - { - Ok(entry) => entry, - Err(err) => return Err(Box::new(err).into()), - }; - // Turn the entry into a git tree - entry.to_object(&git).unwrap().as_tree().unwrap().clone() - } else { - commit.tree().unwrap() - }; - - // Iterate over the git tree and collect it into our own tree types - let mut tree = git_tree - .iter() - .map(|entry| { - let object_type = match entry.kind().unwrap() { - git2::ObjectType::Tree => RepositoryObjectType::Tree, - git2::ObjectType::Blob => RepositoryObjectType::Blob, - _ => unreachable!(), - }; - let mut tree_entry = RepositoryTreeEntry::new( - entry.id().to_string().as_str(), - entry.name().unwrap(), - object_type, - entry.filemode(), - ); - - if request.extra_metadata { - // Get the file size if It's a blob - let object = entry.to_object(&git).unwrap(); - if let Some(blob) = object.as_blob() { - tree_entry.size = Some(blob.size()); - } - - // Get the path to the folder the file is in by removing the rev from current_path - let mut path = current_path.replace(&rev, ""); - if path.starts_with('/') { - path.remove(0); - } - - // Format it as the path + file name - let full_path = if path.is_empty() { - entry.name().unwrap().to_string() - } else { - format!("{}/{}", path, entry.name().unwrap()) - }; - - // Get the last commit made to the entry - if let Ok(last_commit) = - GitBackend::get_last_commit_of_file(&full_path, &git, &commit) - { - tree_entry.last_commit = Some(last_commit); - } - } - - tree_entry - }) - .collect::>(); - - // Sort the tree alphabetically and with tree first - tree.sort_unstable_by_key(|entry| entry.name.to_lowercase()); - tree.sort_unstable_by_key(|entry| { - std::cmp::Reverse(format!("{:?}", entry.object_type).to_lowercase()) - }); - - Ok(tree) - } - - async fn repository_file_from_id( - &mut self, - requester: &Option, - repository: &Repository, - request: &RepositoryFileFromIdRequest, - ) -> Result { - let git = self - .open_repository_and_check_permissions(&repository.owner, &repository.name, requester) - .await?; - - // Parse the passed object id - let oid = match git2::Oid::from_str(request.0.as_str()) { - Ok(oid) => oid, - Err(_) => { - return Err(Box::new(GitBackendError::InvalidObjectId(request.0.clone())).into()) - } - }; - - // Find the file and turn it into our own struct - let file = match git.find_blob(oid) { - Ok(blob) => RepositoryFile { - id: blob.id().to_string(), - content: blob.content().to_vec(), - binary: blob.is_binary(), - size: blob.size(), - }, - Err(_) => return Err(Box::new(GitBackendError::BlobNotFound(oid.to_string())).into()), - }; - - Ok(file) - } - - async fn repository_file_from_path( - &mut self, - requester: &Option, - repository: &Repository, - request: &RepositoryFileFromPathRequest, - ) -> Result<(RepositoryFile, String), Error> { - let git = self - .open_repository_and_check_permissions(&repository.owner, &repository.name, requester) - .await?; - - let tree_id = Self::get_oid_from_reference(&git, request.rev.as_deref())?; - - // unwrap might be dangerous? - // Get the commit from the oid - let commit = git.find_commit(tree_id).unwrap(); - - // this is stupid - let mut current_path = request.rev.clone().unwrap_or_else(|| "master".to_string()); - - // Add it to our full path string - current_path.push_str(format!("/{}", request.path).as_str()); - // Get the specified path, return an error if it wasn't found. - let entry = match commit - .tree() - .unwrap() - .get_path(&PathBuf::from(request.path.clone())) - .map_err(|_| GitBackendError::PathNotFound(request.path.to_string())) - { - Ok(entry) => entry, - Err(err) => return Err(Box::new(err).into()), - }; - - // Find the file and turn it into our own struct - let file = match git.find_blob(entry.id()) { - Ok(blob) => RepositoryFile { - id: blob.id().to_string(), - content: blob.content().to_vec(), - binary: blob.is_binary(), - size: blob.size(), - }, - Err(_) => { - return Err(Box::new(GitBackendError::BlobNotFound(entry.id().to_string())).into()) - } - }; - - Ok((file, commit.id().to_string())) - } - - async fn repository_commit_from_id( - &mut self, - requester: &Option, - repository: &Repository, - request: &RepositoryCommitFromIdRequest, - ) -> Result { - let git = self - .open_repository_and_check_permissions(&repository.owner, &repository.name, requester) - .await?; - - // Parse the passed object ids - let oid = git2::Oid::from_str(request.0.as_str()) - .map_err(|_| GitBackendError::InvalidObjectId(request.0.clone()))?; - - // Get the commit from the oid - let commit = git - .find_commit(oid) - .map_err(|_| GitBackendError::CommitNotFound(oid.to_string()))?; - - Ok(Commit::from(commit)) - } - - async fn repository_last_commit_of_file( - &mut self, - requester: &Option, - repository: &Repository, - request: &RepositoryLastCommitOfFileRequest, - ) -> Result { - let git = self - .open_repository_and_check_permissions(&repository.owner, &repository.name, requester) - .await?; - - // Parse the passed object ids - let oid = git2::Oid::from_str(&request.start_commit) - .map_err(|_| GitBackendError::InvalidObjectId(request.start_commit.clone()))?; - - // Get the commit from the oid - let commit = git - .find_commit(oid) - .map_err(|_| GitBackendError::CommitNotFound(oid.to_string()))?; - - // Find the last commit of the file - let commit = GitBackend::get_last_commit_of_file(request.path.as_str(), &git, &commit)?; - - Ok(commit) - } - - async fn repository_get_statistics( - &mut self, - requester: &Option, - repository: &Repository, - request: &RepositoryStatisticsRequest, - ) -> Result { - let git = self - .open_repository_and_check_permissions(&repository.owner, &repository.name, requester) - .await?; - - let tree_id = Self::get_oid_from_reference(&git, request.rev.as_deref())?; - - // unwrap might be dangerous? - // Get the commit from the oid - let commit = git.find_commit(tree_id).unwrap(); - - // Count the amount of branches and tags - let mut branches = 0; - let mut tags = 0; - if let Ok(references) = git.references() { - for reference in references.flatten() { - if reference.is_branch() { - branches += 1; - } else if reference.is_tag() { - tags += 1; - } - } - } - - Ok(RepositoryStatistics { - commits: GitBackend::get_total_commit_count(&git, &commit)?, - branches, - tags, - }) - } - - async fn repository_get_branches( - &mut self, - requester: &Option, - repository: &Repository, - _request: &RepositoryBranchesRequest, - ) -> Result, Error> { - let git = self - .open_repository_and_check_permissions(&repository.owner, &repository.name, requester) - .await?; - - let mut branches = vec![]; - - for branch in git.branches(None)? { - let branch = branch?; - - let Some(name) = branch.0.name().ok().flatten() else { - continue; - }; - - // TODO: Non UTF-8? - let commit = - GitBackend::get_last_commit_in_rev(&git, branch.0.get().name().unwrap()).ok(); - - // TODO: Implement stale with configurable age - let stale = false; - - branches.push(RepositoryBranch { - name: name.to_string(), - stale, - last_commit: commit, - }) - } - - Ok(branches) - } - - async fn repository_diff( - &mut self, - requester: &Option, - repository: &Repository, - request: &RepositoryDiffRequest, - ) -> Result { - let git = self - .open_repository_and_check_permissions(&repository.owner, &repository.name, requester) - .await?; - - // Parse the passed object ids - let oid_old = git2::Oid::from_str(request.old_id.as_str()) - .map_err(|_| GitBackendError::InvalidObjectId(request.old_id.clone()))?; - let oid_new = git2::Oid::from_str(request.new_id.as_str()) - .map_err(|_| GitBackendError::InvalidObjectId(request.new_id.clone()))?; - - // Get the ids associates commits - let commit_old = git - .find_commit(oid_old) - .map_err(|_| GitBackendError::CommitNotFound(oid_old.to_string()))?; - let commit_new = git - .find_commit(oid_new) - .map_err(|_| GitBackendError::CommitNotFound(oid_new.to_string()))?; - - // Get the commit trees - let tree_old = commit_old - .tree() - .map_err(|_| GitBackendError::TreeNotFound(oid_old.to_string()))?; - let tree_new = commit_new - .tree() - .map_err(|_| GitBackendError::TreeNotFound(oid_new.to_string()))?; - - // Diff the two trees against each other - let diff = git - .diff_tree_to_tree(Some(&tree_old), Some(&tree_new), None) - .map_err(|_| { - GitBackendError::FailedDiffing(oid_old.to_string(), oid_new.to_string()) - })?; - - // Should be safe to unwrap? - let stats = diff.stats().unwrap(); - let mut files: Vec = vec![]; - - diff.deltas().enumerate().for_each(|(i, delta)| { - // Parse the old file info from the delta - let old_file_info = match delta.old_file().exists() { - true => Some(RepositoryDiffFileInfo { - id: delta.old_file().id().to_string(), - path: delta - .old_file() - .path() - .unwrap() - .to_str() - .unwrap() - .to_string(), - size: delta.old_file().size(), - binary: delta.old_file().is_binary(), - }), - false => None, - }; - // Parse the new file info from the delta - let new_file_info = match delta.new_file().exists() { - true => Some(RepositoryDiffFileInfo { - id: delta.new_file().id().to_string(), - path: delta - .new_file() - .path() - .unwrap() - .to_str() - .unwrap() - .to_string(), - size: delta.new_file().size(), - binary: delta.new_file().is_binary(), - }), - false => None, - }; - - let mut chunks: Vec = vec![]; - if let Some(patch) = git2::Patch::from_diff(&diff, i).ok().flatten() { - for chunk_num in 0..patch.num_hunks() { - if let Ok((chunk, chunk_num_lines)) = patch.hunk(chunk_num) { - let mut lines: Vec = vec![]; - - for line_num in 0..chunk_num_lines { - if let Ok(line) = patch.line_in_hunk(chunk_num, line_num) { - if let Ok(line_utf8) = String::from_utf8(line.content().to_vec()) { - lines.push(RepositoryChunkLine { - change_type: line.origin_value().into(), - content: line_utf8, - old_line_num: line.old_lineno(), - new_line_num: line.new_lineno(), - }); - } - - continue; - } - } - - chunks.push(RepositoryDiffFileChunk { - header: String::from_utf8(chunk.header().to_vec()).ok(), - old_start: chunk.old_start(), - old_lines: chunk.old_lines(), - new_start: chunk.new_start(), - new_lines: chunk.new_lines(), - lines, - }); - } - } - }; - - let file = RepositoryDiffFile { - status: RepositoryDiffFileStatus::from(delta.status()), - old_file_info, - new_file_info, - chunks, - }; - - files.push(file); - }); - - Ok(RepositoryDiff { - new_commit: Commit::from(commit_new), - files_changed: stats.files_changed(), - insertions: stats.insertions(), - deletions: stats.deletions(), - files, - }) - } - - async fn repository_diff_patch( - &mut self, - requester: &Option, - repository: &Repository, - request: &RepositoryDiffPatchRequest, - ) -> Result { - let git = self - .open_repository_and_check_permissions(&repository.owner, &repository.name, requester) - .await?; - - // Parse the passed object ids - let oid_old = git2::Oid::from_str(request.old_id.as_str()) - .map_err(|_| GitBackendError::InvalidObjectId(request.old_id.clone()))?; - let oid_new = git2::Oid::from_str(request.new_id.as_str()) - .map_err(|_| GitBackendError::InvalidObjectId(request.new_id.clone()))?; - - // Get the ids associates commits - let commit_old = git - .find_commit(oid_old) - .map_err(|_| GitBackendError::CommitNotFound(oid_old.to_string()))?; - let commit_new = git - .find_commit(oid_new) - .map_err(|_| GitBackendError::CommitNotFound(oid_new.to_string()))?; - - // Get the commit trees - let tree_old = commit_old - .tree() - .map_err(|_| GitBackendError::TreeNotFound(oid_old.to_string()))?; - let tree_new = commit_new - .tree() - .map_err(|_| GitBackendError::TreeNotFound(oid_new.to_string()))?; - - // Diff the two trees against each other - let diff = git - .diff_tree_to_tree(Some(&tree_old), Some(&tree_new), None) - .map_err(|_| { - GitBackendError::FailedDiffing(oid_old.to_string(), oid_new.to_string()) - })?; - - // Print the entire patch - let mut patch = String::new(); - - diff.print(git2::DiffFormat::Patch, |_, _, line| { - match line.origin() { - '+' | '-' | ' ' => patch.push(line.origin()), - _ => {} - } - patch.push_str(std::str::from_utf8(line.content()).unwrap()); - true - }) - .unwrap(); - - Ok(patch) - } - - async fn repository_commit_before( - &mut self, - requester: &Option, - repository: &Repository, - request: &RepositoryCommitBeforeRequest, - ) -> Result { - let git = self - .open_repository_and_check_permissions(&repository.owner, &repository.name, requester) - .await?; - - // Parse the passed object id - let oid = match git2::Oid::from_str(request.0.as_str()) { - Ok(oid) => oid, - Err(_) => { - return Err(Box::new(GitBackendError::InvalidObjectId(request.0.clone())).into()) - } - }; - - // Find the commit using the parsed oid - let commit = match git.find_commit(oid) { - Ok(commit) => commit, - Err(_) => return Err(Box::new(GitBackendError::CommitNotFound(oid.to_string())).into()), - }; - - // Get the first parent it has - let parent = commit.parent(0); - if let Ok(parent) = parent { - return Ok(Commit::from(parent)); - } else { - // TODO: See if can be done better - // Walk through the repository commit graph starting at our current commit - let mut revwalk = git.revwalk()?; - revwalk.set_sorting(git2::Sort::TIME)?; - revwalk.push(commit.id())?; - - if let Some(Ok(before_commit_oid)) = revwalk.next() { - // Find the commit using the parsed oid - if let Ok(before_commit) = git.find_commit(before_commit_oid) { - return Ok(Commit::from(before_commit)); - } - } - - Err(Box::new(GitBackendError::CommitParentNotFound(oid.to_string())).into()) - } - } -} - -impl IssuesBackend for GitBackend { - fn issues_count( - &mut self, - _requester: &Option, - _request: &RepositoryIssuesCountRequest, - ) -> Result { - todo!() - } - - fn issue_labels( - &mut self, - _requester: &Option, - _request: &RepositoryIssueLabelsRequest, - ) -> Result, Error> { - todo!() - } - - fn issues( - &mut self, - _requester: &Option, - _request: &RepositoryIssuesRequest, - ) -> Result, Error> { - todo!() - } -} - -#[allow(unused)] -#[derive(Debug, sqlx::FromRow)] -struct RepositoryMetadata { - pub repository: String, - pub name: String, - pub value: String, -} diff --git a/giterated-daemon/src/backend/github.rs b/giterated-daemon/src/backend/github.rs deleted file mode 100644 index 051c8cb..0000000 --- a/giterated-daemon/src/backend/github.rs +++ /dev/null @@ -1,2 +0,0 @@ -//! TODO: GitHub backend to allow for login with GitHub -//! accounts and interacting with GitHub repositories diff --git a/giterated-daemon/src/backend/mod.rs b/giterated-daemon/src/backend/mod.rs deleted file mode 100644 index 5b1b836..0000000 --- a/giterated-daemon/src/backend/mod.rs +++ /dev/null @@ -1,159 +0,0 @@ -pub mod discovery; -pub mod git; -pub mod github; -pub mod settings; -pub mod user; - -use anyhow::Error; -use async_trait::async_trait; -use giterated_stack::AuthenticatedUser; -use serde_json::Value; - -use crate::backend::git::GitBackendError; -use giterated_models::authenticated::UserAuthenticationToken; - -use giterated_models::instance::{ - AuthenticationTokenRequest, Instance, RegisterAccountRequest, RepositoryCreateRequest, -}; - -use giterated_models::repository::{ - Commit, IssueLabel, Repository, RepositoryBranch, RepositoryBranchesRequest, - RepositoryCommitBeforeRequest, RepositoryCommitFromIdRequest, RepositoryDiff, - RepositoryDiffPatchRequest, RepositoryDiffRequest, RepositoryFile, RepositoryFileFromIdRequest, - RepositoryFileFromPathRequest, RepositoryFileInspectRequest, RepositoryIssue, - RepositoryIssueLabelsRequest, RepositoryIssuesCountRequest, RepositoryIssuesRequest, - RepositoryLastCommitOfFileRequest, RepositoryStatistics, RepositoryStatisticsRequest, - RepositorySummary, RepositoryTreeEntry, -}; - -use giterated_models::user::User; - -#[async_trait] -pub trait RepositoryBackend { - async fn create_repository( - &mut self, - user: &AuthenticatedUser, - request: &RepositoryCreateRequest, - ) -> Result; - async fn repository_file_inspect( - &mut self, - requester: &Option, - repository: &Repository, - request: &RepositoryFileInspectRequest, - ) -> Result, Error>; - async fn repository_file_from_id( - &mut self, - requester: &Option, - repository: &Repository, - request: &RepositoryFileFromIdRequest, - ) -> Result; - async fn repository_file_from_path( - &mut self, - requester: &Option, - repository: &Repository, - request: &RepositoryFileFromPathRequest, - ) -> Result<(RepositoryFile, String), Error>; - async fn repository_commit_from_id( - &mut self, - requester: &Option, - repository: &Repository, - request: &RepositoryCommitFromIdRequest, - ) -> Result; - async fn repository_last_commit_of_file( - &mut self, - requester: &Option, - repository: &Repository, - request: &RepositoryLastCommitOfFileRequest, - ) -> Result; - async fn repository_diff( - &mut self, - requester: &Option, - repository: &Repository, - request: &RepositoryDiffRequest, - ) -> Result; - async fn repository_diff_patch( - &mut self, - requester: &Option, - repository: &Repository, - request: &RepositoryDiffPatchRequest, - ) -> Result; - async fn repository_commit_before( - &mut self, - requester: &Option, - repository: &Repository, - request: &RepositoryCommitBeforeRequest, - ) -> Result; - async fn repository_get_statistics( - &mut self, - requester: &Option, - repository: &Repository, - request: &RepositoryStatisticsRequest, - ) -> Result; - async fn repository_get_branches( - &mut self, - requester: &Option, - repository: &Repository, - request: &RepositoryBranchesRequest, - ) -> Result, Error>; - async fn exists( - &mut self, - requester: &Option, - repository: &Repository, - ) -> Result; -} - -pub trait IssuesBackend { - fn issues_count( - &mut self, - requester: &Option, - request: &RepositoryIssuesCountRequest, - ) -> Result; - fn issue_labels( - &mut self, - requester: &Option, - request: &RepositoryIssueLabelsRequest, - ) -> Result, Error>; - fn issues( - &mut self, - requester: &Option, - request: &RepositoryIssuesRequest, - ) -> Result, Error>; -} - -#[async_trait::async_trait] -pub trait AuthBackend { - async fn register( - &mut self, - request: RegisterAccountRequest, - ) -> Result; - - async fn login( - &mut self, - source: &Instance, - request: AuthenticationTokenRequest, - ) -> Result; -} - -#[async_trait::async_trait] -pub trait UserBackend: AuthBackend { - async fn exists(&mut self, user: &User) -> Result; - async fn repositories_for_user( - &mut self, - requester: &Option, - user: &User, - ) -> Result, Error>; -} - -#[async_trait::async_trait] -pub trait MetadataBackend { - async fn user_get(&mut self, user: &User, name: &str) -> Result; - async fn user_write(&mut self, user: &User, name: &str, setting: Value) -> Result<(), Error>; - async fn repository_get(&mut self, repository: &Repository, name: &str) - -> Result; - async fn repository_write( - &mut self, - repository: &Repository, - name: &str, - setting: Value, - ) -> Result<(), Error>; -} diff --git a/giterated-daemon/src/backend/settings.rs b/giterated-daemon/src/backend/settings.rs deleted file mode 100644 index 037b82b..0000000 --- a/giterated-daemon/src/backend/settings.rs +++ /dev/null @@ -1,91 +0,0 @@ -use std::sync::Arc; - -use anyhow::Error; - -use giterated_models::repository::Repository; - -use giterated_models::user::User; - -use giterated_stack::GiteratedStack; -use serde_json::Value; -use sqlx::PgPool; -use tokio::sync::OnceCell; - -use super::MetadataBackend; - -pub struct DatabaseSettings { - pub pg_pool: PgPool, - pub stack: Arc>, -} - -#[async_trait::async_trait] -impl MetadataBackend for DatabaseSettings { - async fn user_get(&mut self, user: &User, name: &str) -> Result { - let row = sqlx::query_as!( - UserSettingRow, - "SELECT * FROM user_settings WHERE username = $1 AND name = $2", - user.username, - name - ) - .fetch_one(&self.pg_pool) - .await?; - - let setting = serde_json::from_str(&row.value)?; - - Ok(setting) - } - async fn user_write(&mut self, user: &User, name: &str, value: Value) -> Result<(), Error> { - sqlx::query!("INSERT INTO user_settings VALUES ($1, $2, $3) ON CONFLICT (username, name) DO UPDATE SET value = $3", - user.username, name, serde_json::to_string(&value)?) - .execute(&self.pg_pool).await?; - - Ok(()) - } - - async fn repository_get( - &mut self, - repository: &Repository, - name: &str, - ) -> Result { - let row = sqlx::query_as!( - RepositorySettingRow, - "SELECT * FROM repository_settings WHERE repository = $1 AND name = $2", - repository.to_string(), - name - ) - .fetch_one(&self.pg_pool) - .await?; - - let setting = serde_json::from_str(&row.value)?; - - Ok(setting) - } - async fn repository_write( - &mut self, - repository: &Repository, - name: &str, - value: Value, - ) -> Result<(), Error> { - sqlx::query!("INSERT INTO repository_settings VALUES ($1, $2, $3) ON CONFLICT (repository, name) DO UPDATE SET value = $3", - repository.to_string(), name, serde_json::to_string(&value)?) - .execute(&self.pg_pool).await?; - - Ok(()) - } -} - -#[allow(unused)] -#[derive(Debug, sqlx::FromRow)] -pub struct UserSettingRow { - pub username: String, - pub name: String, - pub value: String, -} - -#[allow(unused)] -#[derive(Debug, sqlx::FromRow)] -pub struct RepositorySettingRow { - pub repository: String, - pub name: String, - pub value: String, -} diff --git a/giterated-daemon/src/backend/user.rs b/giterated-daemon/src/backend/user.rs deleted file mode 100644 index c8de1ac..0000000 --- a/giterated-daemon/src/backend/user.rs +++ /dev/null @@ -1,242 +0,0 @@ -use anyhow::Error; -use futures_util::StreamExt; -use giterated_models::authenticated::UserAuthenticationToken; - -use giterated_models::instance::{AuthenticationTokenRequest, Instance, RegisterAccountRequest}; - -use giterated_models::repository::{Repository, RepositorySummary}; -use giterated_models::user::User; - -use giterated_stack::AuthenticatedUser; -use std::sync::Arc; - -use aes_gcm::{aead::Aead, AeadCore, Aes256Gcm, Key, KeyInit}; -use argon2::{password_hash::SaltString, Argon2, PasswordHash, PasswordHasher, PasswordVerifier}; -use base64::{engine::general_purpose::STANDARD, Engine as _}; - -use rsa::{ - pkcs8::{EncodePrivateKey, EncodePublicKey}, - rand_core::OsRng, - RsaPrivateKey, RsaPublicKey, -}; - -use secrecy::ExposeSecret; - -use sqlx::{Either, PgPool}; -use tokio::sync::Mutex; - -use crate::authentication::AuthenticationTokenGranter; -use crate::backend::git::GitRepository; - -use super::{AuthBackend, MetadataBackend, UserBackend}; - -pub struct UserAuth { - pub pg_pool: PgPool, - pub this_instance: Instance, - pub auth_granter: Arc>, - pub settings_provider: Arc>, -} - -impl UserAuth { - pub fn new( - pool: PgPool, - this_instance: &Instance, - granter: Arc>, - settings_provider: Arc>, - ) -> Self { - Self { - pg_pool: pool, - this_instance: this_instance.clone(), - auth_granter: granter, - settings_provider, - } - } -} - -#[async_trait::async_trait] -impl UserBackend for UserAuth { - async fn exists(&mut self, user: &User) -> Result { - Ok(sqlx::query_as!( - UserRow, - r#"SELECT * FROM users WHERE username = $1"#, - user.username - ) - .fetch_one(&self.pg_pool.clone()) - .await - .is_ok()) - } - - async fn repositories_for_user( - &mut self, - _requester: &Option, - user: &User, - ) -> Result, Error> { - let mut repositories = sqlx::query_as!( - GitRepository, - r#"SELECT owner_user, name, description, visibility as "visibility: _", default_branch FROM repositories WHERE owner_user = $1"#, - user.to_string() - ) - .fetch_many(&self.pg_pool); - - let mut return_repositories = vec![]; - - while let Some(Ok(Either::Right(repository_row))) = repositories.next().await { - return_repositories.push(RepositorySummary { - repository: Repository { - owner: repository_row.owner_user.clone(), - name: repository_row.name, - instance: self.this_instance.clone(), - }, - owner: repository_row.owner_user, - visibility: repository_row.visibility, - description: repository_row.description, - last_commit: None, - }) - } - - Ok(return_repositories) - } -} - -#[async_trait::async_trait] -impl AuthBackend for UserAuth { - async fn register( - &mut self, - request: RegisterAccountRequest, - ) -> Result { - const BITS: usize = 2048; - - let private_key = RsaPrivateKey::new(&mut OsRng, BITS).unwrap(); - let public_key = RsaPublicKey::from(&private_key); - - let key = { - let mut target: [u8; 32] = [0; 32]; - - let mut index = 0; - let mut iterator = request.password.expose_secret().0.as_bytes().iter(); - while index < 32 { - if let Some(next) = iterator.next() { - target[index] = *next; - index += 1; - } else { - iterator = request.password.expose_secret().0.as_bytes().iter(); - } - } - - target - }; - - let key: &Key = &key.into(); - let cipher = Aes256Gcm::new(key); - let nonce = Aes256Gcm::generate_nonce(&mut OsRng); - let ciphertext = cipher - .encrypt(&nonce, private_key.to_pkcs8_der().unwrap().as_bytes()) - .unwrap(); - - let private_key_enc = format!("{}#{}", STANDARD.encode(nonce), STANDARD.encode(ciphertext)); - - let salt = SaltString::generate(&mut OsRng); - - let argon2 = Argon2::default(); - - let password_hash = argon2 - .hash_password(request.password.expose_secret().0.as_bytes(), &salt) - .unwrap() - .to_string(); - - let user = match sqlx::query_as!( - UserRow, - r#"INSERT INTO users VALUES ($1, $2, $3, $4, $5) returning *"#, - request.username, - "example.com", - password_hash, - public_key - .to_public_key_pem(rsa::pkcs8::LineEnding::LF) - .unwrap(), - private_key_enc - ) - .fetch_one(&self.pg_pool) - .await - { - Ok(user) => user, - Err(err) => { - error!("Failed inserting into the database! {:?}", err); - - return Err(err.into()); - } - }; - - let mut granter = self.auth_granter.lock().await; - let token = granter - .create_token_for( - &User { - username: user.username, - instance: self.this_instance.clone(), - }, - &self.this_instance, - ) - .await; - - Ok(UserAuthenticationToken::from(token)) - } - - async fn login( - &mut self, - source: &Instance, - request: AuthenticationTokenRequest, - ) -> Result { - let user = sqlx::query_as!( - UserRow, - r#"SELECT * FROM users WHERE username = $1"#, - request.username - ) - .fetch_one(&self.pg_pool) - .await?; - - let hash = PasswordHash::new(&user.password).unwrap(); - - if Argon2::default() - .verify_password(request.password.expose_secret().0.as_bytes(), &hash) - .is_err() - { - return Err(Error::from(AuthenticationError::InvalidPassword)); - } - - let mut granter = self.auth_granter.lock().await; - let token = granter - .create_token_for( - &User { - username: user.username, - instance: self.this_instance.clone(), - }, - source, - ) - .await; - - Ok(UserAuthenticationToken::from(token)) - } -} - -#[allow(unused)] -#[derive(Debug, sqlx::FromRow)] -struct UserRow { - pub username: String, - pub email: Option, - pub password: String, - pub public_key: String, - pub enc_private_key: Vec, -} - -#[allow(unused)] -#[derive(Debug, sqlx::FromRow)] -struct UserValue { - pub username: String, - pub name: String, - pub value: String, -} - -#[derive(Debug, thiserror::Error)] -pub enum AuthenticationError { - #[error("invalid password")] - InvalidPassword, -} diff --git a/giterated-daemon/src/database_backend/handler.rs b/giterated-daemon/src/database_backend/handler.rs deleted file mode 100644 index ac35403..0000000 --- a/giterated-daemon/src/database_backend/handler.rs +++ /dev/null @@ -1,404 +0,0 @@ -use giterated_models::{ - authenticated::UserAuthenticationToken, - error::{InstanceError, IntoInternalError, OperationError, RepositoryError, UserError}, - instance::{ - AuthenticationTokenRequest, Instance, RegisterAccountRequest, RepositoryCreateRequest, - }, - object_backend::ObjectBackend, - repository::{ - Commit, DefaultBranch, Description, LatestCommit, Repository, RepositoryBranch, - RepositoryBranchesRequest, RepositoryCommitBeforeRequest, RepositoryCommitFromIdRequest, - RepositoryDiff, RepositoryDiffPatchRequest, RepositoryDiffRequest, RepositoryFile, - RepositoryFileFromIdRequest, RepositoryFileFromPathRequest, RepositoryFileInspectRequest, - RepositoryInfoRequest, RepositoryLastCommitOfFileRequest, RepositoryStatistics, - RepositoryStatisticsRequest, RepositorySummary, RepositoryView, Visibility, - }, - user::{User, UserRepositoriesRequest}, -}; -use giterated_stack::{AuthenticatedUser, GiteratedStack, OperationState, StackOperationState}; - -use super::DatabaseBackend; - -pub async fn user_get_repositories( - object: User, - _operation: UserRepositoriesRequest, - state: DatabaseBackend, - OperationState(_operation_state): OperationState, - requester: Option, -) -> Result, OperationError> { - let object = object.clone(); - - let mut user_backend = state.user_backend.lock().await; - let repositories_response = user_backend - .repositories_for_user(&requester, &object) - .await - .as_internal_error()?; - drop(user_backend); - let mut repositories_backend = state.repository_backend.lock().await; - - let mut repositories = vec![]; - - for repository in repositories_response { - if repositories_backend - .exists(&requester, &repository.repository) - .await - .as_internal_error()? - { - repositories.push(repository); - } - } - - Ok(repositories) -} - -pub async fn repository_info( - object: Repository, - operation: RepositoryInfoRequest, - state: DatabaseBackend, - OperationState(operation_state): OperationState, - backend: GiteratedStack, - requester: Option, -) -> Result> { - info!("Called"); - let mut object = backend - .get_object::(&object.to_string(), &operation_state) - .await - .unwrap(); - let mut repository_backend = state.repository_backend.lock().await; - let tree = repository_backend - .repository_file_inspect( - &requester, - object.object(), - &RepositoryFileInspectRequest { - extra_metadata: operation.extra_metadata, - path: operation.path.clone(), - rev: operation.rev.clone(), - }, - ) - .await - .as_internal_error()?; - - let statistics = repository_backend - .repository_get_statistics( - &requester, - object.object(), - &RepositoryStatisticsRequest { - rev: operation.rev.clone(), - }, - ) - .await - .as_internal_error()?; - drop(repository_backend); - - let info = RepositoryView { - name: object.object().name.clone(), - owner: object.object().owner.clone(), - description: object.get::(&operation_state).await.ok(), - visibility: object - .get::(&operation_state) - .await - .as_internal_error()?, - default_branch: object - .get::(&operation_state) - .await - .as_internal_error()?, - // TODO: Can't be a simple get function, this needs to be returned alongside the tree as this differs depending on the rev and path. - latest_commit: object.get::(&operation_state).await.ok(), - stats: statistics, - tree_rev: operation.rev.clone(), - tree, - }; - - Ok(info) -} - -pub async fn repository_get_statistics( - object: Repository, - operation: RepositoryStatisticsRequest, - state: DatabaseBackend, - OperationState(operation_state): OperationState, - backend: GiteratedStack, - requester: Option, -) -> Result> { - let object = backend - .get_object::(&object.to_string(), &operation_state) - .await - .unwrap(); - - let mut repository_backend = state.repository_backend.lock().await; - let statistics = repository_backend - .repository_get_statistics( - &requester, - object.object(), - &RepositoryStatisticsRequest { - rev: operation.rev.clone(), - }, - ) - .await - .as_internal_error()?; - drop(repository_backend); - - Ok(statistics) -} - -pub async fn repository_get_branches( - object: Repository, - operation: RepositoryBranchesRequest, - state: DatabaseBackend, - OperationState(operation_state): OperationState, - backend: GiteratedStack, - requester: Option, -) -> Result, OperationError> { - let object = backend - .get_object::(&object.to_string(), &operation_state) - .await - .unwrap(); - - let mut repository_backend = state.repository_backend.lock().await; - let branches = repository_backend - .repository_get_branches(&requester, object.object(), &operation) - .await - .as_internal_error()?; - drop(repository_backend); - - Ok(branches) -} - -pub async fn repository_file_from_id( - object: Repository, - operation: RepositoryFileFromIdRequest, - state: DatabaseBackend, - OperationState(operation_state): OperationState, - backend: GiteratedStack, - requester: Option, -) -> Result> { - let object = backend - .get_object::(&object.to_string(), &operation_state) - .await - .unwrap(); - - let mut repository_backend = state.repository_backend.lock().await; - let file = repository_backend - .repository_file_from_id( - &requester, - object.object(), - &RepositoryFileFromIdRequest(operation.0.clone()), - ) - .await - .as_internal_error()?; - drop(repository_backend); - - Ok(file) -} - -pub async fn repository_file_from_path( - object: Repository, - operation: RepositoryFileFromPathRequest, - state: DatabaseBackend, - OperationState(operation_state): OperationState, - backend: GiteratedStack, - requester: Option, -) -> Result<(RepositoryFile, String), OperationError> { - let object = backend - .get_object::(&object.to_string(), &operation_state) - .await - .unwrap(); - - let mut repository_backend = state.repository_backend.lock().await; - let file = repository_backend - .repository_file_from_path( - &requester, - object.object(), - &RepositoryFileFromPathRequest { - rev: operation.rev.clone(), - path: operation.path.clone(), - }, - ) - .await - .as_internal_error()?; - drop(repository_backend); - - Ok(file) -} - -pub async fn repository_last_commit_of_file( - object: Repository, - operation: RepositoryLastCommitOfFileRequest, - state: DatabaseBackend, - OperationState(operation_state): OperationState, - backend: GiteratedStack, - requester: Option, -) -> Result> { - let object = backend - .get_object::(&object.to_string(), &operation_state) - .await - .unwrap(); - - let mut repository_backend = state.repository_backend.lock().await; - let commit = repository_backend - .repository_last_commit_of_file( - &requester, - object.object(), - &RepositoryLastCommitOfFileRequest { - start_commit: operation.start_commit.clone(), - path: operation.path.clone(), - }, - ) - .await - .as_internal_error()?; - drop(repository_backend); - - Ok(commit) -} - -pub async fn repository_commit_by_id( - object: Repository, - operation: RepositoryCommitFromIdRequest, - state: DatabaseBackend, - OperationState(operation_state): OperationState, - backend: GiteratedStack, - requester: Option, -) -> Result> { - let object = backend - .get_object::(&object.to_string(), &operation_state) - .await - .unwrap(); - - let mut repository_backend = state.repository_backend.lock().await; - let commit = repository_backend - .repository_commit_from_id( - &requester, - object.object(), - &RepositoryCommitFromIdRequest(operation.0.clone()), - ) - .await - .as_internal_error()?; - drop(repository_backend); - - Ok(commit) -} - -pub async fn repository_diff( - object: Repository, - operation: RepositoryDiffRequest, - state: DatabaseBackend, - OperationState(operation_state): OperationState, - backend: GiteratedStack, - requester: Option, -) -> Result> { - let object = backend - .get_object::(&object.to_string(), &operation_state) - .await - .unwrap(); - - let mut repository_backend = state.repository_backend.lock().await; - let diff = repository_backend - .repository_diff(&requester, object.object(), &operation) - .await - .as_internal_error()?; - drop(repository_backend); - - Ok(diff) -} - -pub async fn repository_diff_patch( - object: Repository, - operation: RepositoryDiffPatchRequest, - state: DatabaseBackend, - OperationState(operation_state): OperationState, - backend: GiteratedStack, - requester: Option, -) -> Result> { - let object = backend - .get_object::(&object.to_string(), &operation_state) - .await - .unwrap(); - - let mut repository_backend = state.repository_backend.lock().await; - let patch = repository_backend - .repository_diff_patch(&requester, object.object(), &operation) - .await - .as_internal_error()?; - drop(repository_backend); - - Ok(patch) -} - -pub async fn repository_commit_before( - object: Repository, - operation: RepositoryCommitBeforeRequest, - state: DatabaseBackend, - OperationState(operation_state): OperationState, - backend: GiteratedStack, - requester: Option, -) -> Result> { - let object = backend - .get_object::(&object.to_string(), &operation_state) - .await - .unwrap(); - - let mut repository_backend = state.repository_backend.lock().await; - let file = repository_backend - .repository_commit_before(&requester, object.object(), &operation) - .await - .as_internal_error()?; - drop(repository_backend); - - Ok(file) -} - -pub async fn instance_authentication_request( - object: Instance, - operation: AuthenticationTokenRequest, - state: DatabaseBackend, - OperationState(_operation_state): OperationState, - // Authorizes the request for SAME-INSTANCE - // _authorized_instance: AuthorizedInstance, -) -> Result> { - let mut backend = state.user_backend.lock().await; - - backend - .login(&object, operation.clone()) - .await - .as_internal_error() -} - -pub async fn instance_registration_request( - _object: Instance, - operation: RegisterAccountRequest, - state: DatabaseBackend, - OperationState(_operation_state): OperationState, // Authorizes the request for SAME-INSTANCE - // _authorized_instance: AuthorizedInstance, -) -> Result> { - let mut backend = state.user_backend.lock().await; - - backend - .register(operation.clone()) - .await - .as_internal_error() -} - -pub async fn instance_create_repository_request( - _object: Instance, - operation: RepositoryCreateRequest, - state: DatabaseBackend, - OperationState(_operation_state): OperationState, - requester: AuthenticatedUser, - // Authorizes the request for SAME-INSTANCE - // _authorized_instance: AuthorizedInstance, -) -> Result> { - let mut backend = state.repository_backend.lock().await; - - backend - .create_repository(&requester, &operation) - .await - .as_internal_error() -} - -pub async fn repository_latest_commit( - _repository: Repository, - _state: DatabaseBackend, - OperationState(_operation_state): OperationState, -) -> Result> { - Ok(LatestCommit(None)) -} diff --git a/giterated-daemon/src/database_backend/mod.rs b/giterated-daemon/src/database_backend/mod.rs deleted file mode 100644 index 26e2ee5..0000000 --- a/giterated-daemon/src/database_backend/mod.rs +++ /dev/null @@ -1,178 +0,0 @@ -pub mod handler; -pub mod updates; - -use std::any::Any; -use std::sync::Arc; - -use anyhow::Context; - -use giterated_models::instance::Instance; -use giterated_models::repository::{ - CommitBodyType, DefaultBranch, Description, Repository, Visibility, -}; -use giterated_models::user::{Bio, DisplayName, User}; -use giterated_stack::provider::MetadataProvider; -use giterated_stack::{AnyObject, AnySetting, GiteratedStack, ObjectMeta, SubstackBuilder}; -use giterated_stack::{SettingMeta, StackOperationState}; -use serde_json::Value; -use sqlx::PgPool; -use std::fmt::Debug; -use tokio::sync::{Mutex, OnceCell}; - -use crate::backend::settings::{RepositorySettingRow, UserSettingRow}; -use crate::backend::{RepositoryBackend, UserBackend}; - -use self::handler::{ - instance_authentication_request, instance_create_repository_request, - instance_registration_request, repository_commit_before, repository_commit_by_id, - repository_diff, repository_diff_patch, repository_file_from_id, repository_file_from_path, - repository_get_branches, repository_get_statistics, repository_info, - repository_last_commit_of_file, user_get_repositories, -}; - -/// A backend implementation which attempts to resolve data from the instance's database. -#[derive(Clone)] -#[allow(unused)] -pub struct DatabaseBackend { - pub(self) our_instance: Instance, - pub(self) pool: PgPool, - pub(self) user_backend: Arc>, - pub(self) repository_backend: Arc>, - pub stack: Arc>, -} - -impl DatabaseBackend { - pub fn new( - instance: Instance, - user_backend: Arc>, - repository_backend: Arc>, - pool: PgPool, - stack: Arc>, - ) -> Self { - Self { - our_instance: instance, - user_backend, - repository_backend, - pool, - stack, - } - } - - pub fn into_substack(self) -> SubstackBuilder { - let mut builder = SubstackBuilder::::new(self.clone()); - - builder.object_metadata_provider(Box::new(self)); - - builder - .object::() - .object::() - .object::(); - - // Register value settings, which are settings that directly correspond to - // value types. - builder - .value_setting::() - .value_setting::() - .value_setting::() - .value_setting::() - .value_setting::() - .value_setting::(); - - // builder.value(repository_latest_commit); - - builder - .operation(user_get_repositories) - .operation(instance_registration_request) - .operation(instance_authentication_request) - .operation(instance_create_repository_request) - .operation(repository_info) - .operation(repository_get_statistics) - .operation(repository_file_from_id) - .operation(repository_file_from_path) - .operation(repository_last_commit_of_file) - .operation(repository_commit_by_id) - .operation(repository_diff) - .operation(repository_diff_patch) - .operation(repository_commit_before) - .operation(repository_get_branches); - - builder - } -} - -impl Debug for DatabaseBackend { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("DatabaseBackend").finish() - } -} - -#[async_trait::async_trait] -impl MetadataProvider for DatabaseBackend { - fn provides_for(&self, object: &dyn Any) -> bool { - object.is::() || object.is::() || object.is::() - } - - async fn write( - &self, - object: AnyObject, - _object_meta: &ObjectMeta, - setting: AnySetting, - setting_meta: &SettingMeta, - ) -> Result<(), anyhow::Error> { - if let Some(repository) = object.downcast_ref::() { - sqlx::query!("INSERT INTO repository_settings VALUES ($1, $2, $3) ON CONFLICT (repository, name) DO UPDATE SET value = $3", - repository.to_string(), setting_meta.name, serde_json::to_string(&(setting_meta.serialize)(setting).unwrap())?) - .execute(&self.pool).await?; - - Ok(()) - } else if let Some(user) = object.downcast_ref::() { - sqlx::query!("INSERT INTO user_settings VALUES ($1, $2, $3) ON CONFLICT (username, name) DO UPDATE SET value = $3", - user.username, setting_meta.name, serde_json::to_string(&(setting_meta.serialize)(setting).unwrap())?) - .execute(&self.pool).await?; - - Ok(()) - } else { - unreachable!() - } - } - - async fn read( - &self, - object: AnyObject, - _object_meta: &ObjectMeta, - setting_meta: &SettingMeta, - ) -> Result { - if let Some(repository) = object.downcast_ref::() { - let row = sqlx::query_as!( - RepositorySettingRow, - "SELECT * FROM repository_settings WHERE repository = $1 AND name = $2", - repository.to_string(), - setting_meta.name - ) - .fetch_one(&self.pool) - .await?; - - let setting = - serde_json::from_str(&row.value).context("deserializing setting from database")?; - - Ok(setting) - } else if let Some(user) = object.downcast_ref::() { - info!("User for {}", setting_meta.name); - let row = sqlx::query_as!( - UserSettingRow, - "SELECT * FROM user_settings WHERE username = $1 AND name = $2", - user.username, - setting_meta.name - ) - .fetch_one(&self.pool) - .await?; - - let setting = - serde_json::from_str(&row.value).context("deserializing setting from database")?; - - Ok(setting) - } else { - unreachable!() - } - } -} diff --git a/giterated-daemon/src/database_backend/updates.rs b/giterated-daemon/src/database_backend/updates.rs deleted file mode 100644 index bcde63e..0000000 --- a/giterated-daemon/src/database_backend/updates.rs +++ /dev/null @@ -1,76 +0,0 @@ -use futures_util::{future::BoxFuture, FutureExt}; -use giterated_models::{ - repository::{Description, Repository}, - settings::AnySetting, - user::User, - value::AnyValue, -}; -use giterated_stack::{AuthorizedUser, StackOperationState}; - -pub fn user_set_value( - _object: User, - _value_name: String, - _value: AnyValue, - _operation_state: &StackOperationState, -) -> BoxFuture<'static, Result<(), ()>> { - todo!() -} - -pub fn user_set_setting( - _object: User, - _value_name: String, - _value: AnySetting, - _operation_state: &StackOperationState, -) -> BoxFuture<'static, Result<(), ()>> { - todo!() -} - -pub fn repository_set_value( - _object: Repository, - _value_name: String, - _value: AnyValue, - _operation_state: &StackOperationState, -) -> BoxFuture<'static, Result<(), ()>> { - todo!() -} - -pub fn repository_set_setting( - _object: Repository, - _value_name: String, - _value: AnySetting, - _operation_state: &StackOperationState, -) -> BoxFuture<'static, Result<(), ()>> { - todo!() -} - -pub fn repository_set_description( - _object: Repository, - _description: Description, - _user: AuthorizedUser, -) -> BoxFuture<'static, Result<(), ()>> { - async { Ok(()) }.boxed() -} - -// pub fn repository_set_default_branch( -// object: Repository, -// default_branch: DefaultBranch, -// // Ensure user is authorized for this request -// _user: AuthorizedUser, -// backend: DatabaseBackend, -// stack: Arc, -// ) -> BoxFuture<'static, Result<(), ()>> { -// async move { -// stack.write_setting(&object, &default_branch).await.unwrap(); - -// let _set_value = ValueUpdate { -// object: object.to_string(), -// value_name: DefaultBranch::value_name().to_owned(), -// value: unsafe { AnyValue::from_raw(serde_json::to_value(default_branch).unwrap()) }, -// }; - -// // Submit value update back to the daemon -// // state.value_update(set_value); -// Ok(()) -// } -// .boxed() -// } diff --git a/giterated-plugin/Cargo.toml b/giterated-plugin/Cargo.toml new file mode 100644 index 0000000..709db83 --- /dev/null +++ b/giterated-plugin/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "giterated-plugin" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +dlopen2 = "0.6" +anyhow = "1" +thiserror = "1" +tracing = "0.1" +giterated-models = { path = "../giterated-models" } +semver = "*" +serde_json = "1.0" +async-trait = "0.1" diff --git a/giterated-plugin/giterated-plugin-sys/Cargo.toml b/giterated-plugin/giterated-plugin-sys/Cargo.toml new file mode 100644 index 0000000..3c4c1fa --- /dev/null +++ b/giterated-plugin/giterated-plugin-sys/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "giterated-plugin-sys" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +giterated-plugin = { path = "../." } +tracing = "0.1" +giterated-models = { path = "../../giterated-models" } \ No newline at end of file diff --git a/giterated-plugin/giterated-plugin-sys/src/lib.rs b/giterated-plugin/giterated-plugin-sys/src/lib.rs new file mode 100644 index 0000000..d077640 --- /dev/null +++ b/giterated-plugin/giterated-plugin-sys/src/lib.rs @@ -0,0 +1,270 @@ +use std::sync::OnceLock; + +use giterated_models::{ + object::GiteratedObject, operation::GiteratedOperation, settings::Setting, + value::GiteratedObjectValue, +}; +use giterated_plugin::{ + callback::{ + CallbackPtr, IntoPluginOperationHandler, IntoPluginSettingGetter, IntoPluginSettingSetter, + IntoPluginValueGetter, OperationHandlerCallback, SettingGetterCallback, + ValueGetterCallback, + }, + handle::PluginInitializationState, + new_stack::PluginState, + vtable::{ + InitializationVTable, IntoObjectVTable, IntoOperationVTable, IntoSettingVTable, + IntoValueVTable, ObjectVtable, OperationVTable, SettingVtable, ValueVTable, + }, + AnyObject, NewAnyValue, +}; +use tracing::trace_span; + +pub struct PluginStackBuilder<'init, S> { + init_state: *mut PluginInitializationState, + vtable: &'init InitializationVTable, + state: S, +} + +impl<'init, S> PluginStackBuilder<'init, S> { + pub fn new( + plugin_state: S, + state: *mut PluginInitializationState, + vtable: &'init InitializationVTable, + ) -> Self { + Self { + init_state: state, + vtable, + state: plugin_state, + } + } + + pub fn object(&mut self) -> &mut Self { + let _guard = trace_span!("register object").entered(); + + let func = self.vtable.register_object; + + unsafe { func(self.init_state, O::object_name(), ObjectVtable::new::()) }; + + self + } + + pub fn register_operation(&mut self) -> &mut Self + where + D: IntoOperationVTable + GiteratedOperation, + O: GiteratedObject, + { + let _guard = trace_span!("register operation").entered(); + + unsafe { + (self.vtable.register_operation)( + self.init_state, + O::object_name(), + D::operation_name(), + OperationVTable::new::(), + ) + } + + self + } + + pub fn object_setting(&mut self, get: HG, set: HS) -> &mut Self + where + O: GiteratedObject, + OS: IntoSettingVTable + Setting, + HG: IntoPluginSettingGetter, + HS: IntoPluginSettingSetter, + { + let _guard = trace_span!("register setting").entered(); + + unsafe { + (self.vtable.register_setting)( + self.init_state, + O::object_name(), + OS::name(), + SettingVtable::new::(), + ) + } + + self + } + + pub fn object_user_setting(&mut self) -> &mut Self + where + O: GiteratedObject, + OS: IntoSettingVTable + Setting, + { + let _guard = trace_span!("register setting").entered(); + + unsafe { + (self.vtable.register_setting)( + self.init_state, + O::object_name(), + OS::name(), + SettingVtable::new::(), + ) + } + + self + } + + pub fn value(&mut self, handler: T) -> &mut Self + where + O: GiteratedObject, + V: IntoValueVTable + GiteratedObjectValue, + T: IntoPluginValueGetter, + { + let _guard = trace_span!("register value").entered(); + + unsafe { + (self.vtable.register_value)( + self.init_state, + O::object_name(), + V::value_name(), + ValueVTable::new::(), + ) + } + + unsafe { + (self.vtable.value_getter)( + self.init_state, + O::object_name(), + V::value_name(), + ValueGetterCallback::new::(handler), + ) + } + + self + } + + pub fn operation< + DS, + DF, + A, + O: GiteratedObject + IntoObjectVTable, + D: IntoOperationVTable + GiteratedOperation, + T: IntoPluginOperationHandler, + >( + &mut self, + handler: T, + ) -> &mut Self { + let _guard = trace_span!("register operation handler").entered(); + + unsafe { + (self.vtable.operation_handler)( + self.init_state, + O::object_name(), + D::operation_name(), + OperationHandlerCallback::new::(handler), + ) + } + + // TODO: Yikes? + self.object::(); + + self.register_operation::(); + + self + } + + // pub fn value_getter(&mut self, handler: T) -> &mut Self + // where + // O: GiteratedObject + IntoObjectVTable, + // V: GiteratedObjectValue + IntoValueVTable, + // T: IntoPluginValueGetter, + // { + // let _guard = trace_span!("register value_getter handler").entered(); + + // unsafe { + // (self.vtable.value_getter)( + // self.init_state, + // O::object_name(), + // V::value_name(), + // ValueGetterCallback::new::(handler), + // ) + // } + + // // TODO: Yikes? + // self.object::(); + // self.value::(); + + // self + // } + + // pub fn setting_getter(&mut self, handler: T) -> &mut Self + // where + // O: GiteratedObject + IntoObjectVTable, + // OS: Setting + IntoSettingVTable, + // T: IntoPluginSettingGetter, + // { + // let _guard = trace_span!("register setting_getter handler").entered(); + + // unsafe { + // (self.vtable.setting_getter)( + // self.init_state, + // O::object_name(), + // OS::name(), + // SettingGetterCallback::new::(handler), + // ) + // } + + // self.object::(); + // // self.setting::(); + + // self + // } +} + +pub trait ValueSettingExt { + fn value_setting(&mut self, get: HG, set: HS) -> &mut Self + where + O: GiteratedObject + IntoObjectVTable + 'static, + VS: GiteratedObjectValue + IntoValueVTable + Setting + IntoSettingVTable, + HG: IntoPluginSettingGetter, + HS: IntoPluginSettingSetter; +} + +impl ValueSettingExt for PluginStackBuilder<'_, PS> { + fn value_setting(&mut self, get: HG, set: HS) -> &mut Self + where + O: GiteratedObject + IntoObjectVTable + 'static, + VS: GiteratedObjectValue + IntoValueVTable + Setting + IntoSettingVTable, + HG: IntoPluginSettingGetter, + HS: IntoPluginSettingSetter, + { + self + } +} + +pub trait ValueSettingGetter { + unsafe extern "C" fn get_value( + callback: CallbackPtr, + state: &PluginState, + object: AnyObject, + ) -> Result; + + fn callback_ptr(&self) -> CallbackPtr; +} + +impl ValueSettingGetter for HG +where + O: GiteratedObject, + VS: GiteratedObjectValue, + HG: IntoPluginSettingGetter, +{ + unsafe extern "C" fn get_value( + callback: CallbackPtr, + state: &PluginState, + object: AnyObject, + ) -> Result { + let result = HG::get_setting(callback, state, object)?; + + let setting = *result.transmute_owned::(); + + Ok(NewAnyValue::new(setting)) + } + + fn callback_ptr(&self) -> CallbackPtr { + self.callback_ptr() + } +} diff --git a/giterated-plugin/src/callback/mod.rs b/giterated-plugin/src/callback/mod.rs new file mode 100644 index 0000000..8ce311a --- /dev/null +++ b/giterated-plugin/src/callback/mod.rs @@ -0,0 +1,28 @@ +mod operation; +use std::sync::Arc; + +pub use operation::*; +mod value; +pub use value::*; +mod setting; +pub use setting::*; + +use crate::new_stack::{runtime_handler::RuntimeHandle, PluginState, Runtime}; + +/// A container for a callback pointer, used to provide an internal callback function or +/// state to a plugin when performing a callback. +#[derive(Clone, Copy)] +#[repr(C)] +pub struct CallbackPtr(*const ()); + +impl CallbackPtr { + pub unsafe fn from_raw(callback: *const ()) -> Self { + Self(callback) + } +} + +#[repr(C)] +pub struct RuntimeState { + pub runtime: RuntimeHandle, + pub operation_state: PluginState, +} diff --git a/giterated-plugin/src/callback/operation.rs b/giterated-plugin/src/callback/operation.rs new file mode 100644 index 0000000..db34f8a --- /dev/null +++ b/giterated-plugin/src/callback/operation.rs @@ -0,0 +1,176 @@ +use giterated_models::error::OperationError; + +use crate::{ + new_stack::{runtime_handler::RuntimeHandle, PluginState, Runtime, State}, + AnyObject, AnyOperation, +}; + +use std::{any::type_name, fmt::Debug, future::Future, sync::Arc}; + +use super::{CallbackPtr, RuntimeState}; + +#[derive(Clone, Copy)] +pub struct OperationHandlerCallback { + pub callback_ptr: CallbackPtr, + pub func: unsafe extern "C" fn( + CallbackPtr, + &RuntimeState, + &PluginState, + object: AnyObject, + operation: AnyOperation, + ), +} + +impl OperationHandlerCallback { + pub fn new>( + handler: T, + ) -> Self { + OperationHandlerCallback { + func: T::handle, + callback_ptr: T::callback_ptr(&handler), + } + } +} + +pub trait IntoPluginOperationHandler { + unsafe extern "C" fn handle( + callback_ptr: CallbackPtr, + runtime_state: &RuntimeState, + state: &PluginState, + object: AnyObject, + operation: AnyOperation, + ); + fn callback_ptr(&self) -> CallbackPtr; +} + +impl IntoPluginOperationHandler for F +where + Fut: Future>>, + F: Fn(S, O, D) -> Fut, + S: Clone + Debug, + O: Debug, + D: Debug, +{ + unsafe extern "C" fn handle( + callback: CallbackPtr, + runtime_state: &RuntimeState, + state: &PluginState, + mut object: AnyObject, + mut operation: AnyOperation, + ) { + let _guard = trace_span!( + "operation handler", + object = type_name::(), + operation = type_name::() + ) + .entered(); + let state = unsafe { state.transmute_ref::() }; + + // Since this is Rust code, we know that the AnyObject and AnyOperation are just boxes + let object = unsafe { object.transmute_owned::() }; + let operation = unsafe { operation.transmute_owned::() }; + + // Cast the callback ptr to ourselves + let callback: *const F = std::mem::transmute(callback.0); + let callback = callback.as_ref().unwrap(); + + // callback(state.clone(), *object, *operation) + + todo!() + } + + fn callback_ptr(&self) -> CallbackPtr { + unsafe { CallbackPtr::from_raw(self as *const _ as *const ()) } + } +} + +impl IntoPluginOperationHandler for F +where + Fut: Future>>, + F: Fn(S, O, D, A1) -> Fut, + S: Clone + Debug, + O: Debug, + D: Debug, +{ + unsafe extern "C" fn handle( + callback_ptr: CallbackPtr, + runtime_state: &RuntimeState, + state: &PluginState, + object: AnyObject, + operation: AnyOperation, + ) { + todo!() + } + + fn callback_ptr(&self) -> CallbackPtr { + todo!() + } +} + +impl IntoPluginOperationHandler for F +where + Fut: Future>>, + F: Fn(S, O, D, A1, A2) -> Fut, + S: Clone + Debug, + O: Debug, + D: Debug, +{ + unsafe extern "C" fn handle( + callback_ptr: CallbackPtr, + runtime_state: &RuntimeState, + state: &PluginState, + object: AnyObject, + operation: AnyOperation, + ) { + todo!() + } + + fn callback_ptr(&self) -> CallbackPtr { + todo!() + } +} + +pub trait FromOperationState: Sized { + fn from_operation_state( + state: &S, + runtime_state: &RuntimeState, + object: &O, + operation: &D, + ) -> Result>; +} + +impl FromOperationState for RuntimeHandle { + fn from_operation_state( + state: &S, + runtime_state: &RuntimeState, + object: &O, + operation: &D, + ) -> Result> { + Ok(runtime_state.runtime.clone()) + } +} + +impl FromOperationState for Option +where + T: FromOperationState, +{ + fn from_operation_state( + state: &S, + runtime_state: &RuntimeState, + object: &O, + operation: &D, + ) -> Result> { + Ok(T::from_operation_state(state, runtime_state, object, operation).ok()) + } +} + +impl FromOperationState for State { + fn from_operation_state( + state: &S, + runtime_state: &RuntimeState, + object: &O, + operation: &D, + ) -> Result> { + Ok(unsafe { State(runtime_state.operation_state.transmute_ref::().clone()) }) + } +} diff --git a/giterated-plugin/src/callback/setting.rs b/giterated-plugin/src/callback/setting.rs new file mode 100644 index 0000000..21e368d --- /dev/null +++ b/giterated-plugin/src/callback/setting.rs @@ -0,0 +1,168 @@ +use std::future::Future; + +use giterated_models::{ + error::OperationError, + object::GiteratedObject, + settings::{AnySetting, Setting}, +}; + +use crate::{new_stack::PluginState, AnyObject, NewAnySetting}; + +use super::CallbackPtr; + +#[derive(Clone, Copy)] +pub struct SettingGetterCallback { + pub callback_ptr: CallbackPtr, + pub func: unsafe extern "C" fn( + CallbackPtr, + &PluginState, + object: AnyObject, + ) -> Result, +} + +impl SettingGetterCallback { + pub fn new>(callback: T) -> Self { + Self { + func: T::get_setting, + callback_ptr: callback.callback_ptr(), + } + } +} + +pub trait IntoPluginSettingGetter { + unsafe extern "C" fn get_setting( + callback_ptr: CallbackPtr, + state: &PluginState, + object: AnyObject, + ) -> Result; + + fn callback_ptr(&self) -> CallbackPtr { + unsafe { CallbackPtr::from_raw(self as *const _ as *const ()) } + } +} + +impl IntoPluginSettingGetter for F +where + Fut: Future>>, + S: Clone, + O: GiteratedObject, + OS: Setting, + F: Fn(S, O) -> Fut, +{ + unsafe extern "C" fn get_setting( + callback: CallbackPtr, + state: &PluginState, + mut object: AnyObject, + ) -> Result { + let _guard = trace_span!( + "get_setting handler", + object = O::object_name(), + setting = OS::name() + ) + .entered(); + let state = unsafe { state.transmute_ref::() }; + + let object = unsafe { object.transmute_owned::() }; + + // Cast the callback ptr to ourselves + let callback: *const F = std::mem::transmute(callback.0); + let callback = callback.as_ref().unwrap(); + + // let result = callback(state.clone(), *object); + + // match result { + // Ok(setting) => Ok(NewAnySetting::new(setting)), + // Err(_) => todo!(), + // } + + todo!() + } +} + +pub trait IntoPluginSettingSetter { + unsafe extern "C" fn set_setting( + callback_ptr: CallbackPtr, + state: &PluginState, + object: AnyObject, + setting: AnySetting, + ) -> Result<(), ()>; + + fn callback_ptr(&self) -> CallbackPtr { + unsafe { CallbackPtr::from_raw(self as *const _ as *const ()) } + } +} + +impl IntoPluginSettingSetter for F +where + Fut: Future>>, + S: Clone, + O: GiteratedObject, + OS: Setting, + F: Fn(S, O, OS) -> Fut, +{ + unsafe extern "C" fn set_setting( + callback: CallbackPtr, + state: &PluginState, + mut object: AnyObject, + setting: AnySetting, + ) -> Result<(), ()> { + let _guard = trace_span!( + "get_setting handler", + object = O::object_name(), + setting = OS::name() + ) + .entered(); + let state = unsafe { state.transmute_ref::() }; + + let object = unsafe { object.transmute_owned::() }; + + // Cast the callback ptr to ourselves + let callback: *const F = std::mem::transmute(callback.0); + let callback = callback.as_ref().unwrap(); + + // let result = callback(state.clone(), *object); + + // match result { + // Ok(setting) => Ok(NewAnySetting::new(setting)), + // Err(_) => todo!(), + // } + todo!() + } +} + +pub struct SettingChangeCallback { + func: unsafe extern "C" fn( + &PluginState, + object: AnyObject, + setting_name: &str, + new_setting: NewAnySetting, + ), +} + +pub trait IntoSettingChangeCallback { + unsafe extern "C" fn setting_changed( + state: &PluginState, + object: AnyObject, + setting_name: &str, + new_setting: NewAnySetting, + ); +} + +impl IntoSettingChangeCallback for F { + unsafe extern "C" fn setting_changed( + state: &PluginState, + object: AnyObject, + setting_name: &str, + new_setting: NewAnySetting, + ) { + todo!() + } +} + +impl SettingChangeCallback { + pub fn new>() -> Self { + Self { + func: T::setting_changed, + } + } +} diff --git a/giterated-plugin/src/callback/value.rs b/giterated-plugin/src/callback/value.rs new file mode 100644 index 0000000..654ed3d --- /dev/null +++ b/giterated-plugin/src/callback/value.rs @@ -0,0 +1,120 @@ +use std::future::Future; + +use giterated_models::{ + error::OperationError, object::GiteratedObject, settings::Setting, value::GiteratedObjectValue, +}; + +use crate::{ + new_stack::PluginState, + vtable::{AnyObject, NewAnyValue}, +}; + +use super::CallbackPtr; + +#[derive(Copy, Clone)] +pub struct ValueGetterCallback { + pub callback_ptr: CallbackPtr, + pub func: unsafe extern "C" fn( + CallbackPtr, + &PluginState, + object: AnyObject, + ) -> Result, +} + +impl ValueGetterCallback { + pub fn new>(handler: T) -> Self { + Self { + func: T::get_value, + callback_ptr: handler.callback_ptr(), + } + } +} + +pub trait IntoPluginValueGetter { + unsafe extern "C" fn get_value( + callback: CallbackPtr, + state: &PluginState, + object: AnyObject, + ) -> Result; + + fn callback_ptr(&self) -> CallbackPtr; +} + +impl IntoPluginValueGetter for F +where + Fut: Future>>, + S: Clone, + O: GiteratedObject, + V: GiteratedObjectValue, + F: Fn(S, O) -> Fut, +{ + unsafe extern "C" fn get_value( + callback: CallbackPtr, + state: &PluginState, + mut object: AnyObject, + ) -> Result { + let _guard = trace_span!( + "get_value handler", + object = O::object_name(), + value = V::value_name() + ) + .entered(); + let state = unsafe { state.transmute_ref::() }; + + let object = unsafe { object.transmute_owned::() }; + + // Cast the callback ptr to ourselves + let callback: *const F = std::mem::transmute(callback.0); + let callback = callback.as_ref().unwrap(); + + let result = callback(state.clone(), *object); + + // match result { + // Ok(value) => Ok(NewAnyValue::new(value)), + // Err(_) => todo!(), + // } + + todo!() + } + + fn callback_ptr(&self) -> CallbackPtr { + unsafe { CallbackPtr::from_raw(self as *const _ as *const ()) } + } +} + +pub struct ValueChangeCallback { + func: unsafe extern "C" fn( + &PluginState, + object: AnyObject, + value_name: &str, + new_value: NewAnyValue, + ), +} + +pub trait IntoValueChangeCallback { + unsafe extern "C" fn value_changed( + state: &PluginState, + object: AnyObject, + value_name: &str, + new_value: NewAnyValue, + ); +} + +impl IntoValueChangeCallback for F { + unsafe extern "C" fn value_changed( + state: &PluginState, + object: AnyObject, + value_name: &str, + new_value: NewAnyValue, + ) { + todo!() + } +} + +impl ValueChangeCallback { + pub fn new>() -> Self { + Self { + func: T::value_changed, + } + } +} diff --git a/giterated-plugin/src/handle.rs b/giterated-plugin/src/handle.rs new file mode 100644 index 0000000..f262422 --- /dev/null +++ b/giterated-plugin/src/handle.rs @@ -0,0 +1,256 @@ +use std::{collections::HashMap, marker::PhantomData, path::Path, sync::Arc}; + +use anyhow::Error; +use dlopen2::wrapper::Container; +use semver::Version; +use tracing::{debug, trace}; + +use crate::{ + callback::{OperationHandlerCallback, SettingGetterCallback, ValueGetterCallback}, + new_stack::{ + ObjectOperationPair, ObjectSettingPair, ObjectValuePair, PluginMeta, PluginState, + RuntimeHandlers, TypeMetadata, + }, + vtable::{InitializationVTable, ObjectVtable, OperationVTable, SettingVtable, ValueVTable}, + GiteratedPluginApi, +}; + +#[derive(Clone)] +pub struct PluginHandle { + pub meta: PluginMeta, + pub raw: Arc>, + pub initialization: Arc, + pub state: PluginState, +} + +unsafe impl Send for PluginHandle {} +unsafe impl Sync for PluginHandle {} + +impl PluginHandle { + pub fn from_dylib(path: &str) -> Result { + let mut handle = unsafe { Container::load(path) }?; + + // Initialize the raw handle + let init_state = Self::initialize_raw_handle(&mut handle)?; + + let metadata = Self::get_meta(&mut handle)?; + + let initalization = Self::initialize_registration(&mut handle)?; + + trace!( + "Loaded plugin {} (Version: {})", + metadata.name, + metadata.version + ); + + Ok(Self { + raw: Arc::new(handle), + meta: metadata, + initialization: Arc::new(initalization), + state: init_state, + }) + } + + /// Builds the Plugin's Substack. + /// + /// Builds the Plugin into a substack, which can then be provided to the Giterated Runtime. + pub fn build_substack(&mut self) -> Result<(), Error> { + todo!() + } + + fn get_meta(handle: &mut Container) -> Result { + let meta = unsafe { handle.plugin_meta() }; + + let name = unsafe { std::slice::from_raw_parts(meta.name, meta.name_len) }; + let version = unsafe { std::slice::from_raw_parts(meta.version, meta.version_len) }; + + let name = std::str::from_utf8(name).unwrap(); + let version = std::str::from_utf8(version).unwrap(); + + Ok(PluginMeta { + name: String::from(name), + version: Version::parse(version).unwrap(), + }) + } + + pub fn initialize_registration( + handle: &mut Container, + ) -> Result { + debug!("Initializing plugin registration..."); + let mut builder = PluginInitializationTable::default(); + + // SAFETY: The lifetime of the returned type is only valid as long + // as the builder that returned it lives + let func_table = unsafe { builder.func_table() }; + + let state = Box::new(PluginInitializationState::new()); + + unsafe { handle.load_initialization_vtable(&func_table) }; + let state = unsafe { handle.initialize_registration(Box::into_raw(state)) }; + + debug!("Plugin handle initialized!"); + Ok(unsafe { *Box::from_raw(state) }) + } + + fn initialize_raw_handle( + handle: &mut Container, + ) -> Result { + debug!("Initializing plugin handle..."); + + let state = unsafe { handle.initialize() }; + + debug!("Plugin handle initialized!"); + + Ok(state) + } +} + +#[derive(Debug, thiserror::Error)] +pub enum CreationError { + #[error("an error occured opening the library {0}")] + LoadingLibrary(#[from] dlopen2::Error), +} + +pub struct PluginSubstackBuilder {} + +#[derive(Default)] +pub struct PluginInitializationState { + pub type_metadata: TypeMetadata, + pub operation_handlers: HashMap, OperationHandlerCallback>, + pub value_getters: HashMap, ValueGetterCallback>, + pub setting_getters: HashMap, SettingGetterCallback>, +} + +impl PluginInitializationState { + pub fn new() -> Self { + Self::default() + } +} + +#[derive(Default)] +pub struct PluginInitializationTable<'a> { + _marker: PhantomData<&'a ()>, +} + +impl<'a> PluginInitializationTable<'a> { + pub unsafe fn func_table(&mut self) -> InitializationVTable { + InitializationVTable { + register_object, + register_operation, + register_setting, + register_value, + operation_handler, + value_getter, + setting_getter, + } + } +} + +unsafe extern "C" fn register_object( + state: *mut PluginInitializationState, + object_kind: &'static str, + vtable: ObjectVtable, +) { + let mut state = Box::from_raw(state); + + state.type_metadata.register_object(object_kind, vtable); + + Box::into_raw(state); +} + +unsafe extern "C" fn register_operation( + state: *mut PluginInitializationState, + object_kind: &'static str, + operation_name: &'static str, + vtable: OperationVTable, +) { + let mut state = Box::from_raw(state); + + state + .type_metadata + .register_operation(object_kind, operation_name, vtable); + + Box::into_raw(state); +} + +unsafe extern "C" fn register_setting( + state: *mut PluginInitializationState, + object_kind: &'static str, + setting_name: &'static str, + vtable: SettingVtable, +) { + let mut state = Box::from_raw(state); + + state + .type_metadata + .register_setting(object_kind, setting_name, vtable); + + Box::into_raw(state); +} + +unsafe extern "C" fn register_value( + state: *mut PluginInitializationState, + object_kind: &'static str, + value_name: &'static str, + vtable: ValueVTable, +) { + let mut state = Box::from_raw(state); + + state + .type_metadata + .register_value(object_kind, value_name, vtable); + + Box::into_raw(state); +} + +unsafe extern "C" fn operation_handler( + state: *mut PluginInitializationState, + object_kind: &'static str, + operation_name: &'static str, + handler: OperationHandlerCallback, +) { + let mut state = Box::from_raw(state); + + trace!("Operation handler for {}::{}", object_kind, operation_name); + + state.operation_handlers.insert( + ObjectOperationPair::new(object_kind, operation_name), + handler, + ); + + Box::into_raw(state); +} + +unsafe extern "C" fn value_getter( + state: *mut PluginInitializationState, + object_kind: &'static str, + value_name: &'static str, + handler: ValueGetterCallback, +) { + let mut state = Box::from_raw(state); + + trace!("Value getter for {}::{}", object_kind, value_name); + + state + .value_getters + .insert(ObjectValuePair::new(object_kind, value_name), handler); + + Box::into_raw(state); +} + +unsafe extern "C" fn setting_getter( + state: *mut PluginInitializationState, + object_kind: &'static str, + setting_name: &'static str, + handler: SettingGetterCallback, +) { + let mut state = Box::from_raw(state); + + trace!("Setting getter for {}::{}", object_kind, setting_name); + + state + .setting_getters + .insert(ObjectSettingPair::new(object_kind, setting_name), handler); + + Box::into_raw(state); +} diff --git a/giterated-plugin/src/lib.rs b/giterated-plugin/src/lib.rs new file mode 100644 index 0000000..fdc606a --- /dev/null +++ b/giterated-plugin/src/lib.rs @@ -0,0 +1,59 @@ +pub mod callback; +pub mod handle; +pub mod new_stack; +pub mod vtable; + +#[macro_use] +extern crate tracing; + +use std::{any::Any, fmt::Debug, mem::transmute, ptr::null_mut, sync::Arc}; + +use callback::{OperationHandlerCallback, SettingGetterCallback, ValueGetterCallback}; +use dlopen2::wrapper::WrapperApi; +use giterated_models::{ + object::GiteratedObject, operation::GiteratedOperation, settings::Setting, + value::GiteratedObjectValue, +}; +use handle::PluginInitializationState; +use new_stack::{FFIPluginMeta, PluginState}; + +pub use vtable::{AnyFailure, AnyObject, AnyOperation, AnySuccess, NewAnySetting, NewAnyValue}; +use vtable::{HostVTable, InitializationVTable}; + +#[derive(WrapperApi)] +pub struct GiteratedPluginApi { + plugin_meta: unsafe extern "C" fn() -> FFIPluginMeta, + load_host_vtable: unsafe extern "C" fn(vtable: &HostVTable), + load_initialization_vtable: unsafe extern "C" fn(vtable: &InitializationVTable), + initialize: unsafe extern "C" fn() -> PluginState, + initialize_registration: unsafe extern "C" fn( + init_state: *mut PluginInitializationState, + ) -> *mut PluginInitializationState, +} + +#[repr(C)] +pub struct FFIBox(*mut T); + +impl FFIBox { + pub fn from_box(src: Box) -> Self { + Self(Box::into_raw(src)) + } + + pub fn untyped(self) -> FFIBox<()> { + FFIBox(self.0 as *mut ()) + } +} + +impl AsRef for FFIBox { + fn as_ref(&self) -> &T { + todo!() + } +} + +impl std::ops::Deref for FFIBox { + type Target = T; + + fn deref(&self) -> &Self::Target { + unsafe { self.0.as_ref() }.unwrap() + } +} diff --git a/giterated-plugin/src/new_stack/mod.rs b/giterated-plugin/src/new_stack/mod.rs new file mode 100644 index 0000000..26b8d98 --- /dev/null +++ b/giterated-plugin/src/new_stack/mod.rs @@ -0,0 +1,492 @@ +pub mod operation_walker; +pub mod runtime_handler; + +use std::{ + any::type_name, collections::HashMap, fmt::Debug, marker::PhantomData, mem::transmute, + ptr::null_mut, sync::Arc, +}; + +use giterated_models::{ + error::OperationError, instance::Instance, object::GiteratedObject, + operation::GiteratedOperation, +}; +use semver::Version; +use tracing::{debug, debug_span, field::DebugValue, span, trace, trace_span, warn, Level}; + +use crate::{ + callback::{ + OperationHandlerCallback, SettingChangeCallback, SettingGetterCallback, + ValueChangeCallback, ValueGetterCallback, + }, + handle::{PluginHandle, PluginInitializationState}, + vtable::{ObjectVtable, OperationVTable, SettingVtable, ValueVTable}, +}; + +use self::operation_walker::OperationHandlerRules; + +pub struct State(pub S); + +impl std::ops::Deref for State { + type Target = S; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +pub struct OperationState { + pub our_instance: Instance, +} + +#[derive(Default)] +pub struct TypeMetadata { + pub objects: HashMap<&'static str, ObjectVtable>, + pub operations: HashMap, OperationVTable>, + pub settings: HashMap, SettingVtable>, + pub values: HashMap, ValueVTable>, +} + +impl TypeMetadata { + pub fn register_object(&mut self, object_kind: &'static str, vtable: ObjectVtable) { + trace!("Registering type metadata for {}", object_kind); + + self.objects.insert(object_kind, vtable); + } + + pub fn register_operation( + &mut self, + object_kind: &'static str, + operation_name: &'static str, + vtable: OperationVTable, + ) { + trace!( + "Registering operation metadata for {}::{}", + object_kind, + operation_name + ); + + self.operations.insert( + ObjectOperationPair { + object_kind, + operation_name, + }, + vtable, + ); + } + + pub fn register_setting( + &mut self, + object_kind: &'static str, + setting_name: &'static str, + vtable: SettingVtable, + ) { + trace!("Registering setting {}::{}", object_kind, setting_name); + + self.settings.insert( + ObjectSettingPair { + object_kind, + setting_name, + }, + vtable, + ); + } + + pub fn register_value( + &mut self, + object_kind: &'static str, + value_name: &'static str, + vtable: ValueVTable, + ) { + trace!("Registering value {}::{}", object_kind, value_name); + + self.values.insert( + ObjectValuePair { + object_kind, + value_name, + }, + vtable, + ); + } +} + +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] +pub struct ObjectOperationPair<'s> { + pub object_kind: &'s str, + pub operation_name: &'s str, +} + +impl<'s> ObjectOperationPair<'s> { + pub fn new(object_kind: &'s str, operation_name: &'s str) -> Self { + Self { + object_kind, + operation_name, + } + } +} + +#[derive(Clone, Copy, Hash, PartialEq, Eq, Debug)] +pub struct ObjectSettingPair<'s> { + pub object_kind: &'s str, + pub setting_name: &'s str, +} + +impl<'s> ObjectSettingPair<'s> { + pub fn new(object_kind: &'s str, setting_name: &'s str) -> Self { + Self { + object_kind, + setting_name, + } + } +} + +#[derive(Clone, Copy, Hash, PartialEq, Eq, Debug)] +pub struct ObjectValuePair<'s> { + pub object_kind: &'s str, + pub value_name: &'s str, +} + +impl<'s> ObjectValuePair<'s> { + pub fn new(object_kind: &'s str, value_name: &'s str) -> Self { + Self { + object_kind, + value_name, + } + } +} + +#[derive(Clone, Copy)] +#[repr(C)] +pub struct PluginState { + pub inner: *mut (), +} + +impl PluginState { + pub unsafe fn transmute_owned(&mut self) -> Box { + Box::from_raw(self.inner as *mut T) + } + + pub unsafe fn transmute_ref(&self) -> &T { + let ptr: *const T = transmute(self.inner); + + ptr.as_ref().unwrap() + } +} + +impl PluginState { + pub fn null() -> Self { + Self { inner: null_mut() } + } +} + +pub struct Runtime { + plugins: Vec<(PluginMeta, PluginHandle)>, + handlers: RuntimeHandlers, + _marker: PhantomData, +} + +impl Runtime { + pub fn new() -> Self { + Self { + plugins: vec![], + handlers: RuntimeHandlers::default(), + _marker: PhantomData, + } + } + + pub fn insert_plugin(&mut self, mut plugin: PluginHandle) { + let _guard = debug_span!("inserting plugin", meta = debug(&plugin.meta)).entered(); + + for (pair, callback) in &plugin.initialization.operation_handlers { + let _guard = + trace_span!("processing operation handler callbacks", pair = debug(pair)).entered(); + + if self + .handlers + .operation_handlers + .insert(*pair, (RuntimeDomain::from_plugin(&plugin), *callback)) + .is_some() + { + warn!("Warning! Insertion of handler for overwrote a previous handler.") + } + + trace!("Insertion of operation handler successful") + } + + for (pair, callback) in &plugin.initialization.value_getters { + let _guard = + trace_span!("processing value getter callbacks", pair = debug(pair)).entered(); + + if self + .handlers + .value_getters + .insert(*pair, (RuntimeDomain::from_plugin(&plugin), *callback)) + .is_some() + { + warn!("Warning! Insertion of handler for overwrote a previous handler.") + } + + trace!("Insertion of operation handler successful") + } + + for (pair, callback) in &plugin.initialization.setting_getters { + let _guard = + trace_span!("processing setting getter callbacks", pair = debug(pair)).entered(); + + if self + .handlers + .setting_getters + .insert(*pair, (RuntimeDomain::from_plugin(&plugin), *callback)) + .is_some() + { + warn!("Warning! Insertion of setting handler for overwrote a previous handler.") + } + + trace!("Insertion of setting handler successful") + } + } + + pub fn handle( + &self, + object_kind: &str, + operation_name: &str, + object: &str, + operation_payload: &[u8], + ) -> Result<(), OperationError<()>> { + // let rules = self.handlers.handle_operation(object_kind, operation_name); + + // rules.handle(object, operation_payload) + + todo!() + } + + pub fn handle_typed>( + &self, + object: O, + operation: D, + ) -> Result> { + todo!() + } +} + +#[derive(Default)] +pub struct RuntimeHandlers { + operation_handlers: + HashMap, (RuntimeDomain, OperationHandlerCallback)>, + value_getters: HashMap, (RuntimeDomain, ValueGetterCallback)>, + setting_getters: HashMap, (RuntimeDomain, SettingGetterCallback)>, + value_change: HashMap, (RuntimeDomain, ValueChangeCallback)>, + setting_change: HashMap, (RuntimeDomain, SettingChangeCallback)>, +} + +unsafe impl Send for RuntimeHandlers {} +unsafe impl Sync for RuntimeHandlers {} + +impl RuntimeHandlers { + pub fn operation_handler( + &mut self, + pair: ObjectOperationPair<'static>, + handler: OperationHandlerCallback, + domain: RuntimeDomain, + ) { + trace!( + "Inserting operation handler for {}::{}", + pair.object_kind, + pair.operation_name + ); + + // There can only be one handler per operation (at least for now?), send a warning if + // a newly registered handler overwrites the previous handler. + if self + .operation_handlers + .insert(pair, (domain, handler)) + .is_some() + { + debug!("Warning! A newly inserted operation handler for {}::{} overwrites a previous handler.", pair.object_kind, pair.operation_name); + } + } + + pub fn value_getter( + &mut self, + pair: ObjectValuePair<'static>, + handler: ValueGetterCallback, + domain: RuntimeDomain, + ) { + trace!( + "Inserting value getter for {}::{}", + pair.object_kind, + pair.value_name + ); + + if self.value_getters.insert(pair, (domain, handler)).is_some() { + debug!( + "Warning! A newly inserted value getter for {}::{} overwrites a previous handler.", + pair.object_kind, pair.value_name + ); + } + } + + pub fn setting_getter( + &mut self, + pair: ObjectSettingPair<'static>, + handler: SettingGetterCallback, + domain: RuntimeDomain, + ) { + trace!( + "Inserting setting getter for {}::{}", + pair.object_kind, + pair.setting_name + ); + + if self + .setting_getters + .insert(pair, (domain, handler)) + .is_some() + { + debug!("Warning! A newly inserted setting getter for {}::{} overwrites a previous handler.", pair.object_kind, pair.setting_name); + } + } + + pub fn value_change( + &mut self, + pair: ObjectValuePair<'static>, + handler: ValueChangeCallback, + domain: RuntimeDomain, + ) { + trace!( + "Inserting value change handler for {}::{}", + pair.object_kind, + pair.value_name + ); + + if self.value_change.insert(pair, (domain, handler)).is_some() { + debug!("Warning! A newly inserted value change handler for {}::{} overwrites a previous handler.", pair.object_kind, pair.value_name); + panic!("Not intended"); + } + } + + pub fn setting_change( + &mut self, + pair: ObjectSettingPair<'static>, + handler: SettingChangeCallback, + domain: RuntimeDomain, + ) { + trace!( + "Inserting setting change handler for {}::{}", + pair.object_kind, + pair.setting_name + ); + + if self + .setting_change + .insert(pair, (domain, handler)) + .is_some() + { + debug!("Warning! A newly inserted setting change handler for {}::{} overwrites a previous handler.", pair.object_kind, pair.setting_name); + panic!("Not intended"); + } + } +} + +impl RuntimeHandlers { + pub fn handle_operation<'o>( + &'o self, + object_kind: &'o str, + operation_name: &'o str, + ) -> OperationHandlerRules<'o> { + OperationHandlerRules::new(object_kind, operation_name, self) + } +} + +pub struct RuntimeDomain { + plugin: PluginHandle, +} + +impl RuntimeDomain { + pub fn from_plugin(plugin: &PluginHandle) -> Self { + Self { + plugin: plugin.clone(), + } + } + + pub fn object_vtable(&self, object_kind: &str) -> Option { + self.plugin + .initialization + .type_metadata + .objects + .get(object_kind) + .copied() + } + + pub fn operation_vtable( + &self, + object_kind: &str, + operation_name: &str, + ) -> Option { + self.plugin + .initialization + .type_metadata + .operations + .get(&ObjectOperationPair::new(object_kind, operation_name)) + .copied() + } + + pub fn setting_vtable(&self, object_kind: &str, setting_name: &str) -> Option { + self.plugin + .initialization + .type_metadata + .settings + .get(&ObjectSettingPair::new(object_kind, setting_name)) + .copied() + } + + pub fn value_vtable(&self, object_kind: &str, value_name: &str) -> Option { + self.plugin + .initialization + .type_metadata + .values + .get(&ObjectValuePair::new(object_kind, value_name)) + .copied() + } +} + +#[derive(Clone, Debug)] +pub struct PluginMeta { + pub name: String, + pub version: Version, +} + +#[repr(C)] +pub struct FFIPluginMeta { + pub name: *const u8, + pub name_len: usize, + pub version: *const u8, + pub version_len: usize, +} + +pub struct RuntimePlugin { + handle: PluginHandle, + type_metadata: Arc, +} + +impl RuntimePlugin { + pub fn plugin_meta(&self) -> PluginMeta { + let meta = unsafe { self.handle.raw.plugin_meta() }; + + let name = unsafe { std::slice::from_raw_parts(meta.name, meta.name_len) }; + let version = unsafe { std::slice::from_raw_parts(meta.version, meta.version_len) }; + + let name = std::str::from_utf8(name).unwrap(); + let version = std::str::from_utf8(version).unwrap(); + + PluginMeta { + name: String::from(name), + version: Version::parse(version).unwrap(), + } + } +} + +pub enum HandlerError { + Failure(()), + Internal(()), + Unhandled, +} diff --git a/giterated-plugin/src/new_stack/operation_walker.rs b/giterated-plugin/src/new_stack/operation_walker.rs new file mode 100644 index 0000000..bb9f6f6 --- /dev/null +++ b/giterated-plugin/src/new_stack/operation_walker.rs @@ -0,0 +1,232 @@ +use crate::callback::RuntimeState; +use giterated_models::{operation::GiteratedOperation, settings::GetSetting, value::GetValue}; +use tracing::{debug_span, trace, trace_span}; + +use crate::new_stack::{ObjectOperationPair, PluginState}; + +use super::{HandlerError, ObjectSettingPair, ObjectValuePair, RuntimeHandlers}; + +/// A wrapper for operation handling that enforces handling rules. +/// +/// # Handler Resolution +/// In order, handler resolution will be attempted as follows: +/// +/// | Index | object_kind | operation_kind | Special Case? | +/// |-------|-------------|-----------------|---------------| +/// | 1 | `any` | `typed` | No | +/// | 2 | `typed` | `any` | No | +/// | 3 | `any` | `any` | No | +/// | 4 | `any` | `GetValue` | ⚠️ Yes ⚠️ | +/// | 5 | `any` | `GetSetting` | ⚠️ Yes ⚠️ | +/// | 6 | `any` | `SetSetting` | ⚠️ Yes ⚠️ | +/// | 7 | `any` | `ObjectRequest` | ⚠️ Yes ⚠️ | +/// | 8 | `typed` | `typed` | No | +pub struct OperationHandlerRules<'a> { + object_kind: &'a str, + operation_name: &'a str, + handlers: &'a RuntimeHandlers, +} + +impl<'o> OperationHandlerRules<'o> { + pub fn new( + object_kind: &'o str, + operation_name: &'o str, + handlers: &'o RuntimeHandlers, + ) -> Self { + Self { + object_kind, + operation_name, + handlers, + } + } + + pub fn handle( + &self, + runtime_state: &RuntimeState, + object: &str, + operation_payload: &[u8], + ) -> Result<(), HandlerError> { + // object_kind: `any` + // operation_kind: `typed` + if let Some(handler) = self + .handlers + .operation_handlers + .get(&ObjectOperationPair::new("any", self.operation_name)) + { + todo!() + } + + // object_kind: `typed` + // operation_kind: `any` + if let Some(handler) = self + .handlers + .operation_handlers + .get(&ObjectOperationPair::new(self.object_kind, "any")) + {} + + // object_kind: `any` + // operation_kind: `any` + if let Some(handler) = self + .handlers + .operation_handlers + .get(&ObjectOperationPair::new("any", "any")) + {} + + // ⚠️ Special Case ⚠️ + // object_kind: `any` + // operation_kind: `GetValue` + if self.operation_name == "get_value" { + let operation: GetValue = serde_json::from_slice(operation_payload).unwrap(); + let _guard = trace_span!( + "get_value handler resolving", + object = self.object_kind, + value = operation.value_name + ) + .entered(); + + if let Some((domain, callback)) = self.handlers.value_getters.get( + &ObjectValuePair::new(self.object_kind, &operation.value_name), + ) { + trace_span!( + "get_value handler.", + object = self.object_kind, + value_name = operation.value_name + ); + + let object_vtable = domain + .object_vtable(self.object_kind) + .ok_or_else(|| HandlerError::Unhandled)?; + trace!("Resolved object vtable for {}", self.object_kind); + + let value_vtable = domain + .value_vtable(self.object_kind, &operation.value_name) + .ok_or_else(|| HandlerError::Unhandled)?; + trace!( + "Resolved value vtable for {}::{}", + self.object_kind, + operation.value_name + ); + + let object = unsafe { (object_vtable.from_str)(object) } + .map_err(|_| HandlerError::Internal(()))?; + + let _guard = debug_span!("get_value handler"); + + let result = + unsafe { (callback.func)(callback.callback_ptr, &domain.plugin.state, object) }; + + // Todo deser + + return Ok(()); + } else { + trace!("Failed to resolve handler."); + } + } + + // ⚠️ Special Case ⚠️ + // object_kind: `any` + // operation_kind: `GetSetting` + if self.operation_name == "get_setting" { + let operation: GetSetting = serde_json::from_slice(operation_payload).unwrap(); + let _guard = trace_span!( + "get_setting handler resolving", + object = self.object_kind, + setting = operation.setting_name + ) + .entered(); + + if let Some((domain, callback)) = self.handlers.setting_getters.get( + &ObjectSettingPair::new(self.object_kind, &operation.setting_name), + ) { + trace_span!( + "get_setting handler.", + object = self.object_kind, + setting_name = operation.setting_name + ); + + let object_vtable = domain + .object_vtable(self.object_kind) + .ok_or_else(|| HandlerError::Unhandled)?; + trace!("Resolved object vtable for {}", self.object_kind); + + let setting_vtable = domain + .setting_vtable(self.object_kind, &operation.setting_name) + .ok_or_else(|| HandlerError::Unhandled)?; + trace!("Resolved setting vtable for {}", operation.setting_name); + + let object = unsafe { (object_vtable.from_str)(object) } + .map_err(|_| HandlerError::Internal(()))?; + + let _guard = debug_span!("get_value handler"); + + let result = + unsafe { (callback.func)(callback.callback_ptr, &domain.plugin.state, object) }; + + // Todo deser + + return Ok(()); + } else { + trace!("Failed to resolve handler."); + } + } + + // ⚠️ Special Case ⚠️ + // object_kind: `any` + // operation_kind: `SetSetting` + if self.operation_name == "set_setting" {} + + // ⚠️ Special Case ⚠️ + // object_kind: `any` + // operation_kind: `ObjectRequest` + if self.operation_name == "object_request" {} + + // object_kind: `typed` + // operation_kind: `typed` + if let Some((domain, handler)) = + self.handlers + .operation_handlers + .get(&ObjectOperationPair::new( + self.object_kind, + self.operation_name, + )) + { + let _guard = trace_span!("typed_typed handler resolved").entered(); + + let object_vtable = domain + .object_vtable(self.object_kind) + .ok_or_else(|| HandlerError::Unhandled)?; + trace!("Resolved object vtable for {}", self.object_kind); + + let operation_vtable = domain + .operation_vtable(self.object_kind, self.operation_name) + .ok_or_else(|| HandlerError::Unhandled)?; + trace!( + "Resolved operation vtable for {}::{}", + self.object_kind, + self.operation_name + ); + + let object = unsafe { (object_vtable.from_str)(object) } + .map_err(|_| HandlerError::Internal(()))?; + let operation = unsafe { (operation_vtable.deserialize)(operation_payload) } + .map_err(|_| HandlerError::Internal(()))?; + trace!("Parsed operation data"); + + let _guard = debug_span!("calling handler").entered(); + let result = unsafe { + (handler.func)( + handler.callback_ptr, + runtime_state, + &domain.plugin.state, + object, + operation, + ) + }; + + // todo + return Ok(()); + } + + Err(HandlerError::Unhandled) + } +} diff --git a/giterated-plugin/src/new_stack/runtime_handler.rs b/giterated-plugin/src/new_stack/runtime_handler.rs new file mode 100644 index 0000000..178bff6 --- /dev/null +++ b/giterated-plugin/src/new_stack/runtime_handler.rs @@ -0,0 +1,76 @@ +use std::fmt::Debug; +use std::sync::Arc; + +use giterated_models::{ + error::OperationError, + object::{GiteratedObject, Object, ObjectRequestError}, + object_backend::ObjectBackend, + operation::GiteratedOperation, +}; + +use crate::vtable::{AnyFailure, AnySuccess, OperationVTable}; + +use super::PluginState; + +#[derive(Clone)] +pub struct RuntimeHandle { + inner: Arc, +} + +impl RuntimeHandle { + pub async fn handle_serialized( + &self, + operation_name: &str, + object: &str, + operation: &[u8], + ) -> Result, OperationError>> { + todo!() + } +} + +#[repr(C)] +struct RuntimeHandleInner { + state: PluginState, + handle_serialized: unsafe extern "C" fn( + object_kind: &str, + operation_name: &str, + object: &str, + operation_payload: &[u8], + ) -> HandlerResult, +} + +unsafe impl Send for RuntimeHandleInner {} +unsafe impl Sync for RuntimeHandleInner {} + +#[repr(C)] +struct HandlerResult { + operation_vtable: OperationVTable, + result: Result>, +} + +#[async_trait::async_trait(?Send)] +impl ObjectBackend for RuntimeHandle { + async fn object_operation( + &self, + object: O, + operation: &str, + payload: D, + operation_state: &S, + ) -> Result> + where + O: GiteratedObject + Debug + 'static, + D: GiteratedOperation + Debug + 'static, + D::Success: Clone, + D::Failure: Clone, + { + todo!() + } + + async fn get_object( + &self, + object_str: &str, + operation_state: &S, + ) -> Result, OperationError> { + todo!() + } +} diff --git a/giterated-plugin/src/vtable/host.rs b/giterated-plugin/src/vtable/host.rs new file mode 100644 index 0000000..f1204b2 --- /dev/null +++ b/giterated-plugin/src/vtable/host.rs @@ -0,0 +1,64 @@ +use super::{ObjectVtable, OperationVTable, SettingVtable, ValueVTable}; +use crate::{ + callback::{OperationHandlerCallback, SettingGetterCallback, ValueGetterCallback}, + handle::PluginInitializationState, +}; +use std::fmt::Debug; + +#[repr(C)] +pub struct HostVTable {} + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct InitializationVTable { + pub register_object: + unsafe extern "C" fn(*mut PluginInitializationState, &'static str, ObjectVtable), + pub register_operation: unsafe extern "C" fn( + *mut PluginInitializationState, + &'static str, + &'static str, + OperationVTable, + ), + pub register_setting: unsafe extern "C" fn( + *mut PluginInitializationState, + &'static str, + &'static str, + SettingVtable, + ), + pub register_value: unsafe extern "C" fn( + *mut PluginInitializationState, + &'static str, + &'static str, + ValueVTable, + ), + + pub operation_handler: unsafe extern "C" fn( + *mut PluginInitializationState, + &'static str, + &'static str, + OperationHandlerCallback, + ), + + pub value_getter: unsafe extern "C" fn( + *mut PluginInitializationState, + &'static str, + &'static str, + ValueGetterCallback, + ), + + pub setting_getter: unsafe extern "C" fn( + *mut PluginInitializationState, + &'static str, + &'static str, + SettingGetterCallback, + ), +} + +impl Debug for InitializationVTable { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("InitializationVTable").finish() + } +} + +unsafe impl Sync for InitializationVTable {} +unsafe impl Send for InitializationVTable {} diff --git a/giterated-plugin/src/vtable/mod.rs b/giterated-plugin/src/vtable/mod.rs new file mode 100644 index 0000000..543839a --- /dev/null +++ b/giterated-plugin/src/vtable/mod.rs @@ -0,0 +1,13 @@ +//! Giterated's VTable System +//! +//! Docs here? :) +mod setting; +pub use setting::*; +mod operation; +pub use operation::*; +mod object; +pub use object::*; +mod value; +pub use value::*; +mod host; +pub use host::*; diff --git a/giterated-plugin/src/vtable/object.rs b/giterated-plugin/src/vtable/object.rs new file mode 100644 index 0000000..dc00436 --- /dev/null +++ b/giterated-plugin/src/vtable/object.rs @@ -0,0 +1,109 @@ +use std::mem::transmute; + +use giterated_models::object::GiteratedObject; + +use crate::FFIBox; + +#[derive(Clone, Copy)] +#[repr(C)] +pub struct ObjectVtable { + object_kind: *const u8, + object_kind_len: usize, + pub to_str: unsafe extern "C" fn(AnyObject) -> FFIBox, + pub from_str: unsafe extern "C" fn(&str) -> Result>, + pub home_uri: unsafe extern "C" fn(&AnyObject) -> FFIBox, + pub is_same: unsafe extern "C" fn(AnyObject) -> bool, +} + +impl ObjectVtable { + pub fn new() -> Self { + let object_kind = T::object_kind().as_ptr(); + let object_kind_len = T::object_kind().len(); + + Self { + to_str: T::to_str, + from_str: T::from_str, + home_uri: T::home_uri, + is_same: T::is_same, + object_kind, + object_kind_len, + } + } + + pub fn kind(&self) -> &'static str { + let slice = unsafe { std::slice::from_raw_parts(self.object_kind, self.object_kind_len) }; + + std::str::from_utf8(slice).unwrap() + } +} + +pub trait IntoObjectVTable { + fn object_kind() -> &'static str; + unsafe extern "C" fn to_str(this: AnyObject) -> FFIBox; + unsafe extern "C" fn from_str(src: &str) -> Result>; + unsafe extern "C" fn home_uri(this: &AnyObject) -> FFIBox; + unsafe extern "C" fn is_same(other: AnyObject) -> bool; +} + +impl IntoObjectVTable for T { + unsafe extern "C" fn to_str(mut this: AnyObject) -> FFIBox { + let this: &Box = this.transmute_ref(); + + let result = this.to_string(); + + FFIBox::from_box(result.into_boxed_str()) + } + + unsafe extern "C" fn from_str(src: &str) -> Result> { + let result = T::from_object_str(src).unwrap(); + + let any_object = AnyObject::new(result); + + Ok(any_object) + } + + unsafe extern "C" fn home_uri(this: &AnyObject) -> FFIBox { + todo!() + } + + unsafe extern "C" fn is_same(other: AnyObject) -> bool { + todo!() + } + + fn object_kind() -> &'static str { + todo!() + } +} + +#[repr(C)] +pub struct AnyObject { + /// A pointer to the plugin-local object type. We are not capable of + /// knowing what this type is, we use the provided vtable. + inner: FFIBox<()>, + vtable: ObjectVtable, +} + +impl AnyObject { + pub fn new(inner: T) -> Self { + Self { + inner: FFIBox::from_box(Box::new(inner)).untyped(), + vtable: ObjectVtable::new::(), + } + } + + pub fn vtable(&self) -> ObjectVtable { + self.vtable + } +} + +impl AnyObject { + pub unsafe fn transmute_owned(&mut self) -> Box { + Box::from_raw(self.inner.0 as *mut T) + } + + pub unsafe fn transmute_ref(&self) -> &T { + let ptr: *const T = transmute(self.inner.0); + + ptr.as_ref().unwrap() + } +} diff --git a/giterated-plugin/src/vtable/operation.rs b/giterated-plugin/src/vtable/operation.rs new file mode 100644 index 0000000..5ecfa36 --- /dev/null +++ b/giterated-plugin/src/vtable/operation.rs @@ -0,0 +1,136 @@ +use std::mem::transmute; + +use giterated_models::{object::GiteratedObject, operation::GiteratedOperation}; + +use crate::FFIBox; + +use super::AnyObject; + +#[derive(Clone, Copy)] +#[repr(C)] +pub struct OperationVTable { + operation_kind: *const u8, + operation_kind_len: usize, + pub serialize: unsafe extern "C" fn(&AnyOperation) -> Result, ()>, + pub deserialize: unsafe extern "C" fn(&[u8]) -> Result, + pub is_same: unsafe extern "C" fn(AnyObject) -> bool, + pub serialize_success: unsafe extern "C" fn(()) -> Result, ()>, + pub serialize_failure: unsafe extern "C" fn(()) -> Result, ()>, + pub deserialize_success: unsafe extern "C" fn(&[u8]) -> Result, + pub deserialize_failure: unsafe extern "C" fn(&[u8]) -> Result, +} + +impl OperationVTable { + pub fn new>() -> Self { + let operation_kind = T::operation_kind().as_ptr(); + let operation_kind_len = T::operation_kind().len(); + + Self { + serialize: T::serialize, + deserialize: T::deserialize, + is_same: T::is_same, + serialize_success: T::serialize_success, + serialize_failure: T::serialize_failure, + deserialize_success: T::deserialize_success, + deserialize_failure: T::deserialize_failure, + operation_kind, + operation_kind_len, + } + } + + pub fn kind(&self) -> &'static str { + let slice = + unsafe { std::slice::from_raw_parts(self.operation_kind, self.operation_kind_len) }; + + std::str::from_utf8(slice).unwrap() + } +} + +pub struct AnyOperation { + /// A pointer to the plugin-local object type. We are not capable of + /// knowing what this type is, we use the provided vtable. + inner: FFIBox<()>, + vtable: OperationVTable, +} + +impl AnyOperation { + pub unsafe fn transmute_owned(&mut self) -> Box { + Box::from_raw(self.inner.0 as *mut T) + } + + pub unsafe fn transmute_ref(&self) -> &T { + let ptr: *const T = transmute(self.inner.0); + + ptr.as_ref().unwrap() + } + + pub fn vtable(&self) -> OperationVTable { + self.vtable + } +} + +#[repr(C)] +pub struct AnySuccess { + inner: FFIBox<()>, + vtable: OperationVTable, +} + +#[repr(C)] +pub struct AnyFailure { + inner: FFIBox<()>, + vtable: OperationVTable, +} + +pub trait IntoOperationVTable { + fn operation_kind() -> &'static str; + unsafe extern "C" fn serialize(this: &AnyOperation) -> Result, ()>; + unsafe extern "C" fn deserialize(src: &[u8]) -> Result; + unsafe extern "C" fn is_same(this: AnyObject) -> bool; + unsafe extern "C" fn serialize_success(success: ()) -> Result, ()>; + unsafe extern "C" fn serialize_failure(failure: ()) -> Result, ()>; + unsafe extern "C" fn deserialize_success(src: &[u8]) -> Result; + unsafe extern "C" fn deserialize_failure(src: &[u8]) -> Result; +} + +impl IntoOperationVTable for D +where + D: GiteratedOperation, + O: GiteratedObject, +{ + unsafe extern "C" fn serialize(this: &AnyOperation) -> Result, ()> { + todo!() + } + + unsafe extern "C" fn deserialize(src: &[u8]) -> Result { + let deserialized: D = serde_json::from_slice(src).unwrap(); + + Ok(AnyOperation { + inner: FFIBox::from_box(Box::new(deserialized)).untyped(), + vtable: OperationVTable::new::(), + }) + } + + unsafe extern "C" fn is_same(this: AnyObject) -> bool { + todo!() + } + + unsafe extern "C" fn serialize_success(success: ()) -> Result, ()> { + todo!() + } + + unsafe extern "C" fn serialize_failure(failure: ()) -> Result, ()> { + todo!() + } + + unsafe extern "C" fn deserialize_success(src: &[u8]) -> Result { + todo!() + } + + unsafe extern "C" fn deserialize_failure(src: &[u8]) -> Result { + todo!() + } + + fn operation_kind() -> &'static str { + todo!() + } +} diff --git a/giterated-plugin/src/vtable/setting.rs b/giterated-plugin/src/vtable/setting.rs new file mode 100644 index 0000000..4fd768f --- /dev/null +++ b/giterated-plugin/src/vtable/setting.rs @@ -0,0 +1,66 @@ +use std::mem::transmute; + +use giterated_models::settings::Setting; + +use crate::FFIBox; + +#[repr(C)] +pub struct NewAnySetting { + /// A pointer to the plugin-local object type. We are not capable of + /// knowing what this type is, we use the provided vtable. + inner: FFIBox<()>, + vtable: SettingVtable, +} + +impl NewAnySetting { + pub fn new(value: V) -> Self { + Self { + inner: FFIBox::from_box(Box::new(value)).untyped(), + vtable: SettingVtable::new::(), + } + } + + pub unsafe fn transmute_owned(self) -> Box { + Box::from_raw(self.inner.0 as *mut T) + } + + pub unsafe fn transmute_ref(&self) -> &T { + let ptr: *const T = transmute(self.inner.0); + + ptr.as_ref().unwrap() + } +} + +#[derive(Clone, Copy)] +#[repr(C)] +pub struct SettingVtable { + pub deserialize: unsafe extern "C" fn(&[u8]) -> Result<(), ()>, + pub serialize: unsafe extern "C" fn(NewAnySetting) -> Result, ()>, +} + +impl SettingVtable { + pub fn new() -> Self { + Self { + deserialize: T::deserialize, + serialize: T::serialize, + } + } +} + +pub trait IntoSettingVTable { + unsafe extern "C" fn deserialize(src: &[u8]) -> Result<(), ()>; + unsafe extern "C" fn serialize(this: NewAnySetting) -> Result, ()>; +} + +impl IntoSettingVTable for S +where + S: Setting, +{ + unsafe extern "C" fn deserialize(src: &[u8]) -> Result<(), ()> { + todo!() + } + + unsafe extern "C" fn serialize(this: NewAnySetting) -> Result, ()> { + todo!() + } +} diff --git a/giterated-plugin/src/vtable/value.rs b/giterated-plugin/src/vtable/value.rs new file mode 100644 index 0000000..ba9eb93 --- /dev/null +++ b/giterated-plugin/src/vtable/value.rs @@ -0,0 +1,67 @@ +use giterated_models::{object::GiteratedObject, value::GiteratedObjectValue}; + +use crate::FFIBox; + +#[repr(C)] +pub struct NewAnyValue { + /// A pointer to the plugin-local object type. We are not capable of + /// knowing what this type is, we use the provided vtable. + inner: FFIBox<()>, + vtable: ValueVTable, +} + +impl NewAnyValue { + pub fn new>(value: V) -> Self { + NewAnyValue { + inner: FFIBox::from_box(Box::new(value)).untyped(), + vtable: ValueVTable::new::(), + } + } +} + +#[derive(Clone, Copy)] +#[repr(C)] +pub struct ValueVTable { + pub deserialize: unsafe extern "C" fn(&[u8]) -> Result, + pub serialize: unsafe extern "C" fn(NewAnyValue) -> Result, ()>, +} + +impl ValueVTable { + pub fn new>() -> Self { + Self { + deserialize: T::deserialize, + serialize: T::serialize, + } + } +} + +pub trait IntoValueVTable { + unsafe extern "C" fn deserialize(src: &[u8]) -> Result; + unsafe extern "C" fn serialize(this: NewAnyValue) -> Result, ()>; +} + +impl IntoValueVTable for V +where + O: GiteratedObject, + V: GiteratedObjectValue, +{ + unsafe extern "C" fn deserialize(src: &[u8]) -> Result { + let _guard = trace_span!( + "deserialize value", + object = O::object_name(), + value = V::value_name() + ); + + trace!("Deserializing"); + let deserialized: V = serde_json::from_slice(src).unwrap(); + + Ok(NewAnyValue { + inner: FFIBox::from_box(Box::new(deserialized)).untyped(), + vtable: ValueVTable::new::(), + }) + } + + unsafe extern "C" fn serialize(this: NewAnyValue) -> Result, ()> { + todo!() + } +} diff --git a/giterated-plugins/README.md b/giterated-plugins/README.md deleted file mode 100644 index 970f289..0000000 --- a/giterated-plugins/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Plugins - -Giterated uses a Plugin system to allow for easy extension, but also to separate concerns in the core. \ No newline at end of file diff --git a/giterated-plugins/example-plugin/Cargo.toml b/giterated-plugins/example-plugin/Cargo.toml deleted file mode 100644 index 1d1958a..0000000 --- a/giterated-plugins/example-plugin/Cargo.toml +++ /dev/null @@ -1,21 +0,0 @@ -[package] -name = "example-plugin" -version = "0.1.0" -edition = "2021" - -[lib] -name = "example_plugin_dylib" -path = "src/lib.rs" -crate-type = ["dylib"] - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -giterated-plugin = { path = "../giterated-plugin" } -giterated-plugin-sys = { path = "../giterated-plugin-sys" } -dlopen2 = "0.6" -tracing-subscriber = "0.3" -giterated-models = { path = "../../giterated-models" } -tracing = "0.1" -serde_json = "1.0" -anyhow = "1" diff --git a/giterated-plugins/example-plugin/src/lib.rs b/giterated-plugins/example-plugin/src/lib.rs deleted file mode 100644 index 3125afc..0000000 --- a/giterated-plugins/example-plugin/src/lib.rs +++ /dev/null @@ -1,95 +0,0 @@ -use std::sync::OnceLock; - -use giterated_models::{ - error::OperationError, - instance::Instance, - object::ObjectRequest, - user::{DisplayName, User}, -}; -use giterated_plugin::{ - handle::PluginInitializationState, - new_stack::{FFIPluginMeta, PluginState}, - vtable::{HostVTable, InitializationVTable}, -}; -use giterated_plugin_sys::PluginStackBuilder; -use tracing::{info, trace_span, Level}; - -static INIT_VTABLE: OnceLock = OnceLock::new(); - -#[no_mangle] -pub extern "C" fn plugin_meta() -> FFIPluginMeta { - const PLUGIN_NAME: &str = "Example Plugin"; - const PLUGIN_VERSION: &str = "1.0.0"; - - FFIPluginMeta { - name: PLUGIN_NAME.as_ptr(), - name_len: PLUGIN_NAME.len(), - version: PLUGIN_VERSION.as_ptr(), - version_len: PLUGIN_VERSION.len(), - } -} - -#[no_mangle] -pub extern "C" fn load_host_vtable(_vtable: &HostVTable) { - println!("Loading vtable"); -} - -#[no_mangle] -pub extern "C" fn load_initialization_vtable(init_vtable: &InitializationVTable) { - INIT_VTABLE.set(init_vtable.clone()).unwrap(); - println!("Loaded initialization vtable"); -} - -#[no_mangle] -pub extern "C" fn initialize() -> PluginState { - tracing_subscriber::fmt() - .pretty() - .with_thread_names(true) - .with_max_level(Level::TRACE) - .init(); - - PluginState { - inner: Box::into_raw(Box::new(())), - } -} - -#[no_mangle] -pub extern "C" fn initialize_registration( - state: *mut PluginInitializationState, -) -> *mut PluginInitializationState { - let _guard: tracing::span::EnteredSpan = trace_span!("initialize_registration").entered(); - let init_vtable = INIT_VTABLE.get().unwrap(); - let mut builder = PluginStackBuilder::new((), state, init_vtable); - - builder.object::().object::(); - builder.operation(handler); - builder.value(value_getter); - // builder.setting_getter(setting_getter); - - state -} - -async fn handler( - state: (), - _object: Instance, - _operation: ObjectRequest, -) -> Result<(), OperationError<()>> { - info!("handling operation!"); - - todo!() -} - -async fn value_getter( - state: (), - object: User, -) -> Result> { - info!("OwO, value gotten!"); - - Ok(DisplayName(String::from("heya!"))) -} - -// fn setting_getter(state: (), object: User) -> Result { -// info!("OwO, setting gotten!"); - -// Ok(DisplayName(String::from("heya! (but from a setting)"))) -// } diff --git a/giterated-plugins/example-plugin/src/main.rs b/giterated-plugins/example-plugin/src/main.rs deleted file mode 100644 index c2240d1..0000000 --- a/giterated-plugins/example-plugin/src/main.rs +++ /dev/null @@ -1,63 +0,0 @@ -use dlopen2::wrapper::Container; -use giterated_models::{ - instance::Instance, - object::{GiteratedObject, ObjectRequest}, - operation::GiteratedOperation, - settings::{GetSetting, Setting}, - user::{DisplayName, User}, - value::GetValue, -}; -use giterated_plugin::{handle::PluginHandle, new_stack::Runtime, GiteratedPluginApi}; -use tracing::{info, Level}; - -fn main() { - tracing_subscriber::fmt() - .pretty() - .with_thread_names(true) - .with_max_level(Level::TRACE) - .init(); - - let mut handle = PluginHandle::from_dylib("example_plugin_dylib.dll").unwrap(); - - let mut runtime = Runtime::default(); - - runtime.insert_plugin(handle); - - let object_request = ObjectRequest(String::from("foobar")); - - match runtime.handle( - Instance::object_name(), - ObjectRequest::operation_name(), - "meow", - &serde_json::to_vec(&object_request).unwrap(), - ) { - Ok(success) => info!("handler call success!"), - Err(_) => info!("handler call error"), - } - - match runtime.handle( - User::object_name(), - >::operation_name(), - "amber:giterated.dev", - &serde_json::to_vec(&GetValue { - value_name: String::from("display_name"), - }) - .unwrap(), - ) { - Ok(success) => info!("get_value handler call success!"), - Err(_) => info!("get_value handler call error"), - } - - match runtime.handle( - User::object_name(), - >::operation_name(), - "amber:giterated.dev", - &serde_json::to_vec(&GetSetting { - setting_name: DisplayName::name().to_string(), - }) - .unwrap(), - ) { - Ok(success) => info!("get_setting handler call success!"), - Err(_) => info!("get_setting handler call error"), - } -} diff --git a/giterated-plugins/giterated-backend/Cargo.toml b/giterated-plugins/giterated-backend/Cargo.toml deleted file mode 100644 index 8baed05..0000000 --- a/giterated-plugins/giterated-backend/Cargo.toml +++ /dev/null @@ -1,20 +0,0 @@ -[package] -name = "giterated-backend" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -giterated-plugin = { path = "../giterated-plugin" } -giterated-models = { path = "../../giterated-models" } -serde = { version = "1.0", features = [ "derive" ]} -anyhow = "1" -thiserror = "1" -sqlx = { version = "0.7", features = [ "runtime-tokio", "tls-native-tls", "postgres", "macros", "migrate", "chrono" ] } -tokio = { version = "1.32", features = [ "full" ] } -giterated-plugin-sys = { path = "../giterated-plugin-sys" } -toml = { version = "0.8" } -tracing = "0.1" -tracing-subscriber = "0.3" -serde_json = "1.0" diff --git a/giterated-plugins/giterated-backend/src/handlers.rs b/giterated-plugins/giterated-backend/src/handlers.rs deleted file mode 100644 index a6a6118..0000000 --- a/giterated-plugins/giterated-backend/src/handlers.rs +++ /dev/null @@ -1,28 +0,0 @@ -use std::sync::Arc; - -use giterated_models::{ - error::{OperationError, RepositoryError, UserError}, - repository::{Repository, RepositoryInfoRequest, RepositorySummary, RepositoryView}, - user::{User, UserRepositoriesRequest}, -}; -use giterated_plugin::new_stack::{runtime_handler::RuntimeHandle, OperationState, Runtime, State}; - -use crate::DatabaseBackend; - -pub async fn user_get_repositories( - state: DatabaseBackend, - object: User, - request: UserRepositoriesRequest, -) -> Result, OperationError> { - todo!() -} - -pub async fn repository_info( - state: DatabaseBackend, - object: Repository, - request: RepositoryInfoRequest, - runtime: RuntimeHandle, - State(operation_state): State, -) -> Result> { - todo!() -} diff --git a/giterated-plugins/giterated-backend/src/lib.rs b/giterated-plugins/giterated-backend/src/lib.rs deleted file mode 100644 index 4f4e49b..0000000 --- a/giterated-plugins/giterated-backend/src/lib.rs +++ /dev/null @@ -1,16 +0,0 @@ -pub mod handlers; -pub mod value; - -use giterated_models::{instance::Instance, repository::Repository, user::User}; -use giterated_plugin_sys::{PluginStackBuilder, ValueSettingExt}; -use handlers::{repository_info, user_get_repositories}; -use sqlx::PgPool; -use value::{user_get_bio, user_get_display_name, user_set_bio, user_set_display_name}; - -/// A backend implementation which attempts to resolve data from the instance's database. -#[derive(Debug, Clone)] -#[allow(unused)] -pub struct DatabaseBackend { - pub(self) our_instance: Instance, - pub(self) pool: PgPool, -} diff --git a/giterated-plugins/giterated-backend/src/value.rs b/giterated-plugins/giterated-backend/src/value.rs deleted file mode 100644 index ea9a291..0000000 --- a/giterated-plugins/giterated-backend/src/value.rs +++ /dev/null @@ -1,36 +0,0 @@ -use giterated_models::{ - error::OperationError, - user::{Bio, DisplayName, User}, -}; - -use crate::DatabaseBackend; - -pub async fn user_get_display_name( - state: DatabaseBackend, - user: User, -) -> Result> { - todo!() -} - -pub async fn user_set_display_name( - state: DatabaseBackend, - user: User, - display_name: DisplayName, -) -> Result<(), OperationError> { - todo!() -} - -pub async fn user_get_bio( - state: DatabaseBackend, - user: User, -) -> Result> { - todo!() -} - -pub async fn user_set_bio( - state: DatabaseBackend, - user: User, - bio: Bio, -) -> Result<(), OperationError> { - todo!() -} diff --git a/giterated-plugins/giterated-issues/Cargo.toml b/giterated-plugins/giterated-issues/Cargo.toml deleted file mode 100644 index 85dbd62..0000000 --- a/giterated-plugins/giterated-issues/Cargo.toml +++ /dev/null @@ -1,25 +0,0 @@ -[package] -name = "giterated-issues" -version = "0.1.0" -edition = "2021" - -[lib] -name = "giterated_issues" -path = "src/lib.rs" -crate-type = ["rlib", "dylib"] - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -giterated-plugin = { path = "../giterated-plugin" } -giterated-models = { path = "../../giterated-models" } -serde = { version = "1.0", features = [ "derive" ]} -anyhow = "1" -thiserror = "1" -sqlx = { version = "0.7", features = [ "runtime-tokio", "tls-native-tls", "postgres", "macros", "migrate", "chrono" ] } -tokio = { version = "1.32", features = [ "full" ] } -giterated-plugin-sys = { path = "../giterated-plugin-sys" } -toml = { version = "0.8" } -tracing = "0.1" -tracing-subscriber = "0.3" -serde_json = "1.0" diff --git a/giterated-plugins/giterated-issues/src/db.rs b/giterated-plugins/giterated-issues/src/db.rs deleted file mode 100644 index 3471fd9..0000000 --- a/giterated-plugins/giterated-issues/src/db.rs +++ /dev/null @@ -1,24 +0,0 @@ -use giterated_models::{repository::Repository, user::User}; - -use crate::operations::CommentVisibility; - -/// An [`Issue`]'s database representation. -#[derive(Debug, sqlx::FromRow)] -pub struct IssueRow { - pub id: i32, - #[sqlx(try_from = "String")] - pub repository: Repository, - #[sqlx(try_from = "String")] - pub author: User, - pub creation_date: i32, - pub issue_name: String, - pub contents: Option, -} - -#[derive(Debug, sqlx::FromRow)] -pub struct IssueCommentRow { - #[sqlx(try_from = "String")] - pub author: User, - pub contents: Option, - pub visibility: CommentVisibility, -} diff --git a/giterated-plugins/giterated-issues/src/handlers.rs b/giterated-plugins/giterated-issues/src/handlers.rs deleted file mode 100644 index 4463009..0000000 --- a/giterated-plugins/giterated-issues/src/handlers.rs +++ /dev/null @@ -1,118 +0,0 @@ -use crate::db::IssueRow; -use crate::setting::Contents; -use crate::value::{Author, CommentCount, CreationDate, Name}; -use crate::IssuesPluginState; -use crate::{ - operations::{ - CreateIssueRequest, IssueCreationError, IssueEditError, IssueEditRequest, - IssuePostCommentError, IssuePostCommentRequest, IssueQueryError, QueryIssuesRequest, - }, - Issue, -}; -use giterated_models::error::IntoInternalError; -use giterated_models::user::User; -use giterated_models::{error::OperationError, repository::Repository}; -use sqlx::PgPool; - -pub async fn create_issue_request( - state: IssuesPluginState, - repository: Repository, - request: CreateIssueRequest, -) -> Result> { - // TODO: AUTHN & AUTHZ - let issue = sqlx::query_as!( - IssueRow, - r#"INSERT INTO issues VALUES (null, $1, $2, $3, $4, $5) RETURNING *"#, - repository.to_string(), - request.author.to_string(), - 0, - request.name, - request.contents, - ) - .fetch_one(&state.pool) - .await - .as_internal_error_with_context("creating issue in db")?; - - Ok(Issue { - repository, - id: issue.id as u32, - }) -} - -pub async fn query_issues_request( - state: IssuesPluginState, - repository: Repository, - request: QueryIssuesRequest, -) -> Result, OperationError> { - // TODO: AUTHN & AUTHZ - todo!() -} - -pub async fn edit_issue_request( - state: IssuesPluginState, - issue: Issue, - request: IssueEditRequest, -) -> Result<(), OperationError> { - // TODO: AUTHN & AUTHZ - todo!() -} - -pub async fn issue_post_comment_request( - state: IssuesPluginState, - issue: Issue, - request: IssuePostCommentRequest, -) -> Result> { - // TODO: AUTHN & AUTHZ - todo!() -} - -pub async fn issue_value_author( - state: IssuesPluginState, - issue: Issue, -) -> Result> { - todo!() -} - -pub async fn issue_value_creation_date( - state: IssuesPluginState, - issue: Issue, -) -> Result> { - todo!() -} - -pub async fn issue_value_comment_count( - state: IssuesPluginState, - issue: Issue, -) -> Result> { - todo!() -} - -pub async fn issue_set_setting_name( - state: IssuesPluginState, - issue: Issue, - name: Name, -) -> Result<(), OperationError> { - todo!() -} - -pub async fn issue_get_setting_name( - state: IssuesPluginState, - issue: Issue, -) -> Result> { - todo!() -} - -pub async fn issue_set_setting_contents( - state: IssuesPluginState, - issue: Issue, - contents: Contents, -) -> Result<(), OperationError> { - todo!() -} - -pub async fn issue_get_setting_contents( - state: IssuesPluginState, - issue: Issue, -) -> Result> { - todo!() -} diff --git a/giterated-plugins/giterated-issues/src/lib.rs b/giterated-plugins/giterated-issues/src/lib.rs deleted file mode 100644 index 9e84f14..0000000 --- a/giterated-plugins/giterated-issues/src/lib.rs +++ /dev/null @@ -1,193 +0,0 @@ -use std::{ - fmt::Display, - str::FromStr, - sync::{Arc, OnceLock}, -}; - -use anyhow::Error; -use giterated_models::{object::GiteratedObject, repository::Repository}; -use giterated_plugin::{ - handle::PluginInitializationState, - new_stack::{FFIPluginMeta, PluginState}, - vtable::{HostVTable, InitializationVTable}, -}; -use giterated_plugin_sys::PluginStackBuilder; -use handlers::{ - create_issue_request, edit_issue_request, issue_get_setting_contents, issue_get_setting_name, - issue_post_comment_request, issue_set_setting_contents, issue_set_setting_name, - issue_value_author, issue_value_comment_count, issue_value_creation_date, query_issues_request, -}; -use serde::{Deserialize, Serialize}; -use setting::{Contents, NotificationsOverride}; -use sqlx::{postgres::PgConnectOptions, PgPool}; -use tokio::{ - fs::File, - io::AsyncReadExt, - runtime::Runtime, - spawn, - task::{block_in_place, spawn_blocking}, -}; -use toml::Table; -use tracing::{debug, info}; -use value::{Author, CommentCount, CreationDate, Name}; - -pub mod db; -pub mod handlers; -pub mod operations; -pub mod setting; -pub mod value; - -/// An issue, defined by the repository which owns it and its index. -/// -/// # Textual Format -/// An issue's textual format is defined as: -/// -/// `@{index: u32}:{repository: Repository}` -#[derive(Hash, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] -pub struct Issue { - pub repository: Repository, - pub id: u32, -} - -impl Display for Issue { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(&format!("{}:{}", self.id, self.repository)) - } -} - -impl GiteratedObject for Issue { - fn object_name() -> &'static str { - "issue" - } - - fn home_uri(&self) -> String { - self.repository.home_uri() - } - - fn from_object_str(object_str: &str) -> Result { - Ok(Issue::from_str(object_str)?) - } -} - -impl FromStr for Issue { - type Err = IssueParseError; - - fn from_str(s: &str) -> Result { - let (index, repository) = s.split_once(':').ok_or_else(|| IssueParseError)?; - - let id: u32 = index.parse().map_err(|_| IssueParseError)?; - let repository = Repository::from_str(repository).map_err(|_| IssueParseError)?; - - Ok(Self { repository, id }) - } -} - -#[derive(Debug, thiserror::Error)] -#[error("error parsing issue")] -pub struct IssueParseError; - -static INIT_VTABLE: OnceLock = OnceLock::new(); -static ASYNC_RUNTIME: OnceLock = OnceLock::new(); - -#[no_mangle] -pub extern "C" fn plugin_meta() -> FFIPluginMeta { - const PLUGIN_NAME: &str = "Giterated [Issues]"; - const PLUGIN_VERSION: &str = "0.1.0"; - - FFIPluginMeta { - name: PLUGIN_NAME.as_ptr(), - name_len: PLUGIN_NAME.len(), - version: PLUGIN_VERSION.as_ptr(), - version_len: PLUGIN_VERSION.len(), - } -} - -#[no_mangle] -pub extern "C" fn load_host_vtable(_vtable: &HostVTable) { - println!("Loading vtable"); -} - -#[no_mangle] -pub extern "C" fn load_initialization_vtable(init_vtable: &InitializationVTable) { - INIT_VTABLE.set(init_vtable.clone()).unwrap(); - println!("Loaded initialization vtable"); -} - -#[no_mangle] -pub extern "C" fn initialize() -> PluginState { - // tracing_subscriber::fmt() - // .pretty() - // .with_thread_names(true) - // .with_max_level(Level::TRACE) - // .init(); - - PluginState { - inner: Box::into_raw(Box::new(())), - } -} - -#[no_mangle] -pub extern "C" fn initialize_registration( - state: *mut PluginInitializationState, -) -> *mut PluginInitializationState { - let runtime = Runtime::new().unwrap(); - - // let _guard: tracing::span::EnteredSpan = trace_span!("initialize_registration").entered(); - let init_vtable = INIT_VTABLE.get().unwrap(); - - let db_pool = runtime.block_on(async { - let config: Table = { - let mut file = File::open("Giterated.toml").await.unwrap(); - let mut text = String::new(); - file.read_to_string(&mut text).await.unwrap(); - text.parse().unwrap() - }; - let db_conn_options = PgConnectOptions::new() - .host(config["postgres"]["host"].as_str().unwrap()) - .port(config["postgres"]["port"].as_integer().unwrap() as u16) - .database(config["postgres"]["database"].as_str().unwrap()) - .username(config["postgres"]["user"].as_str().unwrap()) - .password(config["postgres"]["password"].as_str().unwrap()); - let db_pool = PgPool::connect_with(db_conn_options).await.unwrap(); - - debug!("Running database migrations..."); - // sqlx::migrate!().run(&db_pool).await.unwrap(); - info!("Connected"); - - db_pool - }); - - ASYNC_RUNTIME.set(runtime).unwrap(); - - let plugin_state = IssuesPluginState { pool: db_pool }; - - let mut builder: PluginStackBuilder<'_, IssuesPluginState> = - PluginStackBuilder::new(plugin_state, state, init_vtable); - - builder.object::(); - - builder - .object_setting(issue_get_setting_name, issue_set_setting_name) - .object_setting(issue_get_setting_contents, issue_set_setting_contents); - - builder.object_user_setting::(); - - builder - .value(issue_value_creation_date) - .value(issue_value_comment_count) - .value(issue_get_setting_name) - .value(issue_value_author); - - builder - .operation(create_issue_request) - .operation(query_issues_request) - .operation(edit_issue_request) - .operation(issue_post_comment_request); - - state -} - -#[derive(Clone, Debug)] -pub struct IssuesPluginState { - pub pool: PgPool, -} diff --git a/giterated-plugins/giterated-issues/src/main.rs b/giterated-plugins/giterated-issues/src/main.rs deleted file mode 100644 index 1f93aa5..0000000 --- a/giterated-plugins/giterated-issues/src/main.rs +++ /dev/null @@ -1,45 +0,0 @@ -use std::str::FromStr; - -use giterated_issues::operations::CreateIssueRequest; -use giterated_models::{ - instance::Instance, - object::{GiteratedObject, ObjectRequest}, - operation::GiteratedOperation, - repository::Repository, - user::User, -}; -use giterated_plugin::{handle::PluginHandle, new_stack::Runtime}; -use tracing::Level; - -#[tokio::main] -pub async fn main() { - tracing_subscriber::fmt() - .pretty() - .with_thread_names(true) - .with_max_level(Level::TRACE) - .init(); - - let mut handle = PluginHandle::from_dylib("giterated_issues.dll").unwrap(); - - let mut runtime = Runtime::default(); - - runtime.insert_plugin(handle); - - let operation = CreateIssueRequest { - name: String::from("test issue"), - contents: String::from("hey!"), - author: User::from_str("amber:giterated.dev").unwrap(), - }; - - match runtime.handle_typed( - Repository::from_str("barson:giterated.dev/foo@giterated.dev").unwrap(), - operation, - ) { - Ok(success) => { - println!("Success in create issue: {:?}", success) - } - Err(err) => { - println!("Error in create issue: {:?}", err) - } - } -} diff --git a/giterated-plugins/giterated-issues/src/operations.rs b/giterated-plugins/giterated-issues/src/operations.rs deleted file mode 100644 index 1978b94..0000000 --- a/giterated-plugins/giterated-issues/src/operations.rs +++ /dev/null @@ -1,75 +0,0 @@ -use giterated_models::{operation::GiteratedOperation, repository::Repository, user::User}; -use serde::{Deserialize, Serialize}; - -use crate::Issue; - -/// Create an [`Issue`] on a [`Repository`]. -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct CreateIssueRequest { - pub name: String, - pub contents: String, - pub author: User, -} - -impl GiteratedOperation for CreateIssueRequest { - type Success = Issue; - - type Failure = IssueCreationError; -} - -#[derive(Clone, Debug, Serialize, Deserialize, thiserror::Error)] -#[error("failed to create issue")] -pub struct IssueCreationError; - -/// Query for [`Issue`]s on a [`Repository`]. -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct QueryIssuesRequest {} - -impl GiteratedOperation for QueryIssuesRequest { - type Success = Vec; - - type Failure = IssueQueryError; -} - -#[derive(Clone, Debug, Serialize, Deserialize, thiserror::Error)] -#[error("failed to query issues")] -pub struct IssueQueryError; - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct IssueEditRequest { - // Might not be needed :) -} - -impl GiteratedOperation for IssueEditRequest { - type Success = (); - - type Failure = IssueEditError; -} - -#[derive(Clone, Debug, Serialize, Deserialize, thiserror::Error)] -#[error("failed to edit issue")] -pub struct IssueEditError; - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct IssuePostCommentRequest { - pub contents: String, - pub visibility: CommentVisibility, -} - -impl GiteratedOperation for IssuePostCommentRequest { - type Success = u32; - - type Failure = IssuePostCommentError; -} - -#[derive(Clone, Debug, Serialize, Deserialize, thiserror::Error)] -#[error("failed to post comment")] -pub struct IssuePostCommentError; - -#[derive(PartialEq, Eq, Debug, Hash, Serialize, Deserialize, Clone, sqlx::Type)] -#[sqlx(type_name = "comment_visibility", rename_all = "lowercase")] -pub enum CommentVisibility { - Public, - Maintainers, - Private, -} diff --git a/giterated-plugins/giterated-issues/src/setting.rs b/giterated-plugins/giterated-issues/src/setting.rs deleted file mode 100644 index 0f3d08d..0000000 --- a/giterated-plugins/giterated-issues/src/setting.rs +++ /dev/null @@ -1,28 +0,0 @@ -use giterated_models::settings::Setting; -use serde::{Deserialize, Serialize}; - -use crate::value::Name; - -impl Setting for Name { - fn name() -> &'static str { - "name" - } -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct Contents(String); - -impl Setting for Contents { - fn name() -> &'static str { - "contents" - } -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct NotificationsOverride(pub bool); - -impl Setting for NotificationsOverride { - fn name() -> &'static str { - "notifications_override" - } -} diff --git a/giterated-plugins/giterated-issues/src/value.rs b/giterated-plugins/giterated-issues/src/value.rs deleted file mode 100644 index 553227f..0000000 --- a/giterated-plugins/giterated-issues/src/value.rs +++ /dev/null @@ -1,48 +0,0 @@ -use giterated_models::{user::User, value::GiteratedObjectValue}; -use serde::{Deserialize, Serialize}; - -use crate::Issue; - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct CreationDate(pub String); - -impl GiteratedObjectValue for CreationDate { - type Object = Issue; - - fn value_name() -> &'static str { - "creation_date" - } -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct CommentCount(pub u32); - -impl GiteratedObjectValue for CommentCount { - type Object = Issue; - - fn value_name() -> &'static str { - "comment_count" - } -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct Name(pub String); - -impl GiteratedObjectValue for Name { - type Object = Issue; - - fn value_name() -> &'static str { - "name" - } -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct Author(pub User); - -impl GiteratedObjectValue for Author { - type Object = Issue; - - fn value_name() -> &'static str { - "owner" - } -} diff --git a/giterated-plugins/giterated-plugin-sys/Cargo.toml b/giterated-plugins/giterated-plugin-sys/Cargo.toml deleted file mode 100644 index adc4334..0000000 --- a/giterated-plugins/giterated-plugin-sys/Cargo.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "giterated-plugin-sys" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -giterated-plugin = { path = "../giterated-plugin" } -tracing = "0.1" -giterated-models = { path = "../../giterated-models" } \ No newline at end of file diff --git a/giterated-plugins/giterated-plugin-sys/src/lib.rs b/giterated-plugins/giterated-plugin-sys/src/lib.rs deleted file mode 100644 index d077640..0000000 --- a/giterated-plugins/giterated-plugin-sys/src/lib.rs +++ /dev/null @@ -1,270 +0,0 @@ -use std::sync::OnceLock; - -use giterated_models::{ - object::GiteratedObject, operation::GiteratedOperation, settings::Setting, - value::GiteratedObjectValue, -}; -use giterated_plugin::{ - callback::{ - CallbackPtr, IntoPluginOperationHandler, IntoPluginSettingGetter, IntoPluginSettingSetter, - IntoPluginValueGetter, OperationHandlerCallback, SettingGetterCallback, - ValueGetterCallback, - }, - handle::PluginInitializationState, - new_stack::PluginState, - vtable::{ - InitializationVTable, IntoObjectVTable, IntoOperationVTable, IntoSettingVTable, - IntoValueVTable, ObjectVtable, OperationVTable, SettingVtable, ValueVTable, - }, - AnyObject, NewAnyValue, -}; -use tracing::trace_span; - -pub struct PluginStackBuilder<'init, S> { - init_state: *mut PluginInitializationState, - vtable: &'init InitializationVTable, - state: S, -} - -impl<'init, S> PluginStackBuilder<'init, S> { - pub fn new( - plugin_state: S, - state: *mut PluginInitializationState, - vtable: &'init InitializationVTable, - ) -> Self { - Self { - init_state: state, - vtable, - state: plugin_state, - } - } - - pub fn object(&mut self) -> &mut Self { - let _guard = trace_span!("register object").entered(); - - let func = self.vtable.register_object; - - unsafe { func(self.init_state, O::object_name(), ObjectVtable::new::()) }; - - self - } - - pub fn register_operation(&mut self) -> &mut Self - where - D: IntoOperationVTable + GiteratedOperation, - O: GiteratedObject, - { - let _guard = trace_span!("register operation").entered(); - - unsafe { - (self.vtable.register_operation)( - self.init_state, - O::object_name(), - D::operation_name(), - OperationVTable::new::(), - ) - } - - self - } - - pub fn object_setting(&mut self, get: HG, set: HS) -> &mut Self - where - O: GiteratedObject, - OS: IntoSettingVTable + Setting, - HG: IntoPluginSettingGetter, - HS: IntoPluginSettingSetter, - { - let _guard = trace_span!("register setting").entered(); - - unsafe { - (self.vtable.register_setting)( - self.init_state, - O::object_name(), - OS::name(), - SettingVtable::new::(), - ) - } - - self - } - - pub fn object_user_setting(&mut self) -> &mut Self - where - O: GiteratedObject, - OS: IntoSettingVTable + Setting, - { - let _guard = trace_span!("register setting").entered(); - - unsafe { - (self.vtable.register_setting)( - self.init_state, - O::object_name(), - OS::name(), - SettingVtable::new::(), - ) - } - - self - } - - pub fn value(&mut self, handler: T) -> &mut Self - where - O: GiteratedObject, - V: IntoValueVTable + GiteratedObjectValue, - T: IntoPluginValueGetter, - { - let _guard = trace_span!("register value").entered(); - - unsafe { - (self.vtable.register_value)( - self.init_state, - O::object_name(), - V::value_name(), - ValueVTable::new::(), - ) - } - - unsafe { - (self.vtable.value_getter)( - self.init_state, - O::object_name(), - V::value_name(), - ValueGetterCallback::new::(handler), - ) - } - - self - } - - pub fn operation< - DS, - DF, - A, - O: GiteratedObject + IntoObjectVTable, - D: IntoOperationVTable + GiteratedOperation, - T: IntoPluginOperationHandler, - >( - &mut self, - handler: T, - ) -> &mut Self { - let _guard = trace_span!("register operation handler").entered(); - - unsafe { - (self.vtable.operation_handler)( - self.init_state, - O::object_name(), - D::operation_name(), - OperationHandlerCallback::new::(handler), - ) - } - - // TODO: Yikes? - self.object::(); - - self.register_operation::(); - - self - } - - // pub fn value_getter(&mut self, handler: T) -> &mut Self - // where - // O: GiteratedObject + IntoObjectVTable, - // V: GiteratedObjectValue + IntoValueVTable, - // T: IntoPluginValueGetter, - // { - // let _guard = trace_span!("register value_getter handler").entered(); - - // unsafe { - // (self.vtable.value_getter)( - // self.init_state, - // O::object_name(), - // V::value_name(), - // ValueGetterCallback::new::(handler), - // ) - // } - - // // TODO: Yikes? - // self.object::(); - // self.value::(); - - // self - // } - - // pub fn setting_getter(&mut self, handler: T) -> &mut Self - // where - // O: GiteratedObject + IntoObjectVTable, - // OS: Setting + IntoSettingVTable, - // T: IntoPluginSettingGetter, - // { - // let _guard = trace_span!("register setting_getter handler").entered(); - - // unsafe { - // (self.vtable.setting_getter)( - // self.init_state, - // O::object_name(), - // OS::name(), - // SettingGetterCallback::new::(handler), - // ) - // } - - // self.object::(); - // // self.setting::(); - - // self - // } -} - -pub trait ValueSettingExt { - fn value_setting(&mut self, get: HG, set: HS) -> &mut Self - where - O: GiteratedObject + IntoObjectVTable + 'static, - VS: GiteratedObjectValue + IntoValueVTable + Setting + IntoSettingVTable, - HG: IntoPluginSettingGetter, - HS: IntoPluginSettingSetter; -} - -impl ValueSettingExt for PluginStackBuilder<'_, PS> { - fn value_setting(&mut self, get: HG, set: HS) -> &mut Self - where - O: GiteratedObject + IntoObjectVTable + 'static, - VS: GiteratedObjectValue + IntoValueVTable + Setting + IntoSettingVTable, - HG: IntoPluginSettingGetter, - HS: IntoPluginSettingSetter, - { - self - } -} - -pub trait ValueSettingGetter { - unsafe extern "C" fn get_value( - callback: CallbackPtr, - state: &PluginState, - object: AnyObject, - ) -> Result; - - fn callback_ptr(&self) -> CallbackPtr; -} - -impl ValueSettingGetter for HG -where - O: GiteratedObject, - VS: GiteratedObjectValue, - HG: IntoPluginSettingGetter, -{ - unsafe extern "C" fn get_value( - callback: CallbackPtr, - state: &PluginState, - object: AnyObject, - ) -> Result { - let result = HG::get_setting(callback, state, object)?; - - let setting = *result.transmute_owned::(); - - Ok(NewAnyValue::new(setting)) - } - - fn callback_ptr(&self) -> CallbackPtr { - self.callback_ptr() - } -} diff --git a/giterated-plugins/giterated-plugin/Cargo.toml b/giterated-plugins/giterated-plugin/Cargo.toml deleted file mode 100644 index e1596af..0000000 --- a/giterated-plugins/giterated-plugin/Cargo.toml +++ /dev/null @@ -1,16 +0,0 @@ -[package] -name = "giterated-plugin" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -dlopen2 = "0.6" -anyhow = "1" -thiserror = "1" -tracing = "0.1" -giterated-models = { path = "../../giterated-models" } -semver = "*" -serde_json = "1.0" -async-trait = "0.1" diff --git a/giterated-plugins/giterated-plugin/src/callback/mod.rs b/giterated-plugins/giterated-plugin/src/callback/mod.rs deleted file mode 100644 index 8ce311a..0000000 --- a/giterated-plugins/giterated-plugin/src/callback/mod.rs +++ /dev/null @@ -1,28 +0,0 @@ -mod operation; -use std::sync::Arc; - -pub use operation::*; -mod value; -pub use value::*; -mod setting; -pub use setting::*; - -use crate::new_stack::{runtime_handler::RuntimeHandle, PluginState, Runtime}; - -/// A container for a callback pointer, used to provide an internal callback function or -/// state to a plugin when performing a callback. -#[derive(Clone, Copy)] -#[repr(C)] -pub struct CallbackPtr(*const ()); - -impl CallbackPtr { - pub unsafe fn from_raw(callback: *const ()) -> Self { - Self(callback) - } -} - -#[repr(C)] -pub struct RuntimeState { - pub runtime: RuntimeHandle, - pub operation_state: PluginState, -} diff --git a/giterated-plugins/giterated-plugin/src/callback/operation.rs b/giterated-plugins/giterated-plugin/src/callback/operation.rs deleted file mode 100644 index db34f8a..0000000 --- a/giterated-plugins/giterated-plugin/src/callback/operation.rs +++ /dev/null @@ -1,176 +0,0 @@ -use giterated_models::error::OperationError; - -use crate::{ - new_stack::{runtime_handler::RuntimeHandle, PluginState, Runtime, State}, - AnyObject, AnyOperation, -}; - -use std::{any::type_name, fmt::Debug, future::Future, sync::Arc}; - -use super::{CallbackPtr, RuntimeState}; - -#[derive(Clone, Copy)] -pub struct OperationHandlerCallback { - pub callback_ptr: CallbackPtr, - pub func: unsafe extern "C" fn( - CallbackPtr, - &RuntimeState, - &PluginState, - object: AnyObject, - operation: AnyOperation, - ), -} - -impl OperationHandlerCallback { - pub fn new>( - handler: T, - ) -> Self { - OperationHandlerCallback { - func: T::handle, - callback_ptr: T::callback_ptr(&handler), - } - } -} - -pub trait IntoPluginOperationHandler { - unsafe extern "C" fn handle( - callback_ptr: CallbackPtr, - runtime_state: &RuntimeState, - state: &PluginState, - object: AnyObject, - operation: AnyOperation, - ); - fn callback_ptr(&self) -> CallbackPtr; -} - -impl IntoPluginOperationHandler for F -where - Fut: Future>>, - F: Fn(S, O, D) -> Fut, - S: Clone + Debug, - O: Debug, - D: Debug, -{ - unsafe extern "C" fn handle( - callback: CallbackPtr, - runtime_state: &RuntimeState, - state: &PluginState, - mut object: AnyObject, - mut operation: AnyOperation, - ) { - let _guard = trace_span!( - "operation handler", - object = type_name::(), - operation = type_name::() - ) - .entered(); - let state = unsafe { state.transmute_ref::() }; - - // Since this is Rust code, we know that the AnyObject and AnyOperation are just boxes - let object = unsafe { object.transmute_owned::() }; - let operation = unsafe { operation.transmute_owned::() }; - - // Cast the callback ptr to ourselves - let callback: *const F = std::mem::transmute(callback.0); - let callback = callback.as_ref().unwrap(); - - // callback(state.clone(), *object, *operation) - - todo!() - } - - fn callback_ptr(&self) -> CallbackPtr { - unsafe { CallbackPtr::from_raw(self as *const _ as *const ()) } - } -} - -impl IntoPluginOperationHandler for F -where - Fut: Future>>, - F: Fn(S, O, D, A1) -> Fut, - S: Clone + Debug, - O: Debug, - D: Debug, -{ - unsafe extern "C" fn handle( - callback_ptr: CallbackPtr, - runtime_state: &RuntimeState, - state: &PluginState, - object: AnyObject, - operation: AnyOperation, - ) { - todo!() - } - - fn callback_ptr(&self) -> CallbackPtr { - todo!() - } -} - -impl IntoPluginOperationHandler for F -where - Fut: Future>>, - F: Fn(S, O, D, A1, A2) -> Fut, - S: Clone + Debug, - O: Debug, - D: Debug, -{ - unsafe extern "C" fn handle( - callback_ptr: CallbackPtr, - runtime_state: &RuntimeState, - state: &PluginState, - object: AnyObject, - operation: AnyOperation, - ) { - todo!() - } - - fn callback_ptr(&self) -> CallbackPtr { - todo!() - } -} - -pub trait FromOperationState: Sized { - fn from_operation_state( - state: &S, - runtime_state: &RuntimeState, - object: &O, - operation: &D, - ) -> Result>; -} - -impl FromOperationState for RuntimeHandle { - fn from_operation_state( - state: &S, - runtime_state: &RuntimeState, - object: &O, - operation: &D, - ) -> Result> { - Ok(runtime_state.runtime.clone()) - } -} - -impl FromOperationState for Option -where - T: FromOperationState, -{ - fn from_operation_state( - state: &S, - runtime_state: &RuntimeState, - object: &O, - operation: &D, - ) -> Result> { - Ok(T::from_operation_state(state, runtime_state, object, operation).ok()) - } -} - -impl FromOperationState for State { - fn from_operation_state( - state: &S, - runtime_state: &RuntimeState, - object: &O, - operation: &D, - ) -> Result> { - Ok(unsafe { State(runtime_state.operation_state.transmute_ref::().clone()) }) - } -} diff --git a/giterated-plugins/giterated-plugin/src/callback/setting.rs b/giterated-plugins/giterated-plugin/src/callback/setting.rs deleted file mode 100644 index 21e368d..0000000 --- a/giterated-plugins/giterated-plugin/src/callback/setting.rs +++ /dev/null @@ -1,168 +0,0 @@ -use std::future::Future; - -use giterated_models::{ - error::OperationError, - object::GiteratedObject, - settings::{AnySetting, Setting}, -}; - -use crate::{new_stack::PluginState, AnyObject, NewAnySetting}; - -use super::CallbackPtr; - -#[derive(Clone, Copy)] -pub struct SettingGetterCallback { - pub callback_ptr: CallbackPtr, - pub func: unsafe extern "C" fn( - CallbackPtr, - &PluginState, - object: AnyObject, - ) -> Result, -} - -impl SettingGetterCallback { - pub fn new>(callback: T) -> Self { - Self { - func: T::get_setting, - callback_ptr: callback.callback_ptr(), - } - } -} - -pub trait IntoPluginSettingGetter { - unsafe extern "C" fn get_setting( - callback_ptr: CallbackPtr, - state: &PluginState, - object: AnyObject, - ) -> Result; - - fn callback_ptr(&self) -> CallbackPtr { - unsafe { CallbackPtr::from_raw(self as *const _ as *const ()) } - } -} - -impl IntoPluginSettingGetter for F -where - Fut: Future>>, - S: Clone, - O: GiteratedObject, - OS: Setting, - F: Fn(S, O) -> Fut, -{ - unsafe extern "C" fn get_setting( - callback: CallbackPtr, - state: &PluginState, - mut object: AnyObject, - ) -> Result { - let _guard = trace_span!( - "get_setting handler", - object = O::object_name(), - setting = OS::name() - ) - .entered(); - let state = unsafe { state.transmute_ref::() }; - - let object = unsafe { object.transmute_owned::() }; - - // Cast the callback ptr to ourselves - let callback: *const F = std::mem::transmute(callback.0); - let callback = callback.as_ref().unwrap(); - - // let result = callback(state.clone(), *object); - - // match result { - // Ok(setting) => Ok(NewAnySetting::new(setting)), - // Err(_) => todo!(), - // } - - todo!() - } -} - -pub trait IntoPluginSettingSetter { - unsafe extern "C" fn set_setting( - callback_ptr: CallbackPtr, - state: &PluginState, - object: AnyObject, - setting: AnySetting, - ) -> Result<(), ()>; - - fn callback_ptr(&self) -> CallbackPtr { - unsafe { CallbackPtr::from_raw(self as *const _ as *const ()) } - } -} - -impl IntoPluginSettingSetter for F -where - Fut: Future>>, - S: Clone, - O: GiteratedObject, - OS: Setting, - F: Fn(S, O, OS) -> Fut, -{ - unsafe extern "C" fn set_setting( - callback: CallbackPtr, - state: &PluginState, - mut object: AnyObject, - setting: AnySetting, - ) -> Result<(), ()> { - let _guard = trace_span!( - "get_setting handler", - object = O::object_name(), - setting = OS::name() - ) - .entered(); - let state = unsafe { state.transmute_ref::() }; - - let object = unsafe { object.transmute_owned::() }; - - // Cast the callback ptr to ourselves - let callback: *const F = std::mem::transmute(callback.0); - let callback = callback.as_ref().unwrap(); - - // let result = callback(state.clone(), *object); - - // match result { - // Ok(setting) => Ok(NewAnySetting::new(setting)), - // Err(_) => todo!(), - // } - todo!() - } -} - -pub struct SettingChangeCallback { - func: unsafe extern "C" fn( - &PluginState, - object: AnyObject, - setting_name: &str, - new_setting: NewAnySetting, - ), -} - -pub trait IntoSettingChangeCallback { - unsafe extern "C" fn setting_changed( - state: &PluginState, - object: AnyObject, - setting_name: &str, - new_setting: NewAnySetting, - ); -} - -impl IntoSettingChangeCallback for F { - unsafe extern "C" fn setting_changed( - state: &PluginState, - object: AnyObject, - setting_name: &str, - new_setting: NewAnySetting, - ) { - todo!() - } -} - -impl SettingChangeCallback { - pub fn new>() -> Self { - Self { - func: T::setting_changed, - } - } -} diff --git a/giterated-plugins/giterated-plugin/src/callback/value.rs b/giterated-plugins/giterated-plugin/src/callback/value.rs deleted file mode 100644 index 654ed3d..0000000 --- a/giterated-plugins/giterated-plugin/src/callback/value.rs +++ /dev/null @@ -1,120 +0,0 @@ -use std::future::Future; - -use giterated_models::{ - error::OperationError, object::GiteratedObject, settings::Setting, value::GiteratedObjectValue, -}; - -use crate::{ - new_stack::PluginState, - vtable::{AnyObject, NewAnyValue}, -}; - -use super::CallbackPtr; - -#[derive(Copy, Clone)] -pub struct ValueGetterCallback { - pub callback_ptr: CallbackPtr, - pub func: unsafe extern "C" fn( - CallbackPtr, - &PluginState, - object: AnyObject, - ) -> Result, -} - -impl ValueGetterCallback { - pub fn new>(handler: T) -> Self { - Self { - func: T::get_value, - callback_ptr: handler.callback_ptr(), - } - } -} - -pub trait IntoPluginValueGetter { - unsafe extern "C" fn get_value( - callback: CallbackPtr, - state: &PluginState, - object: AnyObject, - ) -> Result; - - fn callback_ptr(&self) -> CallbackPtr; -} - -impl IntoPluginValueGetter for F -where - Fut: Future>>, - S: Clone, - O: GiteratedObject, - V: GiteratedObjectValue, - F: Fn(S, O) -> Fut, -{ - unsafe extern "C" fn get_value( - callback: CallbackPtr, - state: &PluginState, - mut object: AnyObject, - ) -> Result { - let _guard = trace_span!( - "get_value handler", - object = O::object_name(), - value = V::value_name() - ) - .entered(); - let state = unsafe { state.transmute_ref::() }; - - let object = unsafe { object.transmute_owned::() }; - - // Cast the callback ptr to ourselves - let callback: *const F = std::mem::transmute(callback.0); - let callback = callback.as_ref().unwrap(); - - let result = callback(state.clone(), *object); - - // match result { - // Ok(value) => Ok(NewAnyValue::new(value)), - // Err(_) => todo!(), - // } - - todo!() - } - - fn callback_ptr(&self) -> CallbackPtr { - unsafe { CallbackPtr::from_raw(self as *const _ as *const ()) } - } -} - -pub struct ValueChangeCallback { - func: unsafe extern "C" fn( - &PluginState, - object: AnyObject, - value_name: &str, - new_value: NewAnyValue, - ), -} - -pub trait IntoValueChangeCallback { - unsafe extern "C" fn value_changed( - state: &PluginState, - object: AnyObject, - value_name: &str, - new_value: NewAnyValue, - ); -} - -impl IntoValueChangeCallback for F { - unsafe extern "C" fn value_changed( - state: &PluginState, - object: AnyObject, - value_name: &str, - new_value: NewAnyValue, - ) { - todo!() - } -} - -impl ValueChangeCallback { - pub fn new>() -> Self { - Self { - func: T::value_changed, - } - } -} diff --git a/giterated-plugins/giterated-plugin/src/handle.rs b/giterated-plugins/giterated-plugin/src/handle.rs deleted file mode 100644 index f262422..0000000 --- a/giterated-plugins/giterated-plugin/src/handle.rs +++ /dev/null @@ -1,256 +0,0 @@ -use std::{collections::HashMap, marker::PhantomData, path::Path, sync::Arc}; - -use anyhow::Error; -use dlopen2::wrapper::Container; -use semver::Version; -use tracing::{debug, trace}; - -use crate::{ - callback::{OperationHandlerCallback, SettingGetterCallback, ValueGetterCallback}, - new_stack::{ - ObjectOperationPair, ObjectSettingPair, ObjectValuePair, PluginMeta, PluginState, - RuntimeHandlers, TypeMetadata, - }, - vtable::{InitializationVTable, ObjectVtable, OperationVTable, SettingVtable, ValueVTable}, - GiteratedPluginApi, -}; - -#[derive(Clone)] -pub struct PluginHandle { - pub meta: PluginMeta, - pub raw: Arc>, - pub initialization: Arc, - pub state: PluginState, -} - -unsafe impl Send for PluginHandle {} -unsafe impl Sync for PluginHandle {} - -impl PluginHandle { - pub fn from_dylib(path: &str) -> Result { - let mut handle = unsafe { Container::load(path) }?; - - // Initialize the raw handle - let init_state = Self::initialize_raw_handle(&mut handle)?; - - let metadata = Self::get_meta(&mut handle)?; - - let initalization = Self::initialize_registration(&mut handle)?; - - trace!( - "Loaded plugin {} (Version: {})", - metadata.name, - metadata.version - ); - - Ok(Self { - raw: Arc::new(handle), - meta: metadata, - initialization: Arc::new(initalization), - state: init_state, - }) - } - - /// Builds the Plugin's Substack. - /// - /// Builds the Plugin into a substack, which can then be provided to the Giterated Runtime. - pub fn build_substack(&mut self) -> Result<(), Error> { - todo!() - } - - fn get_meta(handle: &mut Container) -> Result { - let meta = unsafe { handle.plugin_meta() }; - - let name = unsafe { std::slice::from_raw_parts(meta.name, meta.name_len) }; - let version = unsafe { std::slice::from_raw_parts(meta.version, meta.version_len) }; - - let name = std::str::from_utf8(name).unwrap(); - let version = std::str::from_utf8(version).unwrap(); - - Ok(PluginMeta { - name: String::from(name), - version: Version::parse(version).unwrap(), - }) - } - - pub fn initialize_registration( - handle: &mut Container, - ) -> Result { - debug!("Initializing plugin registration..."); - let mut builder = PluginInitializationTable::default(); - - // SAFETY: The lifetime of the returned type is only valid as long - // as the builder that returned it lives - let func_table = unsafe { builder.func_table() }; - - let state = Box::new(PluginInitializationState::new()); - - unsafe { handle.load_initialization_vtable(&func_table) }; - let state = unsafe { handle.initialize_registration(Box::into_raw(state)) }; - - debug!("Plugin handle initialized!"); - Ok(unsafe { *Box::from_raw(state) }) - } - - fn initialize_raw_handle( - handle: &mut Container, - ) -> Result { - debug!("Initializing plugin handle..."); - - let state = unsafe { handle.initialize() }; - - debug!("Plugin handle initialized!"); - - Ok(state) - } -} - -#[derive(Debug, thiserror::Error)] -pub enum CreationError { - #[error("an error occured opening the library {0}")] - LoadingLibrary(#[from] dlopen2::Error), -} - -pub struct PluginSubstackBuilder {} - -#[derive(Default)] -pub struct PluginInitializationState { - pub type_metadata: TypeMetadata, - pub operation_handlers: HashMap, OperationHandlerCallback>, - pub value_getters: HashMap, ValueGetterCallback>, - pub setting_getters: HashMap, SettingGetterCallback>, -} - -impl PluginInitializationState { - pub fn new() -> Self { - Self::default() - } -} - -#[derive(Default)] -pub struct PluginInitializationTable<'a> { - _marker: PhantomData<&'a ()>, -} - -impl<'a> PluginInitializationTable<'a> { - pub unsafe fn func_table(&mut self) -> InitializationVTable { - InitializationVTable { - register_object, - register_operation, - register_setting, - register_value, - operation_handler, - value_getter, - setting_getter, - } - } -} - -unsafe extern "C" fn register_object( - state: *mut PluginInitializationState, - object_kind: &'static str, - vtable: ObjectVtable, -) { - let mut state = Box::from_raw(state); - - state.type_metadata.register_object(object_kind, vtable); - - Box::into_raw(state); -} - -unsafe extern "C" fn register_operation( - state: *mut PluginInitializationState, - object_kind: &'static str, - operation_name: &'static str, - vtable: OperationVTable, -) { - let mut state = Box::from_raw(state); - - state - .type_metadata - .register_operation(object_kind, operation_name, vtable); - - Box::into_raw(state); -} - -unsafe extern "C" fn register_setting( - state: *mut PluginInitializationState, - object_kind: &'static str, - setting_name: &'static str, - vtable: SettingVtable, -) { - let mut state = Box::from_raw(state); - - state - .type_metadata - .register_setting(object_kind, setting_name, vtable); - - Box::into_raw(state); -} - -unsafe extern "C" fn register_value( - state: *mut PluginInitializationState, - object_kind: &'static str, - value_name: &'static str, - vtable: ValueVTable, -) { - let mut state = Box::from_raw(state); - - state - .type_metadata - .register_value(object_kind, value_name, vtable); - - Box::into_raw(state); -} - -unsafe extern "C" fn operation_handler( - state: *mut PluginInitializationState, - object_kind: &'static str, - operation_name: &'static str, - handler: OperationHandlerCallback, -) { - let mut state = Box::from_raw(state); - - trace!("Operation handler for {}::{}", object_kind, operation_name); - - state.operation_handlers.insert( - ObjectOperationPair::new(object_kind, operation_name), - handler, - ); - - Box::into_raw(state); -} - -unsafe extern "C" fn value_getter( - state: *mut PluginInitializationState, - object_kind: &'static str, - value_name: &'static str, - handler: ValueGetterCallback, -) { - let mut state = Box::from_raw(state); - - trace!("Value getter for {}::{}", object_kind, value_name); - - state - .value_getters - .insert(ObjectValuePair::new(object_kind, value_name), handler); - - Box::into_raw(state); -} - -unsafe extern "C" fn setting_getter( - state: *mut PluginInitializationState, - object_kind: &'static str, - setting_name: &'static str, - handler: SettingGetterCallback, -) { - let mut state = Box::from_raw(state); - - trace!("Setting getter for {}::{}", object_kind, setting_name); - - state - .setting_getters - .insert(ObjectSettingPair::new(object_kind, setting_name), handler); - - Box::into_raw(state); -} diff --git a/giterated-plugins/giterated-plugin/src/lib.rs b/giterated-plugins/giterated-plugin/src/lib.rs deleted file mode 100644 index fdc606a..0000000 --- a/giterated-plugins/giterated-plugin/src/lib.rs +++ /dev/null @@ -1,59 +0,0 @@ -pub mod callback; -pub mod handle; -pub mod new_stack; -pub mod vtable; - -#[macro_use] -extern crate tracing; - -use std::{any::Any, fmt::Debug, mem::transmute, ptr::null_mut, sync::Arc}; - -use callback::{OperationHandlerCallback, SettingGetterCallback, ValueGetterCallback}; -use dlopen2::wrapper::WrapperApi; -use giterated_models::{ - object::GiteratedObject, operation::GiteratedOperation, settings::Setting, - value::GiteratedObjectValue, -}; -use handle::PluginInitializationState; -use new_stack::{FFIPluginMeta, PluginState}; - -pub use vtable::{AnyFailure, AnyObject, AnyOperation, AnySuccess, NewAnySetting, NewAnyValue}; -use vtable::{HostVTable, InitializationVTable}; - -#[derive(WrapperApi)] -pub struct GiteratedPluginApi { - plugin_meta: unsafe extern "C" fn() -> FFIPluginMeta, - load_host_vtable: unsafe extern "C" fn(vtable: &HostVTable), - load_initialization_vtable: unsafe extern "C" fn(vtable: &InitializationVTable), - initialize: unsafe extern "C" fn() -> PluginState, - initialize_registration: unsafe extern "C" fn( - init_state: *mut PluginInitializationState, - ) -> *mut PluginInitializationState, -} - -#[repr(C)] -pub struct FFIBox(*mut T); - -impl FFIBox { - pub fn from_box(src: Box) -> Self { - Self(Box::into_raw(src)) - } - - pub fn untyped(self) -> FFIBox<()> { - FFIBox(self.0 as *mut ()) - } -} - -impl AsRef for FFIBox { - fn as_ref(&self) -> &T { - todo!() - } -} - -impl std::ops::Deref for FFIBox { - type Target = T; - - fn deref(&self) -> &Self::Target { - unsafe { self.0.as_ref() }.unwrap() - } -} diff --git a/giterated-plugins/giterated-plugin/src/new_stack/mod.rs b/giterated-plugins/giterated-plugin/src/new_stack/mod.rs deleted file mode 100644 index 26b8d98..0000000 --- a/giterated-plugins/giterated-plugin/src/new_stack/mod.rs +++ /dev/null @@ -1,492 +0,0 @@ -pub mod operation_walker; -pub mod runtime_handler; - -use std::{ - any::type_name, collections::HashMap, fmt::Debug, marker::PhantomData, mem::transmute, - ptr::null_mut, sync::Arc, -}; - -use giterated_models::{ - error::OperationError, instance::Instance, object::GiteratedObject, - operation::GiteratedOperation, -}; -use semver::Version; -use tracing::{debug, debug_span, field::DebugValue, span, trace, trace_span, warn, Level}; - -use crate::{ - callback::{ - OperationHandlerCallback, SettingChangeCallback, SettingGetterCallback, - ValueChangeCallback, ValueGetterCallback, - }, - handle::{PluginHandle, PluginInitializationState}, - vtable::{ObjectVtable, OperationVTable, SettingVtable, ValueVTable}, -}; - -use self::operation_walker::OperationHandlerRules; - -pub struct State(pub S); - -impl std::ops::Deref for State { - type Target = S; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -pub struct OperationState { - pub our_instance: Instance, -} - -#[derive(Default)] -pub struct TypeMetadata { - pub objects: HashMap<&'static str, ObjectVtable>, - pub operations: HashMap, OperationVTable>, - pub settings: HashMap, SettingVtable>, - pub values: HashMap, ValueVTable>, -} - -impl TypeMetadata { - pub fn register_object(&mut self, object_kind: &'static str, vtable: ObjectVtable) { - trace!("Registering type metadata for {}", object_kind); - - self.objects.insert(object_kind, vtable); - } - - pub fn register_operation( - &mut self, - object_kind: &'static str, - operation_name: &'static str, - vtable: OperationVTable, - ) { - trace!( - "Registering operation metadata for {}::{}", - object_kind, - operation_name - ); - - self.operations.insert( - ObjectOperationPair { - object_kind, - operation_name, - }, - vtable, - ); - } - - pub fn register_setting( - &mut self, - object_kind: &'static str, - setting_name: &'static str, - vtable: SettingVtable, - ) { - trace!("Registering setting {}::{}", object_kind, setting_name); - - self.settings.insert( - ObjectSettingPair { - object_kind, - setting_name, - }, - vtable, - ); - } - - pub fn register_value( - &mut self, - object_kind: &'static str, - value_name: &'static str, - vtable: ValueVTable, - ) { - trace!("Registering value {}::{}", object_kind, value_name); - - self.values.insert( - ObjectValuePair { - object_kind, - value_name, - }, - vtable, - ); - } -} - -#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] -pub struct ObjectOperationPair<'s> { - pub object_kind: &'s str, - pub operation_name: &'s str, -} - -impl<'s> ObjectOperationPair<'s> { - pub fn new(object_kind: &'s str, operation_name: &'s str) -> Self { - Self { - object_kind, - operation_name, - } - } -} - -#[derive(Clone, Copy, Hash, PartialEq, Eq, Debug)] -pub struct ObjectSettingPair<'s> { - pub object_kind: &'s str, - pub setting_name: &'s str, -} - -impl<'s> ObjectSettingPair<'s> { - pub fn new(object_kind: &'s str, setting_name: &'s str) -> Self { - Self { - object_kind, - setting_name, - } - } -} - -#[derive(Clone, Copy, Hash, PartialEq, Eq, Debug)] -pub struct ObjectValuePair<'s> { - pub object_kind: &'s str, - pub value_name: &'s str, -} - -impl<'s> ObjectValuePair<'s> { - pub fn new(object_kind: &'s str, value_name: &'s str) -> Self { - Self { - object_kind, - value_name, - } - } -} - -#[derive(Clone, Copy)] -#[repr(C)] -pub struct PluginState { - pub inner: *mut (), -} - -impl PluginState { - pub unsafe fn transmute_owned(&mut self) -> Box { - Box::from_raw(self.inner as *mut T) - } - - pub unsafe fn transmute_ref(&self) -> &T { - let ptr: *const T = transmute(self.inner); - - ptr.as_ref().unwrap() - } -} - -impl PluginState { - pub fn null() -> Self { - Self { inner: null_mut() } - } -} - -pub struct Runtime { - plugins: Vec<(PluginMeta, PluginHandle)>, - handlers: RuntimeHandlers, - _marker: PhantomData, -} - -impl Runtime { - pub fn new() -> Self { - Self { - plugins: vec![], - handlers: RuntimeHandlers::default(), - _marker: PhantomData, - } - } - - pub fn insert_plugin(&mut self, mut plugin: PluginHandle) { - let _guard = debug_span!("inserting plugin", meta = debug(&plugin.meta)).entered(); - - for (pair, callback) in &plugin.initialization.operation_handlers { - let _guard = - trace_span!("processing operation handler callbacks", pair = debug(pair)).entered(); - - if self - .handlers - .operation_handlers - .insert(*pair, (RuntimeDomain::from_plugin(&plugin), *callback)) - .is_some() - { - warn!("Warning! Insertion of handler for overwrote a previous handler.") - } - - trace!("Insertion of operation handler successful") - } - - for (pair, callback) in &plugin.initialization.value_getters { - let _guard = - trace_span!("processing value getter callbacks", pair = debug(pair)).entered(); - - if self - .handlers - .value_getters - .insert(*pair, (RuntimeDomain::from_plugin(&plugin), *callback)) - .is_some() - { - warn!("Warning! Insertion of handler for overwrote a previous handler.") - } - - trace!("Insertion of operation handler successful") - } - - for (pair, callback) in &plugin.initialization.setting_getters { - let _guard = - trace_span!("processing setting getter callbacks", pair = debug(pair)).entered(); - - if self - .handlers - .setting_getters - .insert(*pair, (RuntimeDomain::from_plugin(&plugin), *callback)) - .is_some() - { - warn!("Warning! Insertion of setting handler for overwrote a previous handler.") - } - - trace!("Insertion of setting handler successful") - } - } - - pub fn handle( - &self, - object_kind: &str, - operation_name: &str, - object: &str, - operation_payload: &[u8], - ) -> Result<(), OperationError<()>> { - // let rules = self.handlers.handle_operation(object_kind, operation_name); - - // rules.handle(object, operation_payload) - - todo!() - } - - pub fn handle_typed>( - &self, - object: O, - operation: D, - ) -> Result> { - todo!() - } -} - -#[derive(Default)] -pub struct RuntimeHandlers { - operation_handlers: - HashMap, (RuntimeDomain, OperationHandlerCallback)>, - value_getters: HashMap, (RuntimeDomain, ValueGetterCallback)>, - setting_getters: HashMap, (RuntimeDomain, SettingGetterCallback)>, - value_change: HashMap, (RuntimeDomain, ValueChangeCallback)>, - setting_change: HashMap, (RuntimeDomain, SettingChangeCallback)>, -} - -unsafe impl Send for RuntimeHandlers {} -unsafe impl Sync for RuntimeHandlers {} - -impl RuntimeHandlers { - pub fn operation_handler( - &mut self, - pair: ObjectOperationPair<'static>, - handler: OperationHandlerCallback, - domain: RuntimeDomain, - ) { - trace!( - "Inserting operation handler for {}::{}", - pair.object_kind, - pair.operation_name - ); - - // There can only be one handler per operation (at least for now?), send a warning if - // a newly registered handler overwrites the previous handler. - if self - .operation_handlers - .insert(pair, (domain, handler)) - .is_some() - { - debug!("Warning! A newly inserted operation handler for {}::{} overwrites a previous handler.", pair.object_kind, pair.operation_name); - } - } - - pub fn value_getter( - &mut self, - pair: ObjectValuePair<'static>, - handler: ValueGetterCallback, - domain: RuntimeDomain, - ) { - trace!( - "Inserting value getter for {}::{}", - pair.object_kind, - pair.value_name - ); - - if self.value_getters.insert(pair, (domain, handler)).is_some() { - debug!( - "Warning! A newly inserted value getter for {}::{} overwrites a previous handler.", - pair.object_kind, pair.value_name - ); - } - } - - pub fn setting_getter( - &mut self, - pair: ObjectSettingPair<'static>, - handler: SettingGetterCallback, - domain: RuntimeDomain, - ) { - trace!( - "Inserting setting getter for {}::{}", - pair.object_kind, - pair.setting_name - ); - - if self - .setting_getters - .insert(pair, (domain, handler)) - .is_some() - { - debug!("Warning! A newly inserted setting getter for {}::{} overwrites a previous handler.", pair.object_kind, pair.setting_name); - } - } - - pub fn value_change( - &mut self, - pair: ObjectValuePair<'static>, - handler: ValueChangeCallback, - domain: RuntimeDomain, - ) { - trace!( - "Inserting value change handler for {}::{}", - pair.object_kind, - pair.value_name - ); - - if self.value_change.insert(pair, (domain, handler)).is_some() { - debug!("Warning! A newly inserted value change handler for {}::{} overwrites a previous handler.", pair.object_kind, pair.value_name); - panic!("Not intended"); - } - } - - pub fn setting_change( - &mut self, - pair: ObjectSettingPair<'static>, - handler: SettingChangeCallback, - domain: RuntimeDomain, - ) { - trace!( - "Inserting setting change handler for {}::{}", - pair.object_kind, - pair.setting_name - ); - - if self - .setting_change - .insert(pair, (domain, handler)) - .is_some() - { - debug!("Warning! A newly inserted setting change handler for {}::{} overwrites a previous handler.", pair.object_kind, pair.setting_name); - panic!("Not intended"); - } - } -} - -impl RuntimeHandlers { - pub fn handle_operation<'o>( - &'o self, - object_kind: &'o str, - operation_name: &'o str, - ) -> OperationHandlerRules<'o> { - OperationHandlerRules::new(object_kind, operation_name, self) - } -} - -pub struct RuntimeDomain { - plugin: PluginHandle, -} - -impl RuntimeDomain { - pub fn from_plugin(plugin: &PluginHandle) -> Self { - Self { - plugin: plugin.clone(), - } - } - - pub fn object_vtable(&self, object_kind: &str) -> Option { - self.plugin - .initialization - .type_metadata - .objects - .get(object_kind) - .copied() - } - - pub fn operation_vtable( - &self, - object_kind: &str, - operation_name: &str, - ) -> Option { - self.plugin - .initialization - .type_metadata - .operations - .get(&ObjectOperationPair::new(object_kind, operation_name)) - .copied() - } - - pub fn setting_vtable(&self, object_kind: &str, setting_name: &str) -> Option { - self.plugin - .initialization - .type_metadata - .settings - .get(&ObjectSettingPair::new(object_kind, setting_name)) - .copied() - } - - pub fn value_vtable(&self, object_kind: &str, value_name: &str) -> Option { - self.plugin - .initialization - .type_metadata - .values - .get(&ObjectValuePair::new(object_kind, value_name)) - .copied() - } -} - -#[derive(Clone, Debug)] -pub struct PluginMeta { - pub name: String, - pub version: Version, -} - -#[repr(C)] -pub struct FFIPluginMeta { - pub name: *const u8, - pub name_len: usize, - pub version: *const u8, - pub version_len: usize, -} - -pub struct RuntimePlugin { - handle: PluginHandle, - type_metadata: Arc, -} - -impl RuntimePlugin { - pub fn plugin_meta(&self) -> PluginMeta { - let meta = unsafe { self.handle.raw.plugin_meta() }; - - let name = unsafe { std::slice::from_raw_parts(meta.name, meta.name_len) }; - let version = unsafe { std::slice::from_raw_parts(meta.version, meta.version_len) }; - - let name = std::str::from_utf8(name).unwrap(); - let version = std::str::from_utf8(version).unwrap(); - - PluginMeta { - name: String::from(name), - version: Version::parse(version).unwrap(), - } - } -} - -pub enum HandlerError { - Failure(()), - Internal(()), - Unhandled, -} diff --git a/giterated-plugins/giterated-plugin/src/new_stack/operation_walker.rs b/giterated-plugins/giterated-plugin/src/new_stack/operation_walker.rs deleted file mode 100644 index bb9f6f6..0000000 --- a/giterated-plugins/giterated-plugin/src/new_stack/operation_walker.rs +++ /dev/null @@ -1,232 +0,0 @@ -use crate::callback::RuntimeState; -use giterated_models::{operation::GiteratedOperation, settings::GetSetting, value::GetValue}; -use tracing::{debug_span, trace, trace_span}; - -use crate::new_stack::{ObjectOperationPair, PluginState}; - -use super::{HandlerError, ObjectSettingPair, ObjectValuePair, RuntimeHandlers}; - -/// A wrapper for operation handling that enforces handling rules. -/// -/// # Handler Resolution -/// In order, handler resolution will be attempted as follows: -/// -/// | Index | object_kind | operation_kind | Special Case? | -/// |-------|-------------|-----------------|---------------| -/// | 1 | `any` | `typed` | No | -/// | 2 | `typed` | `any` | No | -/// | 3 | `any` | `any` | No | -/// | 4 | `any` | `GetValue` | ⚠️ Yes ⚠️ | -/// | 5 | `any` | `GetSetting` | ⚠️ Yes ⚠️ | -/// | 6 | `any` | `SetSetting` | ⚠️ Yes ⚠️ | -/// | 7 | `any` | `ObjectRequest` | ⚠️ Yes ⚠️ | -/// | 8 | `typed` | `typed` | No | -pub struct OperationHandlerRules<'a> { - object_kind: &'a str, - operation_name: &'a str, - handlers: &'a RuntimeHandlers, -} - -impl<'o> OperationHandlerRules<'o> { - pub fn new( - object_kind: &'o str, - operation_name: &'o str, - handlers: &'o RuntimeHandlers, - ) -> Self { - Self { - object_kind, - operation_name, - handlers, - } - } - - pub fn handle( - &self, - runtime_state: &RuntimeState, - object: &str, - operation_payload: &[u8], - ) -> Result<(), HandlerError> { - // object_kind: `any` - // operation_kind: `typed` - if let Some(handler) = self - .handlers - .operation_handlers - .get(&ObjectOperationPair::new("any", self.operation_name)) - { - todo!() - } - - // object_kind: `typed` - // operation_kind: `any` - if let Some(handler) = self - .handlers - .operation_handlers - .get(&ObjectOperationPair::new(self.object_kind, "any")) - {} - - // object_kind: `any` - // operation_kind: `any` - if let Some(handler) = self - .handlers - .operation_handlers - .get(&ObjectOperationPair::new("any", "any")) - {} - - // ⚠️ Special Case ⚠️ - // object_kind: `any` - // operation_kind: `GetValue` - if self.operation_name == "get_value" { - let operation: GetValue = serde_json::from_slice(operation_payload).unwrap(); - let _guard = trace_span!( - "get_value handler resolving", - object = self.object_kind, - value = operation.value_name - ) - .entered(); - - if let Some((domain, callback)) = self.handlers.value_getters.get( - &ObjectValuePair::new(self.object_kind, &operation.value_name), - ) { - trace_span!( - "get_value handler.", - object = self.object_kind, - value_name = operation.value_name - ); - - let object_vtable = domain - .object_vtable(self.object_kind) - .ok_or_else(|| HandlerError::Unhandled)?; - trace!("Resolved object vtable for {}", self.object_kind); - - let value_vtable = domain - .value_vtable(self.object_kind, &operation.value_name) - .ok_or_else(|| HandlerError::Unhandled)?; - trace!( - "Resolved value vtable for {}::{}", - self.object_kind, - operation.value_name - ); - - let object = unsafe { (object_vtable.from_str)(object) } - .map_err(|_| HandlerError::Internal(()))?; - - let _guard = debug_span!("get_value handler"); - - let result = - unsafe { (callback.func)(callback.callback_ptr, &domain.plugin.state, object) }; - - // Todo deser - - return Ok(()); - } else { - trace!("Failed to resolve handler."); - } - } - - // ⚠️ Special Case ⚠️ - // object_kind: `any` - // operation_kind: `GetSetting` - if self.operation_name == "get_setting" { - let operation: GetSetting = serde_json::from_slice(operation_payload).unwrap(); - let _guard = trace_span!( - "get_setting handler resolving", - object = self.object_kind, - setting = operation.setting_name - ) - .entered(); - - if let Some((domain, callback)) = self.handlers.setting_getters.get( - &ObjectSettingPair::new(self.object_kind, &operation.setting_name), - ) { - trace_span!( - "get_setting handler.", - object = self.object_kind, - setting_name = operation.setting_name - ); - - let object_vtable = domain - .object_vtable(self.object_kind) - .ok_or_else(|| HandlerError::Unhandled)?; - trace!("Resolved object vtable for {}", self.object_kind); - - let setting_vtable = domain - .setting_vtable(self.object_kind, &operation.setting_name) - .ok_or_else(|| HandlerError::Unhandled)?; - trace!("Resolved setting vtable for {}", operation.setting_name); - - let object = unsafe { (object_vtable.from_str)(object) } - .map_err(|_| HandlerError::Internal(()))?; - - let _guard = debug_span!("get_value handler"); - - let result = - unsafe { (callback.func)(callback.callback_ptr, &domain.plugin.state, object) }; - - // Todo deser - - return Ok(()); - } else { - trace!("Failed to resolve handler."); - } - } - - // ⚠️ Special Case ⚠️ - // object_kind: `any` - // operation_kind: `SetSetting` - if self.operation_name == "set_setting" {} - - // ⚠️ Special Case ⚠️ - // object_kind: `any` - // operation_kind: `ObjectRequest` - if self.operation_name == "object_request" {} - - // object_kind: `typed` - // operation_kind: `typed` - if let Some((domain, handler)) = - self.handlers - .operation_handlers - .get(&ObjectOperationPair::new( - self.object_kind, - self.operation_name, - )) - { - let _guard = trace_span!("typed_typed handler resolved").entered(); - - let object_vtable = domain - .object_vtable(self.object_kind) - .ok_or_else(|| HandlerError::Unhandled)?; - trace!("Resolved object vtable for {}", self.object_kind); - - let operation_vtable = domain - .operation_vtable(self.object_kind, self.operation_name) - .ok_or_else(|| HandlerError::Unhandled)?; - trace!( - "Resolved operation vtable for {}::{}", - self.object_kind, - self.operation_name - ); - - let object = unsafe { (object_vtable.from_str)(object) } - .map_err(|_| HandlerError::Internal(()))?; - let operation = unsafe { (operation_vtable.deserialize)(operation_payload) } - .map_err(|_| HandlerError::Internal(()))?; - trace!("Parsed operation data"); - - let _guard = debug_span!("calling handler").entered(); - let result = unsafe { - (handler.func)( - handler.callback_ptr, - runtime_state, - &domain.plugin.state, - object, - operation, - ) - }; - - // todo - return Ok(()); - } - - Err(HandlerError::Unhandled) - } -} diff --git a/giterated-plugins/giterated-plugin/src/new_stack/runtime_handler.rs b/giterated-plugins/giterated-plugin/src/new_stack/runtime_handler.rs deleted file mode 100644 index 178bff6..0000000 --- a/giterated-plugins/giterated-plugin/src/new_stack/runtime_handler.rs +++ /dev/null @@ -1,76 +0,0 @@ -use std::fmt::Debug; -use std::sync::Arc; - -use giterated_models::{ - error::OperationError, - object::{GiteratedObject, Object, ObjectRequestError}, - object_backend::ObjectBackend, - operation::GiteratedOperation, -}; - -use crate::vtable::{AnyFailure, AnySuccess, OperationVTable}; - -use super::PluginState; - -#[derive(Clone)] -pub struct RuntimeHandle { - inner: Arc, -} - -impl RuntimeHandle { - pub async fn handle_serialized( - &self, - operation_name: &str, - object: &str, - operation: &[u8], - ) -> Result, OperationError>> { - todo!() - } -} - -#[repr(C)] -struct RuntimeHandleInner { - state: PluginState, - handle_serialized: unsafe extern "C" fn( - object_kind: &str, - operation_name: &str, - object: &str, - operation_payload: &[u8], - ) -> HandlerResult, -} - -unsafe impl Send for RuntimeHandleInner {} -unsafe impl Sync for RuntimeHandleInner {} - -#[repr(C)] -struct HandlerResult { - operation_vtable: OperationVTable, - result: Result>, -} - -#[async_trait::async_trait(?Send)] -impl ObjectBackend for RuntimeHandle { - async fn object_operation( - &self, - object: O, - operation: &str, - payload: D, - operation_state: &S, - ) -> Result> - where - O: GiteratedObject + Debug + 'static, - D: GiteratedOperation + Debug + 'static, - D::Success: Clone, - D::Failure: Clone, - { - todo!() - } - - async fn get_object( - &self, - object_str: &str, - operation_state: &S, - ) -> Result, OperationError> { - todo!() - } -} diff --git a/giterated-plugins/giterated-plugin/src/vtable/host.rs b/giterated-plugins/giterated-plugin/src/vtable/host.rs deleted file mode 100644 index f1204b2..0000000 --- a/giterated-plugins/giterated-plugin/src/vtable/host.rs +++ /dev/null @@ -1,64 +0,0 @@ -use super::{ObjectVtable, OperationVTable, SettingVtable, ValueVTable}; -use crate::{ - callback::{OperationHandlerCallback, SettingGetterCallback, ValueGetterCallback}, - handle::PluginInitializationState, -}; -use std::fmt::Debug; - -#[repr(C)] -pub struct HostVTable {} - -#[repr(C)] -#[derive(Clone, Copy)] -pub struct InitializationVTable { - pub register_object: - unsafe extern "C" fn(*mut PluginInitializationState, &'static str, ObjectVtable), - pub register_operation: unsafe extern "C" fn( - *mut PluginInitializationState, - &'static str, - &'static str, - OperationVTable, - ), - pub register_setting: unsafe extern "C" fn( - *mut PluginInitializationState, - &'static str, - &'static str, - SettingVtable, - ), - pub register_value: unsafe extern "C" fn( - *mut PluginInitializationState, - &'static str, - &'static str, - ValueVTable, - ), - - pub operation_handler: unsafe extern "C" fn( - *mut PluginInitializationState, - &'static str, - &'static str, - OperationHandlerCallback, - ), - - pub value_getter: unsafe extern "C" fn( - *mut PluginInitializationState, - &'static str, - &'static str, - ValueGetterCallback, - ), - - pub setting_getter: unsafe extern "C" fn( - *mut PluginInitializationState, - &'static str, - &'static str, - SettingGetterCallback, - ), -} - -impl Debug for InitializationVTable { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("InitializationVTable").finish() - } -} - -unsafe impl Sync for InitializationVTable {} -unsafe impl Send for InitializationVTable {} diff --git a/giterated-plugins/giterated-plugin/src/vtable/mod.rs b/giterated-plugins/giterated-plugin/src/vtable/mod.rs deleted file mode 100644 index 543839a..0000000 --- a/giterated-plugins/giterated-plugin/src/vtable/mod.rs +++ /dev/null @@ -1,13 +0,0 @@ -//! Giterated's VTable System -//! -//! Docs here? :) -mod setting; -pub use setting::*; -mod operation; -pub use operation::*; -mod object; -pub use object::*; -mod value; -pub use value::*; -mod host; -pub use host::*; diff --git a/giterated-plugins/giterated-plugin/src/vtable/object.rs b/giterated-plugins/giterated-plugin/src/vtable/object.rs deleted file mode 100644 index dc00436..0000000 --- a/giterated-plugins/giterated-plugin/src/vtable/object.rs +++ /dev/null @@ -1,109 +0,0 @@ -use std::mem::transmute; - -use giterated_models::object::GiteratedObject; - -use crate::FFIBox; - -#[derive(Clone, Copy)] -#[repr(C)] -pub struct ObjectVtable { - object_kind: *const u8, - object_kind_len: usize, - pub to_str: unsafe extern "C" fn(AnyObject) -> FFIBox, - pub from_str: unsafe extern "C" fn(&str) -> Result>, - pub home_uri: unsafe extern "C" fn(&AnyObject) -> FFIBox, - pub is_same: unsafe extern "C" fn(AnyObject) -> bool, -} - -impl ObjectVtable { - pub fn new() -> Self { - let object_kind = T::object_kind().as_ptr(); - let object_kind_len = T::object_kind().len(); - - Self { - to_str: T::to_str, - from_str: T::from_str, - home_uri: T::home_uri, - is_same: T::is_same, - object_kind, - object_kind_len, - } - } - - pub fn kind(&self) -> &'static str { - let slice = unsafe { std::slice::from_raw_parts(self.object_kind, self.object_kind_len) }; - - std::str::from_utf8(slice).unwrap() - } -} - -pub trait IntoObjectVTable { - fn object_kind() -> &'static str; - unsafe extern "C" fn to_str(this: AnyObject) -> FFIBox; - unsafe extern "C" fn from_str(src: &str) -> Result>; - unsafe extern "C" fn home_uri(this: &AnyObject) -> FFIBox; - unsafe extern "C" fn is_same(other: AnyObject) -> bool; -} - -impl IntoObjectVTable for T { - unsafe extern "C" fn to_str(mut this: AnyObject) -> FFIBox { - let this: &Box = this.transmute_ref(); - - let result = this.to_string(); - - FFIBox::from_box(result.into_boxed_str()) - } - - unsafe extern "C" fn from_str(src: &str) -> Result> { - let result = T::from_object_str(src).unwrap(); - - let any_object = AnyObject::new(result); - - Ok(any_object) - } - - unsafe extern "C" fn home_uri(this: &AnyObject) -> FFIBox { - todo!() - } - - unsafe extern "C" fn is_same(other: AnyObject) -> bool { - todo!() - } - - fn object_kind() -> &'static str { - todo!() - } -} - -#[repr(C)] -pub struct AnyObject { - /// A pointer to the plugin-local object type. We are not capable of - /// knowing what this type is, we use the provided vtable. - inner: FFIBox<()>, - vtable: ObjectVtable, -} - -impl AnyObject { - pub fn new(inner: T) -> Self { - Self { - inner: FFIBox::from_box(Box::new(inner)).untyped(), - vtable: ObjectVtable::new::(), - } - } - - pub fn vtable(&self) -> ObjectVtable { - self.vtable - } -} - -impl AnyObject { - pub unsafe fn transmute_owned(&mut self) -> Box { - Box::from_raw(self.inner.0 as *mut T) - } - - pub unsafe fn transmute_ref(&self) -> &T { - let ptr: *const T = transmute(self.inner.0); - - ptr.as_ref().unwrap() - } -} diff --git a/giterated-plugins/giterated-plugin/src/vtable/operation.rs b/giterated-plugins/giterated-plugin/src/vtable/operation.rs deleted file mode 100644 index 5ecfa36..0000000 --- a/giterated-plugins/giterated-plugin/src/vtable/operation.rs +++ /dev/null @@ -1,136 +0,0 @@ -use std::mem::transmute; - -use giterated_models::{object::GiteratedObject, operation::GiteratedOperation}; - -use crate::FFIBox; - -use super::AnyObject; - -#[derive(Clone, Copy)] -#[repr(C)] -pub struct OperationVTable { - operation_kind: *const u8, - operation_kind_len: usize, - pub serialize: unsafe extern "C" fn(&AnyOperation) -> Result, ()>, - pub deserialize: unsafe extern "C" fn(&[u8]) -> Result, - pub is_same: unsafe extern "C" fn(AnyObject) -> bool, - pub serialize_success: unsafe extern "C" fn(()) -> Result, ()>, - pub serialize_failure: unsafe extern "C" fn(()) -> Result, ()>, - pub deserialize_success: unsafe extern "C" fn(&[u8]) -> Result, - pub deserialize_failure: unsafe extern "C" fn(&[u8]) -> Result, -} - -impl OperationVTable { - pub fn new>() -> Self { - let operation_kind = T::operation_kind().as_ptr(); - let operation_kind_len = T::operation_kind().len(); - - Self { - serialize: T::serialize, - deserialize: T::deserialize, - is_same: T::is_same, - serialize_success: T::serialize_success, - serialize_failure: T::serialize_failure, - deserialize_success: T::deserialize_success, - deserialize_failure: T::deserialize_failure, - operation_kind, - operation_kind_len, - } - } - - pub fn kind(&self) -> &'static str { - let slice = - unsafe { std::slice::from_raw_parts(self.operation_kind, self.operation_kind_len) }; - - std::str::from_utf8(slice).unwrap() - } -} - -pub struct AnyOperation { - /// A pointer to the plugin-local object type. We are not capable of - /// knowing what this type is, we use the provided vtable. - inner: FFIBox<()>, - vtable: OperationVTable, -} - -impl AnyOperation { - pub unsafe fn transmute_owned(&mut self) -> Box { - Box::from_raw(self.inner.0 as *mut T) - } - - pub unsafe fn transmute_ref(&self) -> &T { - let ptr: *const T = transmute(self.inner.0); - - ptr.as_ref().unwrap() - } - - pub fn vtable(&self) -> OperationVTable { - self.vtable - } -} - -#[repr(C)] -pub struct AnySuccess { - inner: FFIBox<()>, - vtable: OperationVTable, -} - -#[repr(C)] -pub struct AnyFailure { - inner: FFIBox<()>, - vtable: OperationVTable, -} - -pub trait IntoOperationVTable { - fn operation_kind() -> &'static str; - unsafe extern "C" fn serialize(this: &AnyOperation) -> Result, ()>; - unsafe extern "C" fn deserialize(src: &[u8]) -> Result; - unsafe extern "C" fn is_same(this: AnyObject) -> bool; - unsafe extern "C" fn serialize_success(success: ()) -> Result, ()>; - unsafe extern "C" fn serialize_failure(failure: ()) -> Result, ()>; - unsafe extern "C" fn deserialize_success(src: &[u8]) -> Result; - unsafe extern "C" fn deserialize_failure(src: &[u8]) -> Result; -} - -impl IntoOperationVTable for D -where - D: GiteratedOperation, - O: GiteratedObject, -{ - unsafe extern "C" fn serialize(this: &AnyOperation) -> Result, ()> { - todo!() - } - - unsafe extern "C" fn deserialize(src: &[u8]) -> Result { - let deserialized: D = serde_json::from_slice(src).unwrap(); - - Ok(AnyOperation { - inner: FFIBox::from_box(Box::new(deserialized)).untyped(), - vtable: OperationVTable::new::(), - }) - } - - unsafe extern "C" fn is_same(this: AnyObject) -> bool { - todo!() - } - - unsafe extern "C" fn serialize_success(success: ()) -> Result, ()> { - todo!() - } - - unsafe extern "C" fn serialize_failure(failure: ()) -> Result, ()> { - todo!() - } - - unsafe extern "C" fn deserialize_success(src: &[u8]) -> Result { - todo!() - } - - unsafe extern "C" fn deserialize_failure(src: &[u8]) -> Result { - todo!() - } - - fn operation_kind() -> &'static str { - todo!() - } -} diff --git a/giterated-plugins/giterated-plugin/src/vtable/setting.rs b/giterated-plugins/giterated-plugin/src/vtable/setting.rs deleted file mode 100644 index 4fd768f..0000000 --- a/giterated-plugins/giterated-plugin/src/vtable/setting.rs +++ /dev/null @@ -1,66 +0,0 @@ -use std::mem::transmute; - -use giterated_models::settings::Setting; - -use crate::FFIBox; - -#[repr(C)] -pub struct NewAnySetting { - /// A pointer to the plugin-local object type. We are not capable of - /// knowing what this type is, we use the provided vtable. - inner: FFIBox<()>, - vtable: SettingVtable, -} - -impl NewAnySetting { - pub fn new(value: V) -> Self { - Self { - inner: FFIBox::from_box(Box::new(value)).untyped(), - vtable: SettingVtable::new::(), - } - } - - pub unsafe fn transmute_owned(self) -> Box { - Box::from_raw(self.inner.0 as *mut T) - } - - pub unsafe fn transmute_ref(&self) -> &T { - let ptr: *const T = transmute(self.inner.0); - - ptr.as_ref().unwrap() - } -} - -#[derive(Clone, Copy)] -#[repr(C)] -pub struct SettingVtable { - pub deserialize: unsafe extern "C" fn(&[u8]) -> Result<(), ()>, - pub serialize: unsafe extern "C" fn(NewAnySetting) -> Result, ()>, -} - -impl SettingVtable { - pub fn new() -> Self { - Self { - deserialize: T::deserialize, - serialize: T::serialize, - } - } -} - -pub trait IntoSettingVTable { - unsafe extern "C" fn deserialize(src: &[u8]) -> Result<(), ()>; - unsafe extern "C" fn serialize(this: NewAnySetting) -> Result, ()>; -} - -impl IntoSettingVTable for S -where - S: Setting, -{ - unsafe extern "C" fn deserialize(src: &[u8]) -> Result<(), ()> { - todo!() - } - - unsafe extern "C" fn serialize(this: NewAnySetting) -> Result, ()> { - todo!() - } -} diff --git a/giterated-plugins/giterated-plugin/src/vtable/value.rs b/giterated-plugins/giterated-plugin/src/vtable/value.rs deleted file mode 100644 index ba9eb93..0000000 --- a/giterated-plugins/giterated-plugin/src/vtable/value.rs +++ /dev/null @@ -1,67 +0,0 @@ -use giterated_models::{object::GiteratedObject, value::GiteratedObjectValue}; - -use crate::FFIBox; - -#[repr(C)] -pub struct NewAnyValue { - /// A pointer to the plugin-local object type. We are not capable of - /// knowing what this type is, we use the provided vtable. - inner: FFIBox<()>, - vtable: ValueVTable, -} - -impl NewAnyValue { - pub fn new>(value: V) -> Self { - NewAnyValue { - inner: FFIBox::from_box(Box::new(value)).untyped(), - vtable: ValueVTable::new::(), - } - } -} - -#[derive(Clone, Copy)] -#[repr(C)] -pub struct ValueVTable { - pub deserialize: unsafe extern "C" fn(&[u8]) -> Result, - pub serialize: unsafe extern "C" fn(NewAnyValue) -> Result, ()>, -} - -impl ValueVTable { - pub fn new>() -> Self { - Self { - deserialize: T::deserialize, - serialize: T::serialize, - } - } -} - -pub trait IntoValueVTable { - unsafe extern "C" fn deserialize(src: &[u8]) -> Result; - unsafe extern "C" fn serialize(this: NewAnyValue) -> Result, ()>; -} - -impl IntoValueVTable for V -where - O: GiteratedObject, - V: GiteratedObjectValue, -{ - unsafe extern "C" fn deserialize(src: &[u8]) -> Result { - let _guard = trace_span!( - "deserialize value", - object = O::object_name(), - value = V::value_name() - ); - - trace!("Deserializing"); - let deserialized: V = serde_json::from_slice(src).unwrap(); - - Ok(NewAnyValue { - inner: FFIBox::from_box(Box::new(deserialized)).untyped(), - vtable: ValueVTable::new::(), - }) - } - - unsafe extern "C" fn serialize(this: NewAnyValue) -> Result, ()> { - todo!() - } -} diff --git a/giterated-plugins/giterated-protocol/Cargo.toml b/giterated-plugins/giterated-protocol/Cargo.toml deleted file mode 100644 index 9196113..0000000 --- a/giterated-plugins/giterated-protocol/Cargo.toml +++ /dev/null @@ -1,30 +0,0 @@ -[package] -name = "giterated-protocol" -version = "0.1.0" -edition = "2021" - -[lib] -name = "giterated_protocol" -path = "src/lib.rs" -crate-type = ["dylib", "rlib"] - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -giterated-plugin = { path = "../giterated-plugin" } -giterated-models = { path = "../../giterated-models" } -serde = { version = "1.0", features = [ "derive" ]} -anyhow = "1" -giterated-plugin-sys = { path = "../giterated-plugin-sys" } -toml = { version = "0.8" } -tracing = "0.1" -tracing-subscriber = "0.3" -serde_json = "1.0" -rand = "0.8" -rsa = {version = "0.9", features = ["sha2"]} -tokio-tungstenite = { version = "0.20" } -tokio = { version = "1.32.0", features = ["full"] } -thiserror = "1" -bincode = "1.3" -futures-util = "0.3" -async-trait = "0.1" \ No newline at end of file diff --git a/giterated-plugins/giterated-protocol/src/handlers.rs b/giterated-plugins/giterated-protocol/src/handlers.rs deleted file mode 100644 index 02178ee..0000000 --- a/giterated-plugins/giterated-protocol/src/handlers.rs +++ /dev/null @@ -1,255 +0,0 @@ -use std::{fmt::Display, net::SocketAddr, str::FromStr, sync::Arc}; - -use anyhow::Error; -use futures_util::{SinkExt, StreamExt}; -use giterated_models::{ - error::{NetworkOperationError, OperationError}, - instance::Instance, - object::GiteratedObject, - operation::GiteratedOperation, -}; -use giterated_plugin::{ - new_stack::{runtime_handler::RuntimeHandle, Runtime}, - AnyFailure, AnyObject, AnyOperation, AnySuccess, -}; -use serde::{Deserialize, Serialize}; -use tokio::net::TcpStream; -use tokio_tungstenite::{connect_async, tungstenite::Message, MaybeTlsStream, WebSocketStream}; - -use crate::{Authenticated, GiteratedMessage, ProtocolState, RemoteError}; - -pub async fn handle_network_operation( - state: ProtocolState, - object: NetworkedObject, - operation: NetworkedOperation, - runtime: RuntimeHandle, -) -> Result, OperationError>> { - trace!("Handle network operation {}", operation.name); - - runtime - .handle_serialized(&object.0, &operation.name, &operation.payload) - .await -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct NetworkedObject(pub String); - -impl FromStr for NetworkedObject { - type Err = (); - - fn from_str(s: &str) -> Result { - Ok(NetworkedObject(s.to_string())) - } -} - -impl Display for NetworkedObject { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(&self.0) - } -} - -impl GiteratedObject for NetworkedObject { - fn object_name() -> &'static str { - "networked_object" - } - - fn from_object_str(_object_str: &str) -> Result { - todo!() - } - - fn home_uri(&self) -> String { - todo!() - } -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct NetworkedOperation { - pub name: String, - pub payload: Vec, -} - -impl NetworkedOperation { - pub fn new(name: String, payload: Vec) -> Self { - Self { name, payload } - } -} - -impl GiteratedOperation for NetworkedOperation { - type Success = Vec; - - type Failure = Vec; - - fn operation_name() -> &'static str { - "networked_operation" - } -} - -/// Handler which will attempt to resolve any operation that doesn't resolve locally -/// against a remote instance. -pub async fn try_handle_with_remote( - state: ProtocolState, - object: AnyObject, - operation: AnyOperation, -) -> Result> { - // if object.is::() { - // return Err(OperationError::Unhandled); - // } - // trace!( - // "Try handling object operation {}::{} with remote", - // object.kind(), - // operation.kind().operation_name - // ); - // TODO: - // Ideally we support pass-through on object types that aren't used locally. - // For now, we aren't worrying about that. - let object_meta = object.vtable().clone(); - - let operation_meta = operation.vtable().clone(); - - // trace!( - // "Serializing with {}::{}", - // operation.kind().object_name, - // operation.kind().operation_name - // ); - - let object_home_uri = unsafe { (object_meta.home_uri)(&object) }; - - if let Some(home_uri) = state.home_uri { - if &home_uri == object_home_uri.as_ref() { - // This isn't a remote request, requests aren't supposed to hit this layer - // if they're not remote. - // warn!("Try handling object operation {}::{}, resolved object home uri as local home uri. This is a bug.", object.kind(), - // operation.kind().operation_name); - - return Err(OperationError::Unhandled); - } - } - - // trace!( - // "Handling object operation {}::{} sending payload", - // object.kind(), - // operation.kind().operation_name - // ); - - let object = NetworkedObject(unsafe { (object_meta.to_str)(object).as_ref().to_string() }); - - let payload = unsafe { (operation_meta.serialize)(&operation) }.unwrap(); - let payload = Vec::from(payload.as_ref()); - - let operation = NetworkedOperation::new(operation_meta.kind().to_string(), payload); - - // let authenticated = Authenticated::new(object, operation); - - let message = GiteratedMessage { - object, - operation: NetworkedOperation::operation_name().to_string(), - payload: operation, - }; - - let authenticated = Authenticated::new(message); - - let mut socket: WebSocketStream> = connect_to( - &Instance::from_str(&object_home_uri).unwrap(), - &Some(("127.0.0.1:1111").parse().unwrap()), - ) - .await - .unwrap(); - - // TODO AUTH - - let result: Result, OperationError>> = - send_expect(&mut socket, authenticated).await; - - match result { - Ok(success) => { - let success = unsafe { (operation_meta.deserialize_success)(&success) }.unwrap(); - - Ok(success) - } - Err(err) => Err(match err { - OperationError::Operation(failure) => { - let failure = unsafe { (operation_meta.deserialize_failure)(&failure) }.unwrap(); - - OperationError::Operation(failure) - } - OperationError::Internal(internal) => OperationError::Internal(internal), - OperationError::Unhandled => OperationError::Unhandled, - }), - } -} - -type Socket = WebSocketStream>; - -async fn connect_to( - instance: &Instance, - - socket_addr: &Option, -) -> Result { - if let Some(addr) = socket_addr { - info!( - "Connecting to {}", - format!("ws://{}/.giterated/daemon/", addr) - ); - - let (websocket, _response) = - connect_async(&format!("ws://{}/.giterated/daemon/", addr)).await?; - - info!("Connection established with {}", addr); - - Ok(websocket) - } else { - info!( - "Connecting to {}", - format!("wss://{}/.giterated/daemon/", instance.0) - ); - - let (websocket, _response) = - connect_async(&format!("wss://{}/.giterated/daemon/", instance.0)).await?; - - info!("Connection established with {}", instance.0); - - Ok(websocket) - } -} - -async fn send_expect>( - socket: &mut Socket, - message: Authenticated, -) -> Result, OperationError>> { - let payload = bincode::serialize(&message.into_payload()).unwrap(); - - socket.send(Message::Binary(payload)).await.unwrap(); - - while let Some(message) = socket.next().await { - let payload = match message.unwrap() { - Message::Binary(payload) => payload, - - _ => { - continue; - } - }; - - let raw_result = - bincode::deserialize::, NetworkOperationError>>>(&payload) - .map_err(|e| OperationError::Internal(Error::from(e)))?; - - trace!( - "Received response for networked operation {}::{}.", - O::object_name(), - D::operation_name() - ); - - return match raw_result { - Ok(success) => Ok(success), - Err(err) => Err(match err { - NetworkOperationError::Operation(operation_error) => { - OperationError::Operation(operation_error) - } - NetworkOperationError::Internal => OperationError::Internal(RemoteError.into()), - NetworkOperationError::Unhandled => OperationError::Unhandled, - }), - }; - } - - panic!() -} diff --git a/giterated-plugins/giterated-protocol/src/lib.rs b/giterated-plugins/giterated-protocol/src/lib.rs deleted file mode 100644 index 4eacc08..0000000 --- a/giterated-plugins/giterated-protocol/src/lib.rs +++ /dev/null @@ -1,300 +0,0 @@ -use std::{ops::Deref, str::FromStr, sync::Arc}; - -use giterated_models::{ - authenticated::{InstanceSignature, UserAuthenticationToken}, - instance::Instance, - object::GiteratedObject, - operation::GiteratedOperation, - user::User, -}; -use handlers::{NetworkedObject, NetworkedOperation}; -use object::NetworkAnyObject; -use operations::NetworkAnyOperation; -use rsa::{ - pkcs1::DecodeRsaPrivateKey, - pss::SigningKey, - sha2::Sha256, - signature::{RandomizedSigner, SignatureEncoding}, - RsaPrivateKey, -}; -use serde::{Deserialize, Serialize}; -use std::fmt::Debug; - -pub mod handlers; -pub mod object; -pub mod operations; - -#[macro_use] -extern crate tracing; - -#[derive(Clone)] -pub struct StackOperationState { - pub our_instance: Instance, - pub instance: Option, - pub user: Option, -} - -#[derive(Clone, Debug)] -pub struct AuthenticatedInstance(Instance); - -impl AuthenticatedInstance { - pub fn new(instance: Instance) -> Self { - AuthenticatedInstance(instance) - } -} - -impl Deref for AuthenticatedInstance { - type Target = Instance; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -#[derive(Clone, Debug)] -pub struct AuthenticatedUser(User); - -impl AuthenticatedUser { - pub fn new(user: User) -> Self { - AuthenticatedUser(user) - } -} - -impl Deref for AuthenticatedUser { - type Target = User; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -#[derive(Clone)] -pub struct NetworkOperationState { - authentication: Vec>, -} - -impl NetworkOperationState { - pub fn new() -> Self { - Self { - authentication: vec![], - } - } - - pub fn authenticate( - &mut self, - provider: impl AuthenticationSourceProviders + Send + Sync + 'static, - ) { - self.authentication.push(Arc::new(provider)) - } -} - -#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)] -pub struct AuthenticatedPayload { - pub source: Vec, - pub object: String, - pub operation: String, - pub payload: Vec, -} - -impl AuthenticatedPayload { - pub fn into_message(self) -> GiteratedMessage { - GiteratedMessage { - object: NetworkedObject::from_str(&self.object).unwrap(), - operation: self.operation, - payload: serde_json::from_slice(&self.payload).unwrap(), - } - } -} - -pub trait AuthenticationSourceProvider: Debug { - fn authenticate(&self, payload: &Vec) -> AuthenticationSource; -} - -pub trait AuthenticationSourceProviders: Debug { - fn authenticate_all(&self, payload: &Vec) -> Vec; -} - -impl AuthenticationSourceProviders for A -where - A: AuthenticationSourceProvider, -{ - fn authenticate_all(&self, payload: &Vec) -> Vec { - vec![self.authenticate(payload)] - } -} - -impl AuthenticationSourceProviders for (A, B) -where - A: AuthenticationSourceProvider, - B: AuthenticationSourceProvider, -{ - fn authenticate_all(&self, payload: &Vec) -> Vec { - let (first, second) = self; - - vec![first.authenticate(payload), second.authenticate(payload)] - } -} - -#[derive(Clone, Debug)] -pub struct UserAuthenticator { - pub user: User, - pub token: UserAuthenticationToken, -} - -impl AuthenticationSourceProvider for UserAuthenticator { - fn authenticate(&self, _payload: &Vec) -> AuthenticationSource { - AuthenticationSource::User { - user: self.user.clone(), - token: self.token.clone(), - } - } -} - -#[derive(Debug, Clone)] -pub struct InstanceAuthenticator { - pub instance: Instance, - pub private_key: String, -} - -impl AuthenticationSourceProvider for InstanceAuthenticator { - fn authenticate(&self, payload: &Vec) -> AuthenticationSource { - let mut rng = rand::thread_rng(); - - let private_key = RsaPrivateKey::from_pkcs1_pem(&self.private_key).unwrap(); - let signing_key = SigningKey::::new(private_key); - let signature = signing_key.sign_with_rng(&mut rng, payload); - - AuthenticationSource::Instance { - instance: self.instance.clone(), - // TODO: Actually parse signature from private key - signature: InstanceSignature(signature.to_bytes().into_vec()), - } - } -} - -#[derive(Clone, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] -pub enum AuthenticationSource { - User { - user: User, - token: UserAuthenticationToken, - }, - Instance { - instance: Instance, - signature: InstanceSignature, - }, -} - -#[derive(Serialize)] -#[serde(bound(deserialize = "O: GiteratedObject, V: GiteratedOperation"))] -pub struct GiteratedMessage> { - #[serde(with = "string")] - pub object: O, - pub operation: String, - pub payload: V, -} - -#[allow(unused)] -mod string { - use std::fmt::Display; - use std::str::FromStr; - - use serde::{de, Deserialize, Deserializer, Serializer}; - - pub fn serialize(value: &T, serializer: S) -> Result - where - T: Display, - S: Serializer, - { - serializer.collect_str(value) - } - - pub fn deserialize<'de, T, D>(deserializer: D) -> Result - where - T: FromStr, - T::Err: Display, - D: Deserializer<'de>, - { - String::deserialize(deserializer)? - .parse() - .map_err(de::Error::custom) - } -} - -impl GiteratedMessage { - pub fn try_into>( - &self, - ) -> Result, ()> { - let object = O::from_object_str(&self.object.0).map_err(|_| ())?; - let payload = serde_json::from_slice::(&self.payload.0).map_err(|_| ())?; - - Ok(GiteratedMessage { - object, - operation: self.operation.clone(), - payload, - }) - } -} - -impl + Debug, O: GiteratedObject + Debug> Debug - for GiteratedMessage -{ - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("GiteratedMessage") - .field("object", &self.object) - .field("operation", &self.operation) - .field("payload", &self.payload) - .finish() - } -} - -#[derive(Debug, Clone, thiserror::Error)] -#[error("a remote internal error occurred")] -pub struct RemoteError; - -#[derive(Debug, thiserror::Error)] -#[error("a remote internal error occurred")] - -pub struct NetworkError; - -#[derive(Debug)] -pub struct Authenticated> { - pub source: Vec>, - pub message: GiteratedMessage, -} - -impl> Authenticated { - pub fn new(message: GiteratedMessage) -> Self { - Self { - source: vec![], - message, - } - } - - pub fn append_authentication( - &mut self, - authentication: Arc, - ) { - self.source.push(authentication); - } - - pub fn into_payload(mut self) -> AuthenticatedPayload { - let payload = serde_json::to_vec(&self.message.payload).unwrap(); - - AuthenticatedPayload { - object: self.message.object.to_string(), - operation: self.message.operation, - source: self - .source - .drain(..) - .map(|provider| provider.as_ref().authenticate_all(&payload)) - .flatten() - .collect::>(), - payload, - } - } -} - -#[derive(Clone, Debug)] -pub struct ProtocolState { - pub home_uri: Option, -} diff --git a/giterated-plugins/giterated-protocol/src/object.rs b/giterated-plugins/giterated-protocol/src/object.rs deleted file mode 100644 index 558f1d6..0000000 --- a/giterated-plugins/giterated-protocol/src/object.rs +++ /dev/null @@ -1,38 +0,0 @@ -use std::{convert::Infallible, fmt::Display, str::FromStr}; - -use anyhow::Error; -use giterated_models::object::GiteratedObject; -use serde::{Deserialize, Serialize}; - -#[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(transparent)] -#[repr(transparent)] -pub struct NetworkAnyObject(pub String); - -impl GiteratedObject for NetworkAnyObject { - fn object_name() -> &'static str { - "network_object" - } - - fn from_object_str(object_str: &str) -> Result { - Ok(Self(object_str.to_string())) - } - - fn home_uri(&self) -> String { - todo!() - } -} - -impl Display for NetworkAnyObject { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(&self.0) - } -} - -impl FromStr for NetworkAnyObject { - type Err = Infallible; - - fn from_str(s: &str) -> Result { - Ok(Self(s.to_owned())) - } -} diff --git a/giterated-plugins/giterated-protocol/src/operations.rs b/giterated-plugins/giterated-protocol/src/operations.rs deleted file mode 100644 index 6e54db4..0000000 --- a/giterated-plugins/giterated-protocol/src/operations.rs +++ /dev/null @@ -1,13 +0,0 @@ -use giterated_models::{object::GiteratedObject, operation::GiteratedOperation}; -use serde::{Deserialize, Serialize}; - -#[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(transparent)] -#[repr(transparent)] -pub struct NetworkAnyOperation(pub Vec); - -impl GiteratedOperation for NetworkAnyOperation { - type Success = Vec; - - type Failure = Vec; -} diff --git a/giterated-stack/Cargo.toml b/giterated-stack/Cargo.toml deleted file mode 100644 index 4e1f00b..0000000 --- a/giterated-stack/Cargo.toml +++ /dev/null @@ -1,28 +0,0 @@ -[package] -name = "giterated-stack" -version = "0.1.0" -authors = ["Amber Kowalski"] -edition = "2021" -rust-version = "1.70.0" -description = "Giterated's Unified Stack" -homepage = "https://giterated.dev/ambee/giterated" -repository = "https://giterated.dev/ambee/giterated" -license = "MPL-2.0" -keywords = ["giterated"] - -# Leave until MVP -publish = false - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -giterated-models = { path = "../giterated-models" } -async-trait = "0.1" -serde = { version = "1.0.188", features = [ "derive" ]} -serde_json = "1.0" -bincode = "1.3" -futures-util = "0.3" -tracing = "0.1" -tokio = { version = "1.32", features = [ "full" ] } -anyhow = "1" -thiserror = "1" \ No newline at end of file diff --git a/giterated-stack/README.md b/giterated-stack/README.md deleted file mode 100644 index 3f3fed8..0000000 --- a/giterated-stack/README.md +++ /dev/null @@ -1,41 +0,0 @@ -# Giterated Stack - -Giterated stack is the unified protocol stack implementation for Giterated. The architecture of the Giterated Stack allows for easy re-use of the same stack handling code across the entire Giterated ecosystem, from daemon to client. This is aided by the aggressive type erasure when using Giterated's Unified Stack as well as reflection to enable dynamic creation at runtime of an efficient handler stack for Giterated's protocol. - -# Architecture - -Giterated Stack defines the type `GiteratedStack`, which contains all the state necessary to handle Giterated Operations into their result. In order to do this, there is a registration process for object types, setting types, value types, operation handlers, and other metadata. - - -A `GiteratedStack` is created by merging any number of `SubstackBuilder`s into the `GiteratedStack`. The creation process is completed once the `GiteratedStack` is placed within an `Arc`, leaving it read-only. `GiteratedStack` is designed to be completely immutable at operation handling time. This means you can access it from any number of threads without concern for lock contention or other similar cases. - -## Reflection -`GiteratedStack` uses traits to embed reflection into the binary. `GiteratedStack` generates this reflection metadata at runtime from traits which are inherent on the types it is reflecting. - -This means that if, for example, `T: ValueMeta`: `T` also has an implementation of `T: IntoValueMeta`. - -* Giterated's Unifed Stack uses the `IntoValueMeta` implementation (generated by the compiler) to populate function pointers for reflection. We avoid an extra allocation over boxed `Fn*` traits. - -* Reflection is used in Giterated to allow for deserializing objects and operations into their concrete type as soon as possible. - -* Reflection is also used for values and settings, to allow for serializing and deserializing, as well as keeping the data in its concrete type. - -* Keeping data in `Box` has performance and ergonomic benefits over a ser/de intermediate style instead. - - -**For specifics on what is reflected, how it is reflected, and how the reflection is used, please refer to the implementation of `GiteratedStack`**. - -# Parallelism - -Giterated's Unified Stack aims to leave compute parallelism to the caller. It contains no internal notion of runtime state, thread pools, etc. - -If the caller seeks compute parallelism, they can provide that by calling Giterated's Unified Stack from as many threads as they wish. - -# Motivation - -The design of the Giterated Unified Stack is intended to create a data structure that represents the entire stack's state, and then operations are completed using that context. Giterated's Unified Stack maintains no concept of tasks, threads, or thread pools; rather, Giterated's Unified Stack exists only as the entire state needed to process an operation into a response. - -## Implementation - -- It is crucial that the internals of Giterated's Unified Stack (reflection, etc) remain well-encapsulated and consumers do not have to concern themselves with the internals. -- It is a goal for `giterated-stack` to be `#![no_std]` with `alloc`. This is to better facilitate integration through WASM support, support for more targets, and libraries that are more slim. \ No newline at end of file diff --git a/giterated-stack/src/dynamic.rs b/giterated-stack/src/dynamic.rs deleted file mode 100644 index cecf318..0000000 --- a/giterated-stack/src/dynamic.rs +++ /dev/null @@ -1,236 +0,0 @@ -use std::{any::Any, ops::Deref, sync::Arc}; - -use giterated_models::{ - object::GiteratedObject, operation::GiteratedOperation, settings::Setting, - value::GiteratedObjectValue, -}; - -use crate::{ - ObjectMeta, ObjectOperationPair, ObjectSettingPair, ObjectValuePair, OperationMeta, ValueMeta, -}; - -#[derive(Clone)] -pub struct AnyObject { - inner: Arc, - meta: Arc, - kind: &'static str, -} - -impl AnyObject { - pub fn new(object: O) -> Self { - Self { - inner: Arc::new(object) as _, - meta: Arc::new(ObjectMeta::new::()), - kind: O::object_name(), - } - } - - pub fn new_raw(_object: Arc, _kind: &'static str) -> Self { - todo!() - } - - pub fn kind(&self) -> &'static str { - self.kind - } - - pub fn meta(&self) -> &Arc { - &self.meta - } -} - -impl Deref for AnyObject { - type Target = dyn Any + Send + Sync; - - fn deref(&self) -> &Self::Target { - self.inner.as_ref() - } -} - -#[derive(Clone)] -pub struct AnyOperation { - inner: Arc, - meta: Arc, - kind: ObjectOperationPair<'static>, -} - -impl AnyOperation { - pub fn new + 'static>( - operation: D, - ) -> Self { - Self { - inner: Arc::new(operation) as _, - meta: Arc::new(OperationMeta::new::()), - kind: ObjectOperationPair::from_types::(), - } - } - - pub fn new_raw( - _operation: Arc, - _kind: ObjectOperationPair<'static>, - ) -> Self { - todo!() - } - - pub fn kind(&self) -> ObjectOperationPair<'static> { - self.kind - } - - pub fn meta(&self) -> &Arc { - &self.meta - } -} - -impl Deref for AnyOperation { - type Target = dyn Any + Send + Sync; - - fn deref(&self) -> &Self::Target { - self.inner.as_ref() - } -} - -#[derive(Clone)] -pub struct AnyValue { - inner: Arc, - kind: ObjectValuePair<'static>, -} - -impl AnyValue { - pub fn new + 'static>( - value: V, - ) -> Self { - Self { - inner: Arc::new(value) as _, - kind: ObjectValuePair::from_types::(), - } - } - - pub fn new_raw(_value: Arc, _kind: ObjectValuePair<'static>) -> Self { - todo!() - } - - pub fn kind(&self) -> ObjectValuePair<'static> { - self.kind - } -} - -impl Deref for AnyValue { - type Target = dyn Any + Send + Sync; - - fn deref(&self) -> &Self::Target { - self.inner.as_ref() - } -} - -#[derive(Clone)] -pub struct AnySetting { - inner: Arc, - kind: ObjectSettingPair<'static>, -} - -impl AnySetting { - pub fn new(setting: S) -> Self { - Self { - inner: Arc::new(setting) as _, - kind: ObjectSettingPair::from_types::(), - } - } - - pub fn new_raw( - _setting: Arc, - _kind: ObjectSettingPair<'static>, - ) -> Self { - todo!() - } - - pub fn kind(&self) -> ObjectSettingPair<'static> { - self.kind - } -} - -impl Deref for AnySetting { - type Target = dyn Any + Send + Sync; - - fn deref(&self) -> &Self::Target { - self.inner.as_ref() - } -} - -#[derive(Clone)] -pub struct AnySuccess(pub Arc); - -#[derive(Clone)] -pub struct AnyFailure(pub Arc); - -/// Should be renamed. -/// -/// Allows accepting object types that are either GiteratedObject types or -/// AnyObject. -pub trait MaybeDynamicObject: Clone { - fn from_any(object: &AnyObject) -> Self; - - fn object_name() -> &'static str; -} - -impl MaybeDynamicObject for O { - fn from_any(_object: &AnyObject) -> Self { - todo!() - } - - fn object_name() -> &'static str { - ::object_name() - } -} - -impl MaybeDynamicObject for AnyObject { - fn from_any(object: &AnyObject) -> Self { - object.clone() - } - - fn object_name() -> &'static str { - "any" - } -} - -pub trait MaybeDynamicValue { - fn from_any(value: &AnyValue) -> Self; - fn into_any(self) -> AnyValue; - fn meta() -> Option; - - fn value_name() -> &'static str; -} - -impl MaybeDynamicValue for V { - fn from_any(_object: &AnyValue) -> Self { - todo!() - } - - fn value_name() -> &'static str { - todo!() - } - - fn into_any(self) -> AnyValue { - todo!() - } - - fn meta() -> Option { - todo!() - } -} - -impl MaybeDynamicValue for AnyValue { - fn value_name() -> &'static str { - "any" - } - - fn from_any(value: &AnyValue) -> Self { - value.clone() - } - - fn into_any(self) -> AnyValue { - self - } - - fn meta() -> Option { - todo!() - } -} diff --git a/giterated-stack/src/handler/handler_impl.rs b/giterated-stack/src/handler/handler_impl.rs deleted file mode 100644 index 6029d3e..0000000 --- a/giterated-stack/src/handler/handler_impl.rs +++ /dev/null @@ -1,272 +0,0 @@ -use futures_util::Future; -use giterated_models::error::OperationError; - -use crate::{HandlerResolvable, IntoGiteratedHandler}; - -#[async_trait::async_trait(?Send)] -impl - IntoGiteratedHandler<(R1,), (), S, OS, Result>> for H -where - H: FnMut(R1, S) -> Fut + Clone, - Fut: Future>> + 'static, - Success: 'static, - Failure: 'static, - R1: 'static, - S: 'static, - OS: 'static, -{ - type Future = Fut; - - async fn handle( - &self, - parameters: (R1,), - state: S, - _operation_state: OS, - ) -> Result> { - let (r1,) = parameters; - (self.clone())(r1, state).await - } -} - -#[async_trait::async_trait(?Send)] -impl - IntoGiteratedHandler<(R1,), (A1,), S, OS, Result>> for H -where - H: FnMut(R1, S, A1) -> Fut + Clone, - Fut: Future>> + 'static, - Success: 'static, - Failure: 'static, - R1: 'static, - S: 'static, - OS: 'static, - A1: HandlerResolvable<(R1,), OS>, - A1::Error: Into, -{ - type Future = Fut; - - async fn handle( - &self, - parameters: (R1,), - state: S, - operation_state: OS, - ) -> Result> { - let a1 = A1::from_handler_state(¶meters, &operation_state) - .await - .map_err(|e| OperationError::Internal(e.into()))?; - let (r1,) = parameters; - (self.clone())(r1, state, a1).await - } -} - -#[async_trait::async_trait(?Send)] -impl - IntoGiteratedHandler<(R1,), (A1, A2), S, OS, Result>> for H -where - H: FnMut(R1, S, A1, A2) -> Fut + Clone, - Fut: Future>> + 'static, - Success: 'static, - Failure: 'static, - R1: 'static, - S: 'static, - OS: 'static, - A1: HandlerResolvable<(R1,), OS>, - A1::Error: Into, - A2: HandlerResolvable<(R1,), OS>, - A2::Error: Into, -{ - type Future = Fut; - - async fn handle( - &self, - parameters: (R1,), - state: S, - operation_state: OS, - ) -> Result> { - let a1 = A1::from_handler_state(¶meters, &operation_state) - .await - .map_err(|e| OperationError::Internal(e.into()))?; - let a2 = A2::from_handler_state(¶meters, &operation_state) - .await - .map_err(|e| OperationError::Internal(e.into()))?; - let (r1,) = parameters; - (self.clone())(r1, state, a1, a2).await - } -} - -#[async_trait::async_trait(?Send)] -impl - IntoGiteratedHandler<(R1,), (A1, A2, A3), S, OS, Result>> for H -where - H: FnMut(R1, S, A1, A2, A3) -> Fut + Clone, - Fut: Future>> + 'static, - Success: 'static, - Failure: 'static, - R1: 'static, - S: 'static, - OS: 'static, - A1: HandlerResolvable<(R1,), OS>, - A1::Error: Into, - A2: HandlerResolvable<(R1,), OS>, - A2::Error: Into, - A3: HandlerResolvable<(R1,), OS>, - A3::Error: Into, -{ - type Future = Fut; - - async fn handle( - &self, - parameters: (R1,), - state: S, - operation_state: OS, - ) -> Result> { - let a1 = A1::from_handler_state(¶meters, &operation_state) - .await - .map_err(|e| OperationError::Internal(e.into()))?; - let a2 = A2::from_handler_state(¶meters, &operation_state) - .await - .map_err(|e| OperationError::Internal(e.into()))?; - let a3 = A3::from_handler_state(¶meters, &operation_state) - .await - .map_err(|e| OperationError::Internal(e.into()))?; - let (r1,) = parameters; - (self.clone())(r1, state, a1, a2, a3).await - } -} - -#[async_trait::async_trait(?Send)] -impl - IntoGiteratedHandler<(R1, R2), (), S, OS, Result>> for H -where - H: FnMut(R1, R2, S) -> Fut + Clone, - Fut: Future>> + 'static, - Success: 'static, - Failure: 'static, - R1: 'static, - R2: 'static, - S: 'static, - OS: 'static, -{ - type Future = Fut; - - async fn handle( - &self, - parameters: (R1, R2), - state: S, - _operation_state: OS, - ) -> Result> { - let (r1, r2) = parameters; - (self.clone())(r1, r2, state).await - } -} - -#[async_trait::async_trait(?Send)] -impl - IntoGiteratedHandler<(R1, R2), (A1,), S, OS, Result>> for H -where - H: FnMut(R1, R2, S, A1) -> Fut + Clone, - Fut: Future>> + 'static, - Success: 'static, - Failure: 'static, - R1: 'static, - R2: 'static, - S: 'static, - OS: 'static, - A1: HandlerResolvable<(R1, R2), OS>, - A1::Error: Into, -{ - type Future = Fut; - - async fn handle( - &self, - parameters: (R1, R2), - state: S, - operation_state: OS, - ) -> Result> { - let a1 = A1::from_handler_state(¶meters, &operation_state) - .await - .map_err(|e| OperationError::Internal(e.into()))?; - - let (r1, r2) = parameters; - (self.clone())(r1, r2, state, a1).await - } -} - -#[async_trait::async_trait(?Send)] -impl - IntoGiteratedHandler<(R1, R2), (A1, A2), S, OS, Result>> for H -where - H: FnMut(R1, R2, S, A1, A2) -> Fut + Clone, - Fut: Future>> + 'static, - Success: 'static, - Failure: 'static, - R1: 'static, - R2: 'static, - S: 'static, - OS: 'static, - A1: HandlerResolvable<(R1, R2), OS>, - A1::Error: Into, - A2: HandlerResolvable<(R1, R2), OS>, - A2::Error: Into, -{ - type Future = Fut; - - async fn handle( - &self, - parameters: (R1, R2), - state: S, - operation_state: OS, - ) -> Result> { - let a1 = A1::from_handler_state(¶meters, &operation_state) - .await - .map_err(|e| OperationError::Internal(e.into()))?; - let a2 = A2::from_handler_state(¶meters, &operation_state) - .await - .map_err(|e| OperationError::Internal(e.into()))?; - - let (r1, r2) = parameters; - (self.clone())(r1, r2, state, a1, a2).await - } -} - -#[async_trait::async_trait(?Send)] -impl - IntoGiteratedHandler<(R1, R2), (A1, A2, A3), S, OS, Result>> - for H -where - H: FnMut(R1, R2, S, A1, A2, A3) -> Fut + Clone, - Fut: Future>> + 'static, - Success: 'static, - Failure: 'static, - R1: 'static, - R2: 'static, - S: 'static, - OS: 'static, - A1: HandlerResolvable<(R1, R2), OS>, - A1::Error: Into, - A2: HandlerResolvable<(R1, R2), OS>, - A2::Error: Into, - A3: HandlerResolvable<(R1, R2), OS>, - A3::Error: Into, -{ - type Future = Fut; - - async fn handle( - &self, - parameters: (R1, R2), - state: S, - operation_state: OS, - ) -> Result> { - let a1 = A1::from_handler_state(¶meters, &operation_state) - .await - .map_err(|e| OperationError::Internal(e.into()))?; - let a2 = A2::from_handler_state(¶meters, &operation_state) - .await - .map_err(|e| OperationError::Internal(e.into()))?; - let a3 = A3::from_handler_state(¶meters, &operation_state) - .await - .map_err(|e| OperationError::Internal(e.into()))?; - - let (r1, r2) = parameters; - (self.clone())(r1, r2, state, a1, a2, a3).await - } -} diff --git a/giterated-stack/src/handler/mod.rs b/giterated-stack/src/handler/mod.rs deleted file mode 100644 index 30fa024..0000000 --- a/giterated-stack/src/handler/mod.rs +++ /dev/null @@ -1,236 +0,0 @@ -use std::{any::Any, sync::Arc}; -pub mod handler_impl; -use futures_util::{future::LocalBoxFuture, Future, FutureExt}; -use giterated_models::error::OperationError; - -use crate::{ - AuthenticatedInstance, AuthenticatedUser, GiteratedStack, MissingValue, StackOperationState, -}; - -#[async_trait::async_trait(?Send)] -pub trait IntoGiteratedHandler -where - // Output cannot have non-static references - Output: 'static, -{ - type Future: Future; - - async fn handle(&self, parameters: Params, state: State, operation_state: OState) -> Output; -} - -pub struct HandlerTree { - elements: Vec, -} - -impl Default for HandlerTree { - fn default() -> Self { - Self { - elements: Default::default(), - } - } -} - -impl<'fut: 'o + 'p, 'p, 'o, P, O, E, OS> HandlerTree> -where - P: Clone, -{ - pub fn push(&mut self, handler: HandlerWrapper) { - self.elements.push(handler); - } - pub async fn handle(&self, parameters: P, operation_state: OS) -> Result> - where - OS: Clone + 'static, - { - for handler in self.elements.iter() { - match handler - .handle(parameters.clone(), operation_state.clone()) - .await - { - Ok(handled) => return Ok(handled), - Err(err) => match err { - OperationError::Internal(err) => return Err(OperationError::Internal(err)), - OperationError::Operation(err) => return Err(OperationError::Operation(err)), - OperationError::Unhandled => continue, - }, - } - } - - Err(OperationError::Unhandled) - } -} - -pub struct HandlerWrapper { - func: Arc< - dyn Fn( - P, - Arc, - OS, - ) -> LocalBoxFuture<'static, Result>> - + Send - + Sync, - >, - state: Arc, -} - -impl HandlerWrapper { - pub fn new(state: S, handler: F) -> Self - where - F: IntoGiteratedHandler>> + Send + Sync, - S: Send + Sync + Clone + 'static, - E: 'static, - P: 'static + Clone, - F: 'static, - O: 'static, - OS: Clone, - { - let state = Arc::new(state); - - let handler_func = Arc::new(handler); - let state_two = state.clone(); - HandlerWrapper { - func: Arc::new( - move |args: P, state: Arc, operation_state: OS| { - let handler = handler_func.clone(); - let operation_state = operation_state.clone(); - let state = state.downcast_ref::().unwrap(); - let state = state.clone(); - async move { - let handler = handler.clone(); - let operation_state = operation_state; - handler.handle(args, state, operation_state).await - } - .boxed_local() - }, - ), - state: state_two, - } - } - - pub async fn handle(&self, required: P, operation_state: OS) -> Result> { - (self.func)(required, self.state.clone(), operation_state).await - } - - pub fn map(self, predicate: F) -> HandlerWrapper - where - F: Fn(&N, &OS) -> Result> + Clone + Send + Sync, - R: std::fmt::Debug + 'static, - E: Into> + 'static, - OperationError: From> + 'static, - F: 'static, - N: 'static, - P: 'static, - O: 'static, - OS: Clone, - { - let func = Arc::new(self.func); - let predicate = Arc::new(predicate); - HandlerWrapper { - func: Arc::new( - move |args: N, state: Arc, operation_state: OS| { - let predicate_output = predicate(&args, &operation_state); - let func = func.clone(); - let operation_state: OS = operation_state.clone(); - async move { - let predicate_output = predicate_output?; - let operation_state = operation_state; - match (func)(predicate_output, state, operation_state).await { - Ok(success) => Ok(success), - Err(err) => Err(err.into()), - } - } - .boxed_local() - }, - ), - state: self.state, - } - } - - pub fn map_return(self, predicate: F) -> HandlerWrapper - where - F: Fn(Result>, &OS) -> Result> - + Clone - + Send - + Sync, - O: 'static, - F: 'static, - E: 'static, - P: 'static, - OS: Clone, - { - let predicate = Arc::new(predicate); - let func = self.func; - HandlerWrapper { - func: Arc::new( - move |args: P, state: Arc, operation_state: OS| { - let clone = predicate.clone(); - let func = func.clone(); - let _statoperation_statee = operation_state.clone(); - - async move { - let func = func.clone(); - let clone = clone; - let operation_state = operation_state; - clone( - (func)(args, state, operation_state.clone()).await, - &operation_state, - ) - } - .boxed_local() - }, - ), - state: self.state, - } - } -} - -#[async_trait::async_trait(?Send)] -pub trait HandlerResolvable: Sized { - type Error; - - async fn from_handler_state( - required_parameters: &RequiredParameters, - operation_state: &OperationState, - ) -> Result; -} - -#[async_trait::async_trait(?Send)] -impl HandlerResolvable for GiteratedStack { - type Error = MissingValue; - - async fn from_handler_state( - _required_parameters: &R, - operation_state: &StackOperationState, - ) -> Result { - Ok(operation_state.runtime.clone()) - } -} - -#[async_trait::async_trait(?Send)] -impl HandlerResolvable for AuthenticatedUser { - type Error = MissingValue; - - async fn from_handler_state( - _required_parameters: &R, - operation_state: &StackOperationState, - ) -> Result { - operation_state - .user - .clone() - .ok_or_else(|| MissingValue("AuthenticatedUser")) - } -} - -#[async_trait::async_trait(?Send)] -impl HandlerResolvable for AuthenticatedInstance { - type Error = MissingValue; - - async fn from_handler_state( - _required_parameters: &R, - operation_state: &StackOperationState, - ) -> Result { - operation_state - .instance - .clone() - .ok_or_else(|| MissingValue("AuthenticatedInstance")) - } -} diff --git a/giterated-stack/src/lib.rs b/giterated-stack/src/lib.rs deleted file mode 100644 index 1138f31..0000000 --- a/giterated-stack/src/lib.rs +++ /dev/null @@ -1,489 +0,0 @@ -mod dynamic; -mod handler; -pub use dynamic::*; -pub use handler::*; -mod meta; -pub use meta::*; -pub mod provider; -mod stack; -pub use stack::*; -mod substack; -use serde::{de::DeserializeOwned, Deserialize, Serialize}; -pub use stack::*; -pub use substack::*; -pub mod update; - -// Temp pub use to figure out what's important -pub mod models { - pub use anyhow::Error; - pub use giterated_models::authenticated::*; - pub use giterated_models::error::{IntoInternalError, NetworkOperationError, OperationError}; - pub use giterated_models::instance::Instance; - pub use giterated_models::object::GiteratedObject; - pub use giterated_models::operation::GiteratedOperation; - pub use giterated_models::user::User; -} - -use std::{convert::Infallible, ops::Deref}; - -use core::fmt::Debug; -use giterated_models::{ - error::{ExtractorError, UnauthorizedError}, - instance::{ - AuthenticationTokenRequest, Instance, RegisterAccountRequest, RepositoryCreateRequest, - }, - object::GiteratedObject, - object_backend::ObjectBackend, - operation::GiteratedOperation, - repository::{AccessList, Repository}, - settings::{GetSetting, SetSetting, Setting}, - user::User, - value::{GetValue, GiteratedObjectValue}, -}; - -#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)] -pub struct ObjectOperationPair<'a> { - pub object_name: &'a str, - pub operation_name: &'a str, -} - -impl ObjectOperationPair<'static> { - #[allow(unused)] - pub fn from_types>() -> Self { - Self { - object_name: O::object_name(), - operation_name: D::operation_name(), - } - } -} - -#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)] -pub struct ObjectValuePair<'a> { - pub object_kind: &'a str, - pub value_kind: &'a str, -} - -impl ObjectValuePair<'static> { - pub fn from_types>() -> Self { - Self { - object_kind: O::object_name(), - value_kind: V::value_name(), - } - } -} - -#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)] -pub struct ObjectSettingPair<'a> { - pub object_kind: &'a str, - pub setting_name: &'a str, -} - -impl ObjectSettingPair<'static> { - pub fn from_types() -> Self { - Self { - object_kind: O::object_name(), - setting_name: S::name(), - } - } -} - -#[async_trait::async_trait(?Send)] -pub trait FromOperationState>: Sized + Clone { - type Error: Into; - - async fn from_state( - object: &O, - operation: &D, - state: &StackOperationState, - ) -> Result>; -} - -#[async_trait::async_trait(?Send)] -impl> FromOperationState - for StackOperationState -{ - type Error = Infallible; - - async fn from_state( - _object: &O, - _operation: &D, - state: &StackOperationState, - ) -> Result> { - Ok(state.clone()) - } -} - -#[derive(Debug, thiserror::Error, Serialize, Deserialize)] -#[error("missing value {0}")] -pub struct MissingValue(&'static str); - -#[async_trait::async_trait(?Send)] -impl + Send + Sync> FromOperationState - for AuthenticatedUser -{ - type Error = MissingValue; - - async fn from_state( - _object: &O, - _operation: &D, - state: &StackOperationState, - ) -> Result> { - state - .user - .clone() - .ok_or(ExtractorError(MissingValue("AuthenticatedUser"))) - } -} - -#[async_trait::async_trait(?Send)] -impl + Send + Sync> FromOperationState - for AuthenticatedInstance -{ - type Error = MissingValue; - - async fn from_state( - _object: &O, - _operation: &D, - state: &StackOperationState, - ) -> Result> { - state - .instance - .clone() - .ok_or(ExtractorError(MissingValue("AuthenticatedInstance"))) - } -} - -#[async_trait::async_trait(?Send)] -impl< - T: FromOperationState + Send + Sync, - O: GiteratedObject + Sync, - D: GiteratedOperation + Send + Sync, - > FromOperationState for Option -{ - type Error = Infallible; - - async fn from_state( - object: &O, - operation: &D, - state: &StackOperationState, - ) -> Result, ExtractorError> { - Ok(T::from_state(object, operation, state).await.ok()) - } -} - -#[derive(Clone)] -pub struct AuthorizedUser(AuthenticatedUser); - -#[derive(Clone)] -pub struct AuthorizedInstance(AuthenticatedInstance); - -#[async_trait::async_trait(?Send)] -pub trait AuthorizedOperation: GiteratedOperation { - type Error: Into; - - async fn authorize( - &self, - authorize_for: &O, - state: &StackOperationState, - ) -> Result>; -} - -#[async_trait::async_trait(?Send)] -impl AuthorizedOperation for GetValue { - type Error = anyhow::Error; - - async fn authorize( - &self, - authorize_for: &O, - operation_state: &StackOperationState, - ) -> Result> { - Ok(operation_state - .runtime - .get_object::(&authorize_for.to_string(), operation_state) - .await - .is_ok()) - } -} - -#[async_trait::async_trait(?Send)] -impl AuthorizedOperation for SetSetting { - type Error = MissingValue; - - async fn authorize( - &self, - authorize_for: &User, - operation_state: &StackOperationState, - ) -> Result> { - let authenticated_user = operation_state - .user - .as_ref() - .ok_or(MissingValue("AuthenticatedUser"))?; - - Ok(authorize_for == authenticated_user.deref()) - } -} - -#[async_trait::async_trait(?Send)] -impl AuthorizedOperation for GetSetting { - type Error = MissingValue; - - async fn authorize( - &self, - authorize_for: &User, - operation_state: &StackOperationState, - ) -> Result> { - let authenticated_user = operation_state - .user - .as_ref() - .ok_or(MissingValue("AuthenticatedUser"))?; - - Ok(authorize_for == authenticated_user.deref()) - } -} - -#[async_trait::async_trait(?Send)] -impl AuthorizedOperation for SetSetting { - type Error = anyhow::Error; - - async fn authorize( - &self, - authorize_for: &Repository, - operation_state: &StackOperationState, - ) -> Result> { - let authenticated_user = operation_state - .user - .as_ref() - .ok_or_else(|| anyhow::Error::from(MissingValue("AuthenticatedUser")))?; - - let mut object = operation_state - .runtime - .get_object::(&authorize_for.to_string(), operation_state) - .await - .map_err(anyhow::Error::from)?; - - let access_list = object - .get_setting::(operation_state) - .await - .map_err(anyhow::Error::from)?; - - if access_list - .0 - .iter() - .any(|user| user == authenticated_user.deref()) - { - Ok(true) - } else { - Ok(false) - } - } -} - -#[async_trait::async_trait(?Send)] -impl AuthorizedOperation for GetSetting { - type Error = anyhow::Error; - - async fn authorize( - &self, - authorize_for: &Repository, - operation_state: &StackOperationState, - ) -> Result> { - let authenticated_user = operation_state - .user - .as_ref() - .ok_or_else(|| anyhow::Error::from(MissingValue("AuthenticatedUser")))?; - - let mut object = operation_state - .runtime - .get_object::(&authorize_for.to_string(), operation_state) - .await - .map_err(anyhow::Error::from)?; - - let access_list = object - .get_setting::(operation_state) - .await - .map_err(anyhow::Error::from)?; - - if access_list - .0 - .iter() - .any(|user| user == authenticated_user.deref()) - { - Ok(true) - } else { - Ok(false) - } - } -} - -#[async_trait::async_trait(?Send)] -impl AuthorizedOperation for RegisterAccountRequest { - type Error = Infallible; - - async fn authorize( - &self, - authorize_for: &Instance, - state: &StackOperationState, - ) -> Result> { - if state.our_instance == *authorize_for { - Ok(true) - } else { - Ok(false) - } - } -} - -#[async_trait::async_trait(?Send)] -impl AuthorizedOperation for AuthenticationTokenRequest { - type Error = Infallible; - - async fn authorize( - &self, - authorize_for: &Instance, - state: &StackOperationState, - ) -> Result> { - if state.our_instance == *authorize_for { - Ok(true) - } else { - Ok(false) - } - } -} - -#[async_trait::async_trait(?Send)] -impl AuthorizedOperation for RepositoryCreateRequest { - type Error = Infallible; - - async fn authorize( - &self, - authorize_for: &Instance, - state: &StackOperationState, - ) -> Result> { - if state.our_instance == *authorize_for { - Ok(true) - } else { - Ok(false) - } - } -} - -#[async_trait::async_trait(?Send)] -impl + Send + Sync> FromOperationState for AuthorizedUser { - type Error = UnauthorizedError; - - async fn from_state( - object: &User, - operation: &A, - state: &StackOperationState, - ) -> Result> { - // TODO - let authenticated = AuthenticatedUser::from_state(object, operation, state) - .await - .map_err(|_| ExtractorError(UnauthorizedError))?; - - match operation.authorize(object, state).await { - Ok(authorized) => { - assert!(authorized); - } - Err(_err) => return Err(ExtractorError(UnauthorizedError)), - }; - - Ok(AuthorizedUser(authenticated)) - } -} - -#[async_trait::async_trait(?Send)] -impl + Send + Sync> FromOperationState - for AuthorizedInstance -{ - type Error = UnauthorizedError; - - async fn from_state( - object: &Instance, - operation: &A, - state: &StackOperationState, - ) -> Result> { - //TODO - let authenticated = AuthenticatedInstance::from_state(object, operation, state) - .await - .map_err(|_| ExtractorError(UnauthorizedError))?; - - match operation.authorize(object, state).await { - Ok(authorized) => { - assert!(authorized); - } - Err(_err) => return Err(ExtractorError(UnauthorizedError)), - }; - - Ok(AuthorizedInstance(authenticated)) - } -} - -#[derive(Clone)] -pub struct StackOperationState { - pub our_instance: Instance, - pub instance: Option, - pub user: Option, -} - -#[derive(Clone, Debug)] -pub struct AuthenticatedInstance(Instance); - -impl AuthenticatedInstance { - pub fn new(instance: Instance) -> Self { - AuthenticatedInstance(instance) - } -} - -impl Deref for AuthenticatedInstance { - type Target = Instance; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -#[derive(Clone, Debug)] -pub struct AuthenticatedUser(User); - -impl AuthenticatedUser { - pub fn new(user: User) -> Self { - AuthenticatedUser(user) - } -} - -impl Deref for AuthenticatedUser { - type Target = User; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(transparent)] -#[serde(bound(deserialize = "S: DeserializeOwned"))] -pub struct SettingUpdate(pub S); - -impl GiteratedOperation for SettingUpdate -where - O: GiteratedObject, - S: Setting + Serialize + DeserializeOwned, -{ - type Success = (); - - type Failure = (); -} - -pub struct OperationState(pub OS); - -#[async_trait::async_trait(?Send)] -impl HandlerResolvable for OperationState { - type Error = MissingValue; - - async fn from_handler_state( - _required_parameters: &P, - operation_state: &OS, - ) -> Result { - Ok(Self(operation_state.clone())) - } -} diff --git a/giterated-stack/src/meta/mod.rs b/giterated-stack/src/meta/mod.rs deleted file mode 100644 index 62ed475..0000000 --- a/giterated-stack/src/meta/mod.rs +++ /dev/null @@ -1,388 +0,0 @@ -use std::{any::Any, collections::HashMap, str::FromStr, sync::Arc}; - -use futures_util::{future::LocalBoxFuture, FutureExt}; -use giterated_models::{ - object::GiteratedObject, - operation::GiteratedOperation, - settings::{SetSetting, Setting}, - value::{GetValue, GetValueTyped, GiteratedObjectValue}, -}; -use serde_json::Value; -use tracing::trace; - -use crate::{ - AnyFailure, AnyObject, AnyOperation, AnySetting, AnySuccess, AnyValue, GiteratedStack, - ObjectOperationPair, ObjectSettingPair, ObjectValuePair, StackOperationState, -}; - -/// Stores runtime metadata for all in-use Giterated protocol types. -#[derive(Default)] -pub struct RuntimeMetadata { - pub objects: HashMap, - pub operations: HashMap, OperationMeta>, - pub values: HashMap, ValueMeta>, - pub settings: HashMap, SettingMeta>, -} - -impl RuntimeMetadata { - pub fn register_object(&mut self) { - let object_name = O::object_name().to_string(); - - let object_meta = ObjectMeta::new::(); - - if self.objects.insert(object_name, object_meta).is_some() { - trace!( - "Registration of object {} overwrote previous registration.", - O::object_name() - ); - } else { - trace!("Registration of object {}.", O::object_name()) - } - } - - pub fn register_operation + 'static>( - &mut self, - ) { - let _object_name = O::object_name().to_string(); - let _operation_name = D::operation_name().to_string(); - - if self - .operations - .insert( - ObjectOperationPair::from_types::(), - OperationMeta::new::(), - ) - .is_some() - { - trace!( - "Registration of object operation {}<{}> overwrote previous registration.", - D::operation_name(), - O::object_name() - ); - } else { - trace!( - "Registration of object operation {}<{}>.", - D::operation_name(), - O::object_name() - ) - } - } - - pub fn register_value< - O: GiteratedObject + 'static, - V: GiteratedObjectValue + 'static, - >( - &mut self, - ) { - if self - .values - .insert(ObjectValuePair::from_types::(), ValueMeta::new::()) - .is_some() - { - trace!( - "Registration of value <{}>::{} overwrote previous registration.", - O::object_name(), - V::value_name() - ); - } else { - trace!( - "Registration of value <{}>::{}.", - O::object_name(), - V::value_name() - ); - } - - self.operations.insert( - ObjectOperationPair::from_types::(), - OperationMeta::new::(), - ); - } - - pub fn register_setting(&mut self) { - if self - .settings - .insert( - ObjectSettingPair::from_types::(), - SettingMeta::new::(), - ) - .is_some() - { - trace!( - "Registration of setting {} overwrote previous registration.", - S::name() - ); - } else { - trace!("Registration of setting {}.", S::name()); - } - - self.operations.insert( - ObjectOperationPair::from_types::(), - OperationMeta::new::(), - ); - } - - pub fn append(&mut self, other: Self) { - self.objects.extend(other.objects); - self.operations.extend(other.operations); - self.values.extend(other.values); - self.settings.extend(other.settings); - } -} - -pub struct ValueMeta { - pub name: String, - pub deserialize: fn(&[u8]) -> Result, - pub serialize: fn(AnyValue) -> Result, serde_json::Error>, - pub typed_get: fn() -> Box, - pub is_get_value_typed: fn(AnyOperation) -> bool, -} - -pub trait IntoValueMeta { - fn name() -> String; - fn deserialize(buffer: &[u8]) -> Result; - fn serialize(value: AnyValue) -> Result, serde_json::Error>; - fn typed_get() -> Box; - fn is_get_value_typed(typed_get_value: AnyOperation) -> bool; -} - -impl + 'static> IntoValueMeta for V { - fn name() -> String { - V::value_name().to_string() - } - - fn deserialize(buffer: &[u8]) -> Result { - Ok(AnyValue::new(serde_json::from_slice::(buffer)?)) - } - - fn serialize(value: AnyValue) -> Result, serde_json::Error> { - let value = value.downcast_ref::().unwrap(); - - serde_json::to_vec(&*value) - } - - fn typed_get() -> Box { - Box::new(GetValueTyped:: { - ty: Default::default(), - }) - } - - fn is_get_value_typed(typed_get_value: AnyOperation) -> bool { - typed_get_value.is::>() - } -} - -impl ValueMeta { - pub fn new() -> Self { - Self { - name: I::name(), - deserialize: I::deserialize, - serialize: I::serialize, - typed_get: I::typed_get, - is_get_value_typed: I::is_get_value_typed, - } - } -} - -pub struct OperationMeta { - pub name: String, - pub object_kind: String, - pub serialize: fn(AnyOperation) -> Result, serde_json::Error>, - pub deserialize: fn(&[u8]) -> Result, - pub any_is_same: fn(&dyn Any) -> bool, - pub serialize_success: fn(AnySuccess) -> Result, serde_json::Error>, - pub serialize_error: fn(AnyFailure) -> Result, serde_json::Error>, - pub deserialize_success: fn(Vec) -> Result, - pub deserialize_failure: fn(Vec) -> Result, -} - -pub trait IntoOperationMeta { - fn name() -> String; - fn deserialize(buffer: &[u8]) -> Result; - fn serialize_success(success: AnySuccess) -> Result, serde_json::Error>; - fn serialize_failure(failure: AnyFailure) -> Result, serde_json::Error>; - fn deserialize_success(success: Vec) -> Result; - fn deserialize_failure(failure: Vec) -> Result; - fn any_is_same(other: &dyn Any) -> bool; - fn serialize(operation: AnyOperation) -> Result, serde_json::Error>; -} - -impl IntoOperationMeta for D -where - D::Failure: 'static, - D::Success: 'static, - O: GiteratedObject + 'static, - D: GiteratedOperation + 'static, -{ - fn name() -> String { - D::operation_name().to_string() - } - - fn deserialize(buffer: &[u8]) -> Result { - Ok(AnyOperation::new(serde_json::from_slice::(buffer)?)) - } - - fn serialize_success(success: AnySuccess) -> Result, serde_json::Error> { - let to_serialize = success.0.downcast_ref::().unwrap(); - serde_json::to_vec(&to_serialize) - } - - fn serialize_failure(failure: AnyFailure) -> Result, serde_json::Error> { - let to_serialize = failure.0.downcast_ref::().unwrap(); - serde_json::to_vec(&to_serialize) - } - - fn any_is_same(other: &dyn Any) -> bool { - other.is::() - } - - fn serialize(operation: AnyOperation) -> Result, serde_json::Error> { - let operation: &D = operation.downcast_ref().unwrap(); - - serde_json::to_vec(operation) - } - - fn deserialize_success(success: Vec) -> Result { - let success: D::Success = serde_json::from_slice(&success)?; - - Ok(AnySuccess(Arc::new(success))) - } - - fn deserialize_failure(failure: Vec) -> Result { - let failure: D::Failure = serde_json::from_slice(&failure)?; - - Ok(AnyFailure(Arc::new(failure))) - } -} - -impl OperationMeta { - pub fn new + 'static>() -> Self { - Self { - name: I::name(), - deserialize: I::deserialize, - serialize_success: I::serialize_success, - serialize_error: I::serialize_failure, - object_kind: O::object_name().to_string(), - any_is_same: I::any_is_same, - serialize: I::serialize, - deserialize_success: I::deserialize_success, - deserialize_failure: I::deserialize_failure, - } - } -} - -pub struct ObjectMeta { - pub name: String, - pub to_str: Box String + Send + Sync>, - pub from_str: Box Result + Send + Sync>, - pub home_uri: fn(AnyObject) -> String, - pub any_is_same: fn(&dyn Any) -> bool, -} - -pub trait IntoObjectMeta: FromStr { - fn name() -> String; - fn any_is_same(other: &dyn Any) -> bool; - fn home_uri(object: AnyObject) -> String; -} - -impl IntoObjectMeta for O { - fn name() -> String { - O::object_name().to_string() - } - - fn any_is_same(other: &dyn Any) -> bool { - other.is::() - } - - fn home_uri(object: AnyObject) -> String { - let object: &O = object.downcast_ref().unwrap(); - - object.home_uri() - } -} - -impl ObjectMeta { - pub fn new() -> Self { - Self { - name: I::name(), - from_str: Box::new(|source| { - let object = I::from_str(source).map_err(|_| ())?; - - Ok(AnyObject::new(object)) - }), - to_str: Box::new(|source| { - let object: &I = source.downcast_ref().unwrap(); - - object.to_string() - }), - any_is_same: I::any_is_same, - home_uri: ::home_uri, - } - } -} - -pub struct SettingMeta { - pub name: String, - pub deserialize: fn(Value) -> Result, - pub serialize: fn(AnySetting) -> Result, - pub setting_updated: for<'fut> fn( - AnyObject, - AnySetting, - GiteratedStack, - &StackOperationState, - ) -> LocalBoxFuture<'_, ()>, -} - -pub trait IntoSettingMeta { - fn name() -> String; - fn deserialize(value: Value) -> Result; - fn serialize(setting: AnySetting) -> Result; - fn setting_updated( - object: AnyObject, - setting: AnySetting, - stack: GiteratedStack, - operation_state: &StackOperationState, - ) -> LocalBoxFuture<'_, ()>; -} - -impl IntoSettingMeta for S { - fn name() -> String { - S::name().to_string() - } - - fn deserialize(value: Value) -> Result { - Ok(AnySetting::new::(serde_json::from_value::(value)?)) - } - - fn serialize(setting: AnySetting) -> Result { - serde_json::to_value(setting.downcast_ref::().unwrap()) - } - - fn setting_updated( - object: AnyObject, - setting: AnySetting, - stack: GiteratedStack, - operation_state: &StackOperationState, - ) -> LocalBoxFuture<'_, ()> { - async move { - stack - .setting_update( - object.downcast_ref::().unwrap().clone(), - setting.downcast_ref::().unwrap().clone(), - operation_state, - ) - .await - } - .boxed_local() - } -} - -impl SettingMeta { - pub fn new + 'static>() -> Self { - Self { - name: I::name(), - deserialize: I::deserialize, - serialize: I::serialize, - setting_updated: I::setting_updated, - } - } -} diff --git a/giterated-stack/src/provider/metadata.rs b/giterated-stack/src/provider/metadata.rs deleted file mode 100644 index 451b301..0000000 --- a/giterated-stack/src/provider/metadata.rs +++ /dev/null @@ -1,25 +0,0 @@ -use std::any::Any; - -use anyhow::Error; - -use serde_json::Value; - -use crate::{AnyObject, AnySetting, ObjectMeta, SettingMeta}; - -#[async_trait::async_trait] -pub trait MetadataProvider: Send + Sync + 'static { - fn provides_for(&self, object: &dyn Any) -> bool; - async fn write( - &self, - object: AnyObject, - object_meta: &ObjectMeta, - setting: AnySetting, - setting_meta: &SettingMeta, - ) -> Result<(), Error>; - async fn read( - &self, - object: AnyObject, - object_meta: &ObjectMeta, - setting_meta: &SettingMeta, - ) -> Result; -} diff --git a/giterated-stack/src/provider/mod.rs b/giterated-stack/src/provider/mod.rs deleted file mode 100644 index 6dda9f9..0000000 --- a/giterated-stack/src/provider/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -mod metadata; -pub use metadata::MetadataProvider; diff --git a/giterated-stack/src/stack.rs b/giterated-stack/src/stack.rs deleted file mode 100644 index 3357901..0000000 --- a/giterated-stack/src/stack.rs +++ /dev/null @@ -1,933 +0,0 @@ -use std::any::Any; - -use std::fmt::Debug; -use std::marker::PhantomData; -use std::ops::Deref; -use std::str::FromStr; -use std::{collections::HashMap, sync::Arc}; - -use anyhow::Error; -use giterated_models::error::IntoInternalError; -use giterated_models::instance::Instance; -use giterated_models::object::ObjectResponse; -use giterated_models::settings::{SetSettingError, Setting}; - -use giterated_models::value::GetValue; -use giterated_models::{ - error::OperationError, - object::{GiteratedObject, Object, ObjectRequest, ObjectRequestError}, - object_backend::ObjectBackend, - operation::GiteratedOperation, - settings::{GetSetting, SetSetting}, -}; - -use tracing::{trace, warn}; - -use crate::handler::HandlerTree; -use crate::provider::MetadataProvider; -use crate::{ - AnyFailure, AnyObject, AnyOperation, AnySetting, AnySuccess, AnyValue, HandlerResolvable, - HandlerWrapper, MissingValue, ObjectOperationPair, ObjectSettingPair, ObjectValuePair, - RuntimeMetadata, StackOperationState, SubstackBuilder, -}; - -pub type OperationHandler = - HandlerWrapper<(AnyObject, AnyOperation), AnySuccess, AnyFailure, OS>; - -pub type ValueGetter = HandlerWrapper<(AnyObject, String), AnyValue, AnyFailure, OS>; - -pub type SettingGetter = HandlerWrapper<(AnyObject,), AnySetting, AnyFailure, OS>; - -pub type ValueChange = HandlerWrapper<(AnyObject, AnyValue), (), anyhow::Error, OS>; - -pub type SettingChange = HandlerWrapper<(AnyObject, AnySetting), (), anyhow::Error, OS>; - -#[derive(Default, Clone)] -pub struct GiteratedStack { - pub inner: Arc>, -} - -impl Deref for GiteratedStack { - type Target = GiteratedStackInner; - - fn deref(&self) -> &Self::Target { - &self.inner - } -} - -pub struct GiteratedStackInner { - operation_handlers: HashMap, HandlerTree>>, - value_getters: HashMap, ValueGetter>, - setting_getters: HashMap<&'static str, SettingGetter>, - value_change: HashMap, ValueChange>, - setting_change: HashMap, SettingChange>, - metadata_providers: Vec>, - pub metadata: RuntimeMetadata, - _marker: PhantomData, -} - -impl Default for GiteratedStackInner { - fn default() -> Self { - Self { - operation_handlers: Default::default(), - value_getters: Default::default(), - setting_getters: Default::default(), - value_change: Default::default(), - setting_change: Default::default(), - metadata_providers: Default::default(), - metadata: Default::default(), - _marker: Default::default(), - } - } -} - -impl Debug for GiteratedStack { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("GiteratedStack").finish() - } -} - -pub struct GiteratedStackBuilder { - inner: GiteratedStackInner, -} - -impl Default for GiteratedStackBuilder -where - GiteratedStack: HandlerResolvable<(Instance, ObjectRequest), OS>, - as HandlerResolvable<(Instance, ObjectRequest), OS>>::Error: Into, -{ - fn default() -> Self { - let mut stack = Self { - inner: Default::default(), - }; - - #[derive(Clone)] - struct InstanceResolver; - - let mut builder: SubstackBuilder = - SubstackBuilder::new(InstanceResolver); - - builder.object::(); - - stack.merge_builder(builder); - - stack - } -} - -impl GiteratedStackBuilder { - pub fn merge_builder( - &mut self, - mut builder: SubstackBuilder, - ) -> &mut Self { - for (target, handler) in builder.operation_handlers { - let tree = self.get_or_create_tree(&target); - - tree.push(handler); - } - - for (target, handler) in builder.value_getters { - assert!(self.inner.value_getters.insert(target, handler).is_none()); - } - - for (target, handler) in builder.setting_getters { - assert!(self.inner.setting_getters.insert(target, handler).is_none()); - } - - for (target, handler) in builder.value_change { - self.inner.value_change.insert(target, handler); - } - - for (target, handler) in builder.setting_change { - self.inner.setting_change.insert(target, handler); - } - - self.inner - .metadata_providers - .append(&mut builder.metadata_providers); - - self.inner.metadata.append(builder.metadata); - - self - } - - fn get_or_create_tree( - &mut self, - target: &ObjectOperationPair<'static>, - ) -> &mut HandlerTree> { - if self.inner.operation_handlers.contains_key(target) { - self.inner.operation_handlers.get_mut(target).unwrap() - } else { - self.inner - .operation_handlers - .insert(target.clone(), HandlerTree::default()); - - self.inner.operation_handlers.get_mut(target).unwrap() - } - } - - pub fn finish(self) -> GiteratedStack { - GiteratedStack { - inner: Arc::new(self.inner), - } - } -} - -impl GiteratedStack { - pub async fn value_update(&self, object: O, new_value: AnyValue, operation_state: &OS) - where - O: GiteratedObject + 'static, - { - trace!( - "value updated {}::{}", - O::object_name(), - new_value.kind().value_kind - ); - let object = AnyObject::new(object); - - // First, resolve a handler for the exact object value pair - if let Some(handler) = self.inner.value_change.get(&new_value.kind()) { - // TODO - let _ = handler - .handle((object.clone(), new_value.clone()), operation_state.clone()) - .await; - } - - // We need to resolve for `any` object and `any` value combination - let target = ObjectValuePair { - object_kind: "any", - value_kind: new_value.kind().value_kind, - }; - if let Some(handler) = self.inner.value_change.get(&target) { - // TODO - let _ = handler - .handle((object.clone(), new_value.clone()), operation_state.clone()) - .await; - } - - let target = ObjectValuePair { - object_kind: O::object_name(), - value_kind: "any", - }; - if let Some(handler) = self.inner.value_change.get(&target) { - // TODO - let _ = handler - .handle((object.clone(), new_value.clone()), operation_state.clone()) - .await; - } - - // Now resolve for both `any` - - let target = ObjectValuePair { - object_kind: "any", - value_kind: "any", - }; - if let Some(handler) = self.inner.value_change.get(&target) { - // TODO - let _ = handler - .handle((object.clone(), new_value.clone()), operation_state.clone()) - .await; - } - } - - pub async fn setting_update(&self, object: O, new_setting: S, operation_state: &OS) - where - O: GiteratedObject + 'static, - S: Setting + 'static, - { - trace!("setting updated {}::{}", O::object_name(), S::name()); - let target = ObjectSettingPair::from_types::(); - - if let Some(handler) = self.inner.setting_change.get(&target) { - let _ = handler - .handle( - (AnyObject::new(object), AnySetting::new::(new_setting)), - operation_state.clone(), - ) - .await; - } - } - - pub async fn new_object(&self, _new_object: &O, _operation_state: &StackOperationState) - where - O: GiteratedObject, - { - // TODO - } - - /// Writes a setting for the specified object. - pub async fn write_setting( - &self, - object: &O, - setting: S, - ) -> Result<(), OperationError<()>> - where - O: GiteratedObject + 'static + Clone, - S: Setting + 'static + Clone, - { - for provider in self.inner.metadata_providers.iter() { - if provider.provides_for(object as &dyn Any) { - let setting_meta = self - .inner - .metadata - .settings - .get(&ObjectSettingPair::from_types::()) - .ok_or_else(|| OperationError::Unhandled)?; - - let object_meta = self - .inner - .metadata - .objects - .get(O::object_name()) - .ok_or_else(|| OperationError::Unhandled)?; - - let result = provider - .write( - AnyObject::new(object.clone()), - object_meta, - AnySetting::new::(setting), - setting_meta, - ) - .await - .as_internal_error_with_context(format!("writing setting {}", S::name())); - - return result; - } - } - - Err(OperationError::Unhandled) - } - - /// Gets a setting for the specified object. - pub async fn new_get_setting(&self, object: &O) -> Result> - where - O: GiteratedObject + 'static + Clone, - S: Setting + 'static, - { - for provider in self.inner.metadata_providers.iter() { - if provider.provides_for(object as &dyn Any) { - trace!( - "Resolving setting {} for object {} from provider.", - S::name(), - O::object_name() - ); - - let setting_meta = self - .inner - .metadata - .settings - .get(&ObjectSettingPair::from_types::()) - .ok_or_else(|| OperationError::Unhandled)?; - - let object_meta = self - .inner - .metadata - .objects - .get(O::object_name()) - .ok_or_else(|| OperationError::Unhandled)?; - - let value = provider - .read(AnyObject::new(object.clone()), object_meta, setting_meta) - .await - .as_internal_error_with_context(format!("getting setting {}", S::name()))?; - - return serde_json::from_value(value) - .as_internal_error_with_context("deserializing setting"); - } - } - trace!( - "No provider registered for setting {} and object {}", - S::name(), - O::object_name() - ); - - Err(OperationError::Unhandled) - } -} - -impl GiteratedStack { - pub async fn get_setting( - &self, - object: AnyObject, - object_kind: String, - operation: GetSetting, - _operation_state: &OS, - ) -> Result>> { - trace!( - "Handling network {}::get_setting for {}", - object_kind, - operation.setting_name - ); - - for provider in self.inner.metadata_providers.iter() { - if provider.provides_for(object.deref()) { - let setting_meta = self - .inner - .metadata - .settings - .get(&ObjectSettingPair { - object_kind: &object_kind, - setting_name: &operation.setting_name, - }) - .ok_or_else(|| OperationError::Unhandled)?; - - let object_meta = self - .inner - .metadata - .objects - .get(&object_kind) - .ok_or_else(|| OperationError::Unhandled)?; - - let result = provider - .read(object.clone(), object_meta, setting_meta) - .await - .as_internal_error_with_context(format!( - "reading setting {}", - operation.setting_name - ))?; - - return (setting_meta.deserialize)(result).as_internal_error_with_context(format!( - "deserializing setting {}", - operation.setting_name - )); - } - } - - trace!("setting {} doesn't exist", operation.setting_name); - - Err(OperationError::Unhandled) - } -} - -#[async_trait::async_trait(?Send)] -impl ObjectBackend for GiteratedStack { - async fn object_operation( - &self, - in_object: O, - _operation_name: &str, - payload: D, - operation_state: &OS, - ) -> Result> - where - O: GiteratedObject + Debug + 'static, - D: GiteratedOperation + Debug + 'static, - D::Success: Clone, - D::Failure: Clone, - { - trace!( - "Object operation for {}::{}", - O::object_name(), - D::operation_name() - ); - // Erase object and operation types. - let object = AnyObject::new(in_object.clone()); - let operation = AnyOperation::new(payload); - - let raw_result = self - .new_operation_func(object, operation, operation_state.clone()) - .await; - - // Convert the dynamic result back into its concrete type - match raw_result { - Ok(result) => Ok(result.0.downcast_ref::().unwrap().clone()), - Err(err) => Err(match err { - OperationError::Internal(internal) => { - OperationError::Internal(internal.context(format!( - "operation {}::{} handler outcome", - O::object_name(), - D::operation_name() - ))) - } - OperationError::Operation(boxed_error) => OperationError::Operation( - boxed_error.0.downcast_ref::().unwrap().clone(), - ), - OperationError::Unhandled => OperationError::Unhandled, - }), - } - } - - async fn get_object( - &self, - object_str: &str, - operation_state: &OS, - ) -> Result, OperationError> - where - O: GiteratedObject + Debug + 'static, - { - // TODO: Authorization? - for (object_name, object_meta) in self.inner.metadata.objects.iter() { - if object_name != O::object_name() { - continue; - } - - if let Ok(object) = (object_meta.from_str)(object_str) { - return Ok(unsafe { - Object::new_unchecked(object.downcast_ref::().unwrap().clone(), self.clone()) - }); - } - } - - if let Some(handler) = self.operation_handlers.get(&ObjectOperationPair { - object_name: "any", - operation_name: "any", - }) { - let result = handler - .handle( - ( - AnyObject::new(Instance::from_str("giterated.dev").unwrap()), - AnyOperation::new(ObjectRequest(object_str.to_string())), - ), - operation_state.clone(), - ) - .await; - - match result { - Ok(success) => { - let object: &ObjectResponse = success.0.downcast_ref().unwrap(); - - return Ok(unsafe { - Object::new_unchecked(O::from_object_str(&object.0).unwrap(), self.clone()) - }); - } - Err(err) => match err { - OperationError::Operation(failure) => { - let failure: &ObjectRequestError = failure.0.downcast_ref().unwrap(); - - return Err(OperationError::Operation(failure.clone())); - } - OperationError::Internal(internal) => { - return Err(OperationError::Internal(internal)) - } - OperationError::Unhandled => {} - }, - }; - } - - Err(OperationError::Unhandled) - } -} - -// Placeholder -impl GiteratedStack { - pub async fn new_operation_func( - &self, - object: AnyObject, - operation: AnyOperation, - operation_state: OS, - ) -> Result> { - let operation_name = operation.kind().operation_name; - - if let Some(handler_tree) = self.inner.operation_handlers.get(&ObjectOperationPair { - object_name: "any", - operation_name: "any", - }) { - match handler_tree - .handle((object.clone(), operation.clone()), operation_state.clone()) - .await - { - Ok(success) => return Ok(success), - Err(err) => match err { - OperationError::Operation(operation) => { - return Err(OperationError::Operation(operation)) - } - OperationError::Internal(internal) => { - return Err(OperationError::Internal(internal)) - } - OperationError::Unhandled => {} - }, - } - } - - // We need to hijack get_value, set_setting, and get_setting. - if operation_name == "get_value" { - let get_value = operation - .downcast_ref::() - .ok_or_else(|| OperationError::Unhandled)?; - - let value_meta = self - .inner - .metadata - .values - .get(&ObjectValuePair { - object_kind: object.kind(), - value_kind: &get_value.value_name, - }) - .ok_or_else(|| OperationError::Unhandled)?; - let value_name = value_meta.name.clone(); - - trace!("Handling get_value for {}::{}", object.kind(), value_name); - - if let Some(handler) = self.inner.value_getters.get(&ObjectValuePair { - object_kind: "any", - value_kind: &get_value.value_name, - }) { - match handler - .handle( - (object.clone(), get_value.value_name.clone()), - operation_state.clone(), - ) - .await - { - Ok(success) => { - // self.value_update(in_object, success.clone(), operation_state) - // .await; - - return Ok(*(Box::new((value_meta.serialize)(success).unwrap()) - as Box) - .downcast() - .unwrap()); - } - Err(err) => { - match err { - OperationError::Operation(operation_error) => { - return Err(OperationError::Operation(operation_error)); - } - OperationError::Internal(internal) => { - // This DOES NOT result in an early return - warn!("An internal error occurred during a failable handler operation. {:?}", internal); - } - OperationError::Unhandled => { - // This DOES NOT result in an early return - } - } - } - } - } - - if let Some(handler) = self.inner.value_getters.get(&ObjectValuePair { - object_kind: object.kind(), - value_kind: "any", - }) { - match handler - .handle( - (object.clone(), get_value.value_name.clone()), - operation_state.clone(), - ) - .await - { - Ok(success) => { - // self.value_update(in_object, success.clone(), operation_state) - // .await; - - return Ok(*(Box::new((value_meta.serialize)(success).unwrap()) - as Box) - .downcast() - .unwrap()); - } - Err(err) => { - match err { - OperationError::Operation(operation_error) => { - return Err(OperationError::Operation(operation_error)); - } - OperationError::Internal(internal) => { - // This DOES NOT result in an early return - warn!("An internal error occurred during a failable handler operation. {:?}", internal); - } - OperationError::Unhandled => { - // This DOES NOT result in an early return - } - } - } - } - } - - if let Some(handler) = self.inner.value_getters.get(&ObjectValuePair { - object_kind: "any", - value_kind: "any", - }) { - match handler - .handle( - (object.clone(), get_value.value_name.clone()), - operation_state.clone(), - ) - .await - { - Ok(success) => { - // self.value_update(in_object, success.clone(), operation_state) - // .await; - - return Ok(*(Box::new((value_meta.serialize)(success).unwrap()) - as Box) - .downcast() - .unwrap()); - } - Err(err) => { - match err { - OperationError::Operation(operation_error) => { - return Err(OperationError::Operation(operation_error)); - } - OperationError::Internal(internal) => { - // This DOES NOT result in an early return - warn!("An internal error occurred during a failable handler operation. {:?}", internal); - } - OperationError::Unhandled => { - // This DOES NOT result in an early return - } - } - } - } - } - - if let Some(handler) = self.inner.value_getters.get(&ObjectValuePair { - object_kind: object.kind(), - value_kind: &get_value.value_name, - }) { - match handler - .handle( - (object.clone(), get_value.value_name.clone()), - operation_state.clone(), - ) - .await - { - Ok(success) => { - // self.value_update(in_object, success.clone(), operation_state) - // .await; - - return Ok(AnySuccess(Arc::new( - (value_meta.serialize)(success) - .as_internal_error_with_context("serializing value")?, - ))); - } - Err(err) => { - match err { - OperationError::Operation(operation_error) => { - return Err(OperationError::Operation(operation_error)); - } - OperationError::Internal(internal) => { - // This DOES NOT result in an early return - warn!("An internal error occurred during a failable handler operation. {:?}", internal); - } - OperationError::Unhandled => { - // This DOES NOT result in an early return - } - } - } - } - } - } else if operation.is::() { - let get_setting: &GetSetting = operation.downcast_ref().unwrap(); - let setting_name = get_setting.setting_name.clone(); - - let raw_result = self - .get_setting( - object.clone(), - object.kind().to_string(), - get_setting.clone(), - &operation_state, - ) - .await; - - let setting_meta = self - .metadata - .settings - .get(&ObjectSettingPair { - object_kind: object.kind(), - setting_name: &get_setting.setting_name, - }) - .ok_or_else(|| OperationError::Unhandled)?; - - return match raw_result { - Ok(success) => { - // Success is the setting type, serialize it - // let serialized = (setting_meta.serialize)(success).unwrap(); - - // Ok(serde_json::to_vec(&serialized).unwrap()) - // Ok(success.downcast_ref::().unwrap().clone()) - Ok(AnySuccess(Arc::new( - (setting_meta.serialize)(success).unwrap(), - ))) - } - Err(err) => Err(match err { - OperationError::Operation(failure) => { - // We know this is the right type - OperationError::Operation(*failure.downcast().unwrap()) - } - OperationError::Internal(internal) => { - OperationError::Internal(internal.context(format!( - "{}::get_setting::<{}> handler outcome", - object.kind(), - setting_name - ))) - } - OperationError::Unhandled => OperationError::Unhandled, - }), - }; - } else if operation.is::() { - let operation: &SetSetting = operation.downcast_ref().unwrap(); - let object_type = object.kind(); - - trace!( - "Handling {}::set_setting for {}", - object_type, - operation.setting_name - ); - - let setting_meta = self - .metadata - .settings - .get(&ObjectSettingPair { - object_kind: &object_type, - setting_name: &operation.setting_name, - }) - // TODO: Check this - .ok_or(OperationError::Operation(AnyFailure(Arc::new( - SetSettingError::InvalidSetting( - operation.setting_name.clone(), - object_type.to_string().clone(), - ), - ))))?; - - let setting = (setting_meta.deserialize)(operation.value.clone()) - .as_internal_error_with_context(format!( - "deserializing setting {} for object {}", - operation.setting_name, object_type - ))?; - - trace!( - "Deserialized setting {} for object {}", - operation.setting_name, - object_type, - ); - - for provider in self.metadata_providers.iter() { - if provider.provides_for(object.deref()) { - trace!( - "Resolved setting provider for setting {} for object {}", - operation.setting_name, - object_type, - ); - - let object_meta = self - .metadata - .objects - .get(object_type) - .ok_or_else(|| OperationError::Unhandled)?; - - let raw_result = provider - .write(object.clone(), object_meta, setting.clone(), setting_meta) - .await; - - return match raw_result { - Ok(_) => { - warn!("Setting updated not implemented"); - // (setting_meta.setting_updated)( - // object, - // setting, - // self.clone(), - // operation_state, - // ) - // .await; - - Ok(AnySuccess(Arc::new(()))) - } - Err(e) => Err(OperationError::Internal(e.context(format!( - "writing object {} setting {}", - object_type, operation.setting_name - )))), - }; - } - - trace!( - "Failed to resolve setting provider for setting {} for object {}", - operation.setting_name, - object_type, - ); - } - } else if operation.is::() { - let object_request: &ObjectRequest = operation.downcast_ref().unwrap(); - trace!("handling object request for {}", object_request.0); - - // TODO: Authorization? - for (_object_name, object_meta) in self.inner.metadata.objects.iter() { - if object_meta.name == "networked_object" { - // TODO: HACK - continue; - } - - if let Ok(object) = (object_meta.from_str)(&object_request.0) { - trace!("object request resolved as type {}", object_meta.name); - - return Ok(AnySuccess(Arc::new(ObjectResponse((object_meta.to_str)( - object, - ))))); - } - } - - if let Some(handler) = self.operation_handlers.get(&ObjectOperationPair { - object_name: "any", - operation_name: "any", - }) { - let result = handler - .handle( - ( - AnyObject::new(Instance::from_str("giterated.dev").unwrap()), - AnyOperation::new(ObjectRequest(object_request.0.to_string())), - ), - operation_state.clone(), - ) - .await; - - match result { - Ok(success) => { - let object: &ObjectResponse = success.0.downcast_ref().unwrap(); - - return Ok(AnySuccess(Arc::new(object.clone()))); - } - Err(_err) => { - todo!() - } - } - } - - return Err(OperationError::Unhandled); - } - - trace!( - "Object operation for {}::{} is not special case", - object.kind(), - operation_name - ); - - // Resolve the handler from our handler tree - let handler_tree = self - .inner - .operation_handlers - .get(&operation.kind()) - .ok_or_else(|| OperationError::Unhandled)?; - - trace!( - "Object operation for {}::{} handler tree resolved", - object.kind(), - operation_name - ); - - handler_tree - .handle((object, operation), operation_state.clone()) - .await - } -} - -/// Defines a type that is a valid Giterated runtime state. -/// -/// This allows for extraction of state in handlers, based on a -/// [`FromOperationState`] impl on (what is in this case) [`Self`]. -pub trait GiteratedStackState: Send + Sync + Clone {} - -impl GiteratedStackState for T {} - -#[async_trait::async_trait(?Send)] -impl HandlerResolvable for Option -where - T: HandlerResolvable, -{ - type Error = MissingValue; - - async fn from_handler_state( - required_parameters: &R, - operation_state: &S, - ) -> Result, MissingValue> { - Ok(T::from_handler_state(required_parameters, operation_state) - .await - .ok()) - } -} diff --git a/giterated-stack/src/substack.rs b/giterated-stack/src/substack.rs deleted file mode 100644 index 994527b..0000000 --- a/giterated-stack/src/substack.rs +++ /dev/null @@ -1,342 +0,0 @@ -use std::{collections::HashMap, marker::PhantomData, sync::Arc}; - -use anyhow::Error; -use futures_util::FutureExt; -use giterated_models::{ - error::OperationError, - instance::Instance, - object::{GiteratedObject, ObjectRequest, ObjectResponse}, - operation::GiteratedOperation, - settings::Setting, - value::GiteratedObjectValue, -}; -use tracing::{info, trace}; - -use crate::{ - handler::HandlerWrapper, provider::MetadataProvider, AnyFailure, AnyObject, AnyOperation, - AnySetting, AnySuccess, AnyValue, GiteratedStack, GiteratedStackState, HandlerResolvable, - IntoGiteratedHandler, MaybeDynamicObject, MaybeDynamicValue, ObjectOperationPair, - ObjectSettingPair, ObjectValuePair, OperationHandler, OperationState, RuntimeMetadata, - SettingChange, SettingGetter, StackOperationState, ValueChange, ValueGetter, -}; - -pub struct SubstackBuilder { - pub(crate) operation_handlers: HashMap, OperationHandler>, - pub(crate) value_getters: HashMap, ValueGetter>, - pub(crate) setting_getters: HashMap<&'static str, SettingGetter>, - pub(crate) metadata: RuntimeMetadata, - pub(crate) value_change: HashMap, ValueChange>, - pub(crate) metadata_providers: Vec>, - pub(crate) setting_change: HashMap, SettingChange>, - - pub(crate) state: S, - _marker: PhantomData, -} - -impl SubstackBuilder { - pub fn new(state: S) -> Self { - Self { - operation_handlers: Default::default(), - value_getters: Default::default(), - setting_getters: Default::default(), - metadata: Default::default(), - value_change: Default::default(), - metadata_providers: Default::default(), - setting_change: Default::default(), - state, - _marker: PhantomData::default(), - } - } -} - -impl SubstackBuilder { - /// Insert an operation handler into the runtime builder. - /// - /// # Type Registration - /// Inserting the handler will automatically, if required, register the operation type of the - /// handler. It will **not** register the object type automatically. - pub fn operation(&mut self, handler: H) -> &mut Self - where - O: GiteratedObject + Clone, - D: GiteratedOperation + Clone, - H: IntoGiteratedHandler<(O, D), A, S, OS, Result>> - + Send - + Sync - + 'static, - O: 'static, - D: 'static, - D::Failure: std::fmt::Debug + 'static, - D::Success: 'static, - OS: Clone, - { - let wrapped = HandlerWrapper::new(self.state.clone(), handler); - - let wrapped = wrapped.map( - |(any_object, any_operation): &(AnyObject, AnyOperation), _state: &OS| { - Ok(( - any_object.downcast_ref::().unwrap().clone(), - any_operation.downcast_ref::().unwrap().clone(), - )) - }, - ); - - let wrapped = wrapped.map_return(|ret_val, _state| match ret_val { - Ok(success) => Ok(AnySuccess(Arc::new(success))), - Err(err) => Err(match err { - OperationError::Operation(failure) => { - OperationError::Operation(AnyFailure(Arc::new(failure))) - } - OperationError::Internal(err) => OperationError::Internal(err), - OperationError::Unhandled => OperationError::Unhandled, - }), - }); - - let pair = ObjectOperationPair::from_types::(); - - self.operation_handlers.insert(pair, wrapped); - - self.metadata.register_operation::(); - - self - } - - /// Register a [`GiteratedObjectValue`] type with the runtime, providing - /// its associated handler for [`GetValue`]. - /// - /// # Type Registration - /// This will register the provided [`GiteratedObjectValue`] type for its matching / specified - /// object type. It will **not** register the object type automatically. - pub fn dynamic_value(&mut self, handler: F) -> &mut Self - where - O: MaybeDynamicObject + 'static, - F: IntoGiteratedHandler< - (O, String), - A, - S, - OS, - Result>, - > + Send - + Sync, - F: 'static, - { - let wrapped = HandlerWrapper::new(self.state.clone(), handler); - - let wrapped = wrapped.map(|(any_object, name): &(AnyObject, String), _state: &OS| { - Ok((O::from_any(any_object), name.clone())) - }); - - let wrapped = wrapped.map_return(|ret_val, _state| match ret_val { - Ok(success) => Ok(success.into_any()), - Err(err) => Err(match err { - OperationError::Operation(failure) => OperationError::Internal(failure.into()), - OperationError::Internal(err) => OperationError::Internal(err), - OperationError::Unhandled => OperationError::Unhandled, - }), - }); - - assert!(self - .value_getters - .insert( - ObjectValuePair { - object_kind: O::object_name(), - value_kind: "any" - }, - wrapped - ) - .is_none()); - - self - } - - pub fn value_change(&mut self, handler: F) -> &mut Self - where - F: IntoGiteratedHandler<(O, V), A, S, OS, Result<(), OperationError>> - + Send - + Sync, - V: MaybeDynamicValue + Clone + 'static, - O: 'static + MaybeDynamicObject, - V: 'static, - F: 'static, - { - let wrapped = HandlerWrapper::new(self.state.clone(), handler); - - let wrapped = wrapped.map( - |(any_object, any_value): &(AnyObject, AnyValue), _state: &OS| { - Ok((O::from_any(any_object), V::from_any(any_value))) - }, - ); - - assert!(self - .value_change - .insert( - ObjectValuePair { - object_kind: O::object_name(), - value_kind: V::value_name() - }, - wrapped - ) - .is_none()); - - self - } - - pub fn object_metadata_provider(&mut self, provider: Box) -> &mut Self { - self.metadata_providers.push(provider); - - self - } - - /// Register a [`GiteratedObject`] type with the runtime. - /// - /// # Type Registration - /// This will register the provided object type. - pub fn object(&mut self) -> &mut Self - where - GiteratedStack: HandlerResolvable<(Instance, ObjectRequest), OS>, - as HandlerResolvable<(Instance, ObjectRequest), OS>>::Error: - Into, - { - self.metadata.register_object::(); - - // Insert handler so ObjectRequest is handled properly - - self.operation( - move |_object: Instance, - operation: ObjectRequest, - _state: S, - stack: GiteratedStack| { - let operation = operation.clone(); - async move { - for (_object_name, object_meta) in stack.inner.metadata.objects.iter() { - if (object_meta.from_str)(&operation.0).is_ok() { - return Ok(ObjectResponse(operation.0.clone())); - } - } - - Err(OperationError::Unhandled) - } - .boxed_local() - }, - ); - - self - } -} - -impl SubstackBuilder { - /// Register a [`Setting`] type with the runtime. - /// - /// # Type Registration - /// This will register the provided setting type. - pub fn setting( - &mut self, - ) -> &mut Self { - self.metadata.register_setting::(); - - self - } - - /// Register a [`GiteratedObjectValue`] that is also a [`Setting`], which - /// allows for automatic value updates. - pub fn value_setting< - O: GiteratedObject + 'static + Clone, - T: GiteratedObjectValue + Setting + 'static + Clone, - >( - &mut self, - ) -> &mut Self { - self.metadata.register_setting::(); - self.metadata.register_value::(); - - self.setting_change.insert( - ObjectSettingPair::from_types::(), - HandlerWrapper::new( - (), - move |object: AnyObject, - setting: AnySetting, - _state: _, - OperationState(operation_state): OperationState, - stack: GiteratedStack| { - trace!( - "value setting updated {}::{}", - O::object_name(), - T::value_name() - ); - let object = object.clone(); - async move { - let object = object.downcast_ref::().unwrap(); - let setting = setting.downcast_ref::().unwrap(); - stack - .value_update( - object.clone(), - AnyValue::new(setting.clone()), - &operation_state, - ) - .await; - Ok(()) - } - .boxed_local() - }, - ), - ); - - let wrapped = HandlerWrapper::new( - self.state.clone(), - |object: AnyObject, - _name: String, - _state: _, - stack: GiteratedStack| { - info!("a setting handler called"); - let object = object.clone(); - async move { - match stack - .new_get_setting::(object.downcast_ref().unwrap()) - .await - { - Ok(setting) => Ok(AnyValue::new(setting)), - Err(err) => { - panic!("Error: {:?}", err); - } - } - } - .boxed_local() - }, - ); - - self.value_getters - .insert(ObjectValuePair::from_types::(), wrapped); - - self - } -} - -// Placeholder -impl SubstackBuilder { - pub fn dynamic_operation(&mut self, handler: H) -> &mut Self - where - H: IntoGiteratedHandler< - (AnyObject, AnyOperation), - A, - S, - OS, - Result>, - > + Send - + Sync - + 'static, - OS: Clone + Send + Sync + 'static, - { - let wrapped = HandlerWrapper::new(self.state.clone(), handler); - - self.operation_handlers.insert( - ObjectOperationPair { - object_name: "any", - operation_name: "any", - }, - wrapped, - ); - - self - } -} -#[derive(Debug, Clone, thiserror::Error)] -#[error("downcast error")] -pub struct DowncastError; diff --git a/giterated-stack/src/update.rs b/giterated-stack/src/update.rs deleted file mode 100644 index 1b043e1..0000000 --- a/giterated-stack/src/update.rs +++ /dev/null @@ -1,191 +0,0 @@ -use futures_util::{future::BoxFuture, FutureExt}; -use giterated_models::{ - object::GiteratedObject, - settings::{AnySetting, Setting}, - value::{AnyValue, GiteratedObjectValue}, -}; - -use crate::StackOperationState; - -#[async_trait::async_trait] -pub trait HandleValueUpdate> { - async fn handle_value_update( - &mut self, - object: O, - value_name: String, - value: V, - operation_state: &StackOperationState, - ) -> Result<(), ()>; -} - -#[async_trait::async_trait] -impl HandleValueUpdate for F -where - F: Fn(O, String, V, &StackOperationState) -> BoxFuture<'static, Result<(), ()>> + Send + Sync, - O: GiteratedObject + Send + Sync + 'static, - V: GiteratedObjectValue + Send + Sync + 'static, -{ - async fn handle_value_update( - &mut self, - object: O, - value_name: String, - value: V, - operation_state: &StackOperationState, - ) -> Result<(), ()> { - self(object, value_name, value, operation_state).await - } -} - -#[async_trait::async_trait] -pub trait HandleSettingUpdate { - async fn handle_setting_update( - &mut self, - object: O, - setting_name: String, - setting: S, - operation_state: &StackOperationState, - ) -> Result<(), ()>; -} - -#[async_trait::async_trait] -impl HandleSettingUpdate for F -where - F: Fn(O, String, S, &StackOperationState) -> BoxFuture<'static, Result<(), ()>> + Send + Sync, - O: GiteratedObject + Send + Sync + 'static, - S: Setting + Send + Sync + 'static, -{ - async fn handle_setting_update( - &mut self, - object: O, - setting_name: String, - setting: S, - operation_state: &StackOperationState, - ) -> Result<(), ()> { - self(object, setting_name, setting, operation_state).await - } -} - -#[async_trait::async_trait] -pub trait ValueUpdatedHandler { - async fn value_updated(&mut self, object: &O, value_name: &str, value: AnyValue<()>); -} - -#[async_trait::async_trait] -pub trait SettingUpdatedHandler { - async fn setting_updated(&mut self, object: &O, setting_name: &str, setting: AnySetting); -} - -#[derive(Debug, Clone, Hash, PartialEq, Eq)] -pub struct ValueUpdateKind { - pub object_kind: String, - pub value_name: String, -} - -#[derive(Debug, Clone, Hash, PartialEq, Eq)] -pub struct SettingUpdateKind { - pub object_kind: String, - pub setting_name: String, -} - -pub struct HandleSettingUpdatedFunction { - pub target: SettingUpdateKind, - pub function: Box< - dyn FnOnce( - String, - String, - AnySetting, - StackOperationState, - ) -> BoxFuture<'static, Result<(), ()>> - + Send - + Sync, - >, -} - -impl HandleSettingUpdatedFunction { - pub fn new< - S: Setting + Send + Sync, - T: HandleSettingUpdate + 'static + Clone + Send + Sync, - O: GiteratedObject + Send + Sync, - >( - handler: T, - setting_name: &str, - ) -> Self { - Self { - target: SettingUpdateKind { - object_kind: O::object_name().to_string(), - setting_name: setting_name.to_string(), - }, - - function: Box::new(move |object, _setting_name, _value, _state| { - async move { - let _handler = handler; - - let _object = match O::from_str(&object) { - Ok(object) => object, - Err(_) => return Err(()), - }; - - // let setting: S = serde_json::from_value( - // let _ = handler - // .handle_setting_update(object, setting_name, setting, &state) - // .await;value.0).unwrap(); - - Ok(()) - } - .boxed() - }), - } - } -} - -pub struct HandleValueUpdatedFunction { - pub target: ValueUpdateKind, - pub function: Box< - dyn FnOnce( - String, - String, - AnySetting, - StackOperationState, - ) -> BoxFuture<'static, Result<(), ()>> - + Send - + Sync, - >, -} - -impl HandleValueUpdatedFunction { - pub fn new< - V: GiteratedObjectValue + Send + Sync, - T: HandleValueUpdate + 'static + Clone + Send + Sync, - O: GiteratedObject + Send + Sync, - >( - handler: T, - value_name: &str, - ) -> Self { - Self { - target: ValueUpdateKind { - object_kind: O::object_name().to_string(), - value_name: value_name.to_string(), - }, - - function: Box::new(move |object, _setting_name, _value, _state| { - async move { - let _handler = handler; - - let _object = match O::from_str(&object) { - Ok(object) => object, - Err(_) => return Err(()), - }; - - // let setting: V = serde_json::from_value(value.0).unwrap(); - - // let _ = handler - // .handle_value_update(object, setting_name, setting, &state) - // .await; - - Ok(()) - } - .boxed() - }), - } - } -} diff --git a/plugins/README.md b/plugins/README.md new file mode 100644 index 0000000..970f289 --- /dev/null +++ b/plugins/README.md @@ -0,0 +1,3 @@ +# Plugins + +Giterated uses a Plugin system to allow for easy extension, but also to separate concerns in the core. \ No newline at end of file diff --git a/plugins/example-plugin/Cargo.toml b/plugins/example-plugin/Cargo.toml new file mode 100644 index 0000000..7021024 --- /dev/null +++ b/plugins/example-plugin/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "example-plugin" +version = "0.1.0" +edition = "2021" + +[lib] +name = "example_plugin_dylib" +path = "src/lib.rs" +crate-type = ["dylib"] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +giterated-plugin = { path = "../../giterated-plugin" } +giterated-plugin-sys = { path = "../../giterated-plugin/giterated-plugin-sys" } +dlopen2 = "0.6" +tracing-subscriber = "0.3" +giterated-models = { path = "../../giterated-models" } +tracing = "0.1" +serde_json = "1.0" +anyhow = "1" diff --git a/plugins/example-plugin/src/lib.rs b/plugins/example-plugin/src/lib.rs new file mode 100644 index 0000000..3125afc --- /dev/null +++ b/plugins/example-plugin/src/lib.rs @@ -0,0 +1,95 @@ +use std::sync::OnceLock; + +use giterated_models::{ + error::OperationError, + instance::Instance, + object::ObjectRequest, + user::{DisplayName, User}, +}; +use giterated_plugin::{ + handle::PluginInitializationState, + new_stack::{FFIPluginMeta, PluginState}, + vtable::{HostVTable, InitializationVTable}, +}; +use giterated_plugin_sys::PluginStackBuilder; +use tracing::{info, trace_span, Level}; + +static INIT_VTABLE: OnceLock = OnceLock::new(); + +#[no_mangle] +pub extern "C" fn plugin_meta() -> FFIPluginMeta { + const PLUGIN_NAME: &str = "Example Plugin"; + const PLUGIN_VERSION: &str = "1.0.0"; + + FFIPluginMeta { + name: PLUGIN_NAME.as_ptr(), + name_len: PLUGIN_NAME.len(), + version: PLUGIN_VERSION.as_ptr(), + version_len: PLUGIN_VERSION.len(), + } +} + +#[no_mangle] +pub extern "C" fn load_host_vtable(_vtable: &HostVTable) { + println!("Loading vtable"); +} + +#[no_mangle] +pub extern "C" fn load_initialization_vtable(init_vtable: &InitializationVTable) { + INIT_VTABLE.set(init_vtable.clone()).unwrap(); + println!("Loaded initialization vtable"); +} + +#[no_mangle] +pub extern "C" fn initialize() -> PluginState { + tracing_subscriber::fmt() + .pretty() + .with_thread_names(true) + .with_max_level(Level::TRACE) + .init(); + + PluginState { + inner: Box::into_raw(Box::new(())), + } +} + +#[no_mangle] +pub extern "C" fn initialize_registration( + state: *mut PluginInitializationState, +) -> *mut PluginInitializationState { + let _guard: tracing::span::EnteredSpan = trace_span!("initialize_registration").entered(); + let init_vtable = INIT_VTABLE.get().unwrap(); + let mut builder = PluginStackBuilder::new((), state, init_vtable); + + builder.object::().object::(); + builder.operation(handler); + builder.value(value_getter); + // builder.setting_getter(setting_getter); + + state +} + +async fn handler( + state: (), + _object: Instance, + _operation: ObjectRequest, +) -> Result<(), OperationError<()>> { + info!("handling operation!"); + + todo!() +} + +async fn value_getter( + state: (), + object: User, +) -> Result> { + info!("OwO, value gotten!"); + + Ok(DisplayName(String::from("heya!"))) +} + +// fn setting_getter(state: (), object: User) -> Result { +// info!("OwO, setting gotten!"); + +// Ok(DisplayName(String::from("heya! (but from a setting)"))) +// } diff --git a/plugins/example-plugin/src/main.rs b/plugins/example-plugin/src/main.rs new file mode 100644 index 0000000..3e91522 --- /dev/null +++ b/plugins/example-plugin/src/main.rs @@ -0,0 +1,62 @@ +use giterated_models::{ + instance::Instance, + object::{GiteratedObject, ObjectRequest}, + operation::GiteratedOperation, + settings::{GetSetting, Setting}, + user::{DisplayName, User}, + value::GetValue, +}; +use giterated_plugin::{handle::PluginHandle, new_stack::Runtime, GiteratedPluginApi}; +use tracing::{info, Level}; + +fn main() { + tracing_subscriber::fmt() + .pretty() + .with_thread_names(true) + .with_max_level(Level::TRACE) + .init(); + + let mut handle = PluginHandle::from_dylib("example_plugin_dylib.dll").unwrap(); + + let mut runtime = Runtime::<()>::new(); + + runtime.insert_plugin(handle); + + let object_request = ObjectRequest(String::from("foobar")); + + match runtime.handle( + Instance::object_name(), + ObjectRequest::operation_name(), + "meow", + &serde_json::to_vec(&object_request).unwrap(), + ) { + Ok(success) => info!("handler call success!"), + Err(_) => info!("handler call error"), + } + + match runtime.handle( + User::object_name(), + >::operation_name(), + "amber:giterated.dev", + &serde_json::to_vec(&GetValue { + value_name: String::from("display_name"), + }) + .unwrap(), + ) { + Ok(success) => info!("get_value handler call success!"), + Err(_) => info!("get_value handler call error"), + } + + match runtime.handle( + User::object_name(), + >::operation_name(), + "amber:giterated.dev", + &serde_json::to_vec(&GetSetting { + setting_name: DisplayName::name().to_string(), + }) + .unwrap(), + ) { + Ok(success) => info!("get_setting handler call success!"), + Err(_) => info!("get_setting handler call error"), + } +} diff --git a/plugins/giterated-backend/Cargo.toml b/plugins/giterated-backend/Cargo.toml new file mode 100644 index 0000000..c48052e --- /dev/null +++ b/plugins/giterated-backend/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "giterated-backend" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +giterated-plugin = { path = "../../giterated-plugin" } +giterated-models = { path = "../../giterated-models" } +serde = { version = "1.0", features = [ "derive" ]} +anyhow = "1" +thiserror = "1" +sqlx = { version = "0.7", features = [ "runtime-tokio", "tls-native-tls", "postgres", "macros", "migrate", "chrono" ] } +tokio = { version = "1.32", features = [ "full" ] } +giterated-plugin-sys = { path = "../../giterated-plugin/giterated-plugin-sys" } +toml = { version = "0.8" } +tracing = "0.1" +tracing-subscriber = "0.3" +serde_json = "1.0" diff --git a/plugins/giterated-backend/src/handlers.rs b/plugins/giterated-backend/src/handlers.rs new file mode 100644 index 0000000..a6a6118 --- /dev/null +++ b/plugins/giterated-backend/src/handlers.rs @@ -0,0 +1,28 @@ +use std::sync::Arc; + +use giterated_models::{ + error::{OperationError, RepositoryError, UserError}, + repository::{Repository, RepositoryInfoRequest, RepositorySummary, RepositoryView}, + user::{User, UserRepositoriesRequest}, +}; +use giterated_plugin::new_stack::{runtime_handler::RuntimeHandle, OperationState, Runtime, State}; + +use crate::DatabaseBackend; + +pub async fn user_get_repositories( + state: DatabaseBackend, + object: User, + request: UserRepositoriesRequest, +) -> Result, OperationError> { + todo!() +} + +pub async fn repository_info( + state: DatabaseBackend, + object: Repository, + request: RepositoryInfoRequest, + runtime: RuntimeHandle, + State(operation_state): State, +) -> Result> { + todo!() +} diff --git a/plugins/giterated-backend/src/lib.rs b/plugins/giterated-backend/src/lib.rs new file mode 100644 index 0000000..4f4e49b --- /dev/null +++ b/plugins/giterated-backend/src/lib.rs @@ -0,0 +1,16 @@ +pub mod handlers; +pub mod value; + +use giterated_models::{instance::Instance, repository::Repository, user::User}; +use giterated_plugin_sys::{PluginStackBuilder, ValueSettingExt}; +use handlers::{repository_info, user_get_repositories}; +use sqlx::PgPool; +use value::{user_get_bio, user_get_display_name, user_set_bio, user_set_display_name}; + +/// A backend implementation which attempts to resolve data from the instance's database. +#[derive(Debug, Clone)] +#[allow(unused)] +pub struct DatabaseBackend { + pub(self) our_instance: Instance, + pub(self) pool: PgPool, +} diff --git a/plugins/giterated-backend/src/value.rs b/plugins/giterated-backend/src/value.rs new file mode 100644 index 0000000..ea9a291 --- /dev/null +++ b/plugins/giterated-backend/src/value.rs @@ -0,0 +1,36 @@ +use giterated_models::{ + error::OperationError, + user::{Bio, DisplayName, User}, +}; + +use crate::DatabaseBackend; + +pub async fn user_get_display_name( + state: DatabaseBackend, + user: User, +) -> Result> { + todo!() +} + +pub async fn user_set_display_name( + state: DatabaseBackend, + user: User, + display_name: DisplayName, +) -> Result<(), OperationError> { + todo!() +} + +pub async fn user_get_bio( + state: DatabaseBackend, + user: User, +) -> Result> { + todo!() +} + +pub async fn user_set_bio( + state: DatabaseBackend, + user: User, + bio: Bio, +) -> Result<(), OperationError> { + todo!() +} diff --git a/plugins/giterated-issues/Cargo.toml b/plugins/giterated-issues/Cargo.toml new file mode 100644 index 0000000..024aef8 --- /dev/null +++ b/plugins/giterated-issues/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "giterated-issues" +version = "0.1.0" +edition = "2021" + +[lib] +name = "giterated_issues" +path = "src/lib.rs" +crate-type = ["rlib", "dylib"] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +giterated-plugin = { path = "../../giterated-plugin" } +giterated-models = { path = "../../giterated-models" } +serde = { version = "1.0", features = [ "derive" ]} +anyhow = "1" +thiserror = "1" +sqlx = { version = "0.7", features = [ "runtime-tokio", "tls-native-tls", "postgres", "macros", "migrate", "chrono" ] } +tokio = { version = "1.32", features = [ "full" ] } +giterated-plugin-sys = { path = "../../giterated-plugin/giterated-plugin-sys" } +toml = { version = "0.8" } +tracing = "0.1" +tracing-subscriber = "0.3" +serde_json = "1.0" diff --git a/plugins/giterated-issues/src/db.rs b/plugins/giterated-issues/src/db.rs new file mode 100644 index 0000000..3471fd9 --- /dev/null +++ b/plugins/giterated-issues/src/db.rs @@ -0,0 +1,24 @@ +use giterated_models::{repository::Repository, user::User}; + +use crate::operations::CommentVisibility; + +/// An [`Issue`]'s database representation. +#[derive(Debug, sqlx::FromRow)] +pub struct IssueRow { + pub id: i32, + #[sqlx(try_from = "String")] + pub repository: Repository, + #[sqlx(try_from = "String")] + pub author: User, + pub creation_date: i32, + pub issue_name: String, + pub contents: Option, +} + +#[derive(Debug, sqlx::FromRow)] +pub struct IssueCommentRow { + #[sqlx(try_from = "String")] + pub author: User, + pub contents: Option, + pub visibility: CommentVisibility, +} diff --git a/plugins/giterated-issues/src/handlers.rs b/plugins/giterated-issues/src/handlers.rs new file mode 100644 index 0000000..4463009 --- /dev/null +++ b/plugins/giterated-issues/src/handlers.rs @@ -0,0 +1,118 @@ +use crate::db::IssueRow; +use crate::setting::Contents; +use crate::value::{Author, CommentCount, CreationDate, Name}; +use crate::IssuesPluginState; +use crate::{ + operations::{ + CreateIssueRequest, IssueCreationError, IssueEditError, IssueEditRequest, + IssuePostCommentError, IssuePostCommentRequest, IssueQueryError, QueryIssuesRequest, + }, + Issue, +}; +use giterated_models::error::IntoInternalError; +use giterated_models::user::User; +use giterated_models::{error::OperationError, repository::Repository}; +use sqlx::PgPool; + +pub async fn create_issue_request( + state: IssuesPluginState, + repository: Repository, + request: CreateIssueRequest, +) -> Result> { + // TODO: AUTHN & AUTHZ + let issue = sqlx::query_as!( + IssueRow, + r#"INSERT INTO issues VALUES (null, $1, $2, $3, $4, $5) RETURNING *"#, + repository.to_string(), + request.author.to_string(), + 0, + request.name, + request.contents, + ) + .fetch_one(&state.pool) + .await + .as_internal_error_with_context("creating issue in db")?; + + Ok(Issue { + repository, + id: issue.id as u32, + }) +} + +pub async fn query_issues_request( + state: IssuesPluginState, + repository: Repository, + request: QueryIssuesRequest, +) -> Result, OperationError> { + // TODO: AUTHN & AUTHZ + todo!() +} + +pub async fn edit_issue_request( + state: IssuesPluginState, + issue: Issue, + request: IssueEditRequest, +) -> Result<(), OperationError> { + // TODO: AUTHN & AUTHZ + todo!() +} + +pub async fn issue_post_comment_request( + state: IssuesPluginState, + issue: Issue, + request: IssuePostCommentRequest, +) -> Result> { + // TODO: AUTHN & AUTHZ + todo!() +} + +pub async fn issue_value_author( + state: IssuesPluginState, + issue: Issue, +) -> Result> { + todo!() +} + +pub async fn issue_value_creation_date( + state: IssuesPluginState, + issue: Issue, +) -> Result> { + todo!() +} + +pub async fn issue_value_comment_count( + state: IssuesPluginState, + issue: Issue, +) -> Result> { + todo!() +} + +pub async fn issue_set_setting_name( + state: IssuesPluginState, + issue: Issue, + name: Name, +) -> Result<(), OperationError> { + todo!() +} + +pub async fn issue_get_setting_name( + state: IssuesPluginState, + issue: Issue, +) -> Result> { + todo!() +} + +pub async fn issue_set_setting_contents( + state: IssuesPluginState, + issue: Issue, + contents: Contents, +) -> Result<(), OperationError> { + todo!() +} + +pub async fn issue_get_setting_contents( + state: IssuesPluginState, + issue: Issue, +) -> Result> { + todo!() +} diff --git a/plugins/giterated-issues/src/lib.rs b/plugins/giterated-issues/src/lib.rs new file mode 100644 index 0000000..9e84f14 --- /dev/null +++ b/plugins/giterated-issues/src/lib.rs @@ -0,0 +1,193 @@ +use std::{ + fmt::Display, + str::FromStr, + sync::{Arc, OnceLock}, +}; + +use anyhow::Error; +use giterated_models::{object::GiteratedObject, repository::Repository}; +use giterated_plugin::{ + handle::PluginInitializationState, + new_stack::{FFIPluginMeta, PluginState}, + vtable::{HostVTable, InitializationVTable}, +}; +use giterated_plugin_sys::PluginStackBuilder; +use handlers::{ + create_issue_request, edit_issue_request, issue_get_setting_contents, issue_get_setting_name, + issue_post_comment_request, issue_set_setting_contents, issue_set_setting_name, + issue_value_author, issue_value_comment_count, issue_value_creation_date, query_issues_request, +}; +use serde::{Deserialize, Serialize}; +use setting::{Contents, NotificationsOverride}; +use sqlx::{postgres::PgConnectOptions, PgPool}; +use tokio::{ + fs::File, + io::AsyncReadExt, + runtime::Runtime, + spawn, + task::{block_in_place, spawn_blocking}, +}; +use toml::Table; +use tracing::{debug, info}; +use value::{Author, CommentCount, CreationDate, Name}; + +pub mod db; +pub mod handlers; +pub mod operations; +pub mod setting; +pub mod value; + +/// An issue, defined by the repository which owns it and its index. +/// +/// # Textual Format +/// An issue's textual format is defined as: +/// +/// `@{index: u32}:{repository: Repository}` +#[derive(Hash, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +pub struct Issue { + pub repository: Repository, + pub id: u32, +} + +impl Display for Issue { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(&format!("{}:{}", self.id, self.repository)) + } +} + +impl GiteratedObject for Issue { + fn object_name() -> &'static str { + "issue" + } + + fn home_uri(&self) -> String { + self.repository.home_uri() + } + + fn from_object_str(object_str: &str) -> Result { + Ok(Issue::from_str(object_str)?) + } +} + +impl FromStr for Issue { + type Err = IssueParseError; + + fn from_str(s: &str) -> Result { + let (index, repository) = s.split_once(':').ok_or_else(|| IssueParseError)?; + + let id: u32 = index.parse().map_err(|_| IssueParseError)?; + let repository = Repository::from_str(repository).map_err(|_| IssueParseError)?; + + Ok(Self { repository, id }) + } +} + +#[derive(Debug, thiserror::Error)] +#[error("error parsing issue")] +pub struct IssueParseError; + +static INIT_VTABLE: OnceLock = OnceLock::new(); +static ASYNC_RUNTIME: OnceLock = OnceLock::new(); + +#[no_mangle] +pub extern "C" fn plugin_meta() -> FFIPluginMeta { + const PLUGIN_NAME: &str = "Giterated [Issues]"; + const PLUGIN_VERSION: &str = "0.1.0"; + + FFIPluginMeta { + name: PLUGIN_NAME.as_ptr(), + name_len: PLUGIN_NAME.len(), + version: PLUGIN_VERSION.as_ptr(), + version_len: PLUGIN_VERSION.len(), + } +} + +#[no_mangle] +pub extern "C" fn load_host_vtable(_vtable: &HostVTable) { + println!("Loading vtable"); +} + +#[no_mangle] +pub extern "C" fn load_initialization_vtable(init_vtable: &InitializationVTable) { + INIT_VTABLE.set(init_vtable.clone()).unwrap(); + println!("Loaded initialization vtable"); +} + +#[no_mangle] +pub extern "C" fn initialize() -> PluginState { + // tracing_subscriber::fmt() + // .pretty() + // .with_thread_names(true) + // .with_max_level(Level::TRACE) + // .init(); + + PluginState { + inner: Box::into_raw(Box::new(())), + } +} + +#[no_mangle] +pub extern "C" fn initialize_registration( + state: *mut PluginInitializationState, +) -> *mut PluginInitializationState { + let runtime = Runtime::new().unwrap(); + + // let _guard: tracing::span::EnteredSpan = trace_span!("initialize_registration").entered(); + let init_vtable = INIT_VTABLE.get().unwrap(); + + let db_pool = runtime.block_on(async { + let config: Table = { + let mut file = File::open("Giterated.toml").await.unwrap(); + let mut text = String::new(); + file.read_to_string(&mut text).await.unwrap(); + text.parse().unwrap() + }; + let db_conn_options = PgConnectOptions::new() + .host(config["postgres"]["host"].as_str().unwrap()) + .port(config["postgres"]["port"].as_integer().unwrap() as u16) + .database(config["postgres"]["database"].as_str().unwrap()) + .username(config["postgres"]["user"].as_str().unwrap()) + .password(config["postgres"]["password"].as_str().unwrap()); + let db_pool = PgPool::connect_with(db_conn_options).await.unwrap(); + + debug!("Running database migrations..."); + // sqlx::migrate!().run(&db_pool).await.unwrap(); + info!("Connected"); + + db_pool + }); + + ASYNC_RUNTIME.set(runtime).unwrap(); + + let plugin_state = IssuesPluginState { pool: db_pool }; + + let mut builder: PluginStackBuilder<'_, IssuesPluginState> = + PluginStackBuilder::new(plugin_state, state, init_vtable); + + builder.object::(); + + builder + .object_setting(issue_get_setting_name, issue_set_setting_name) + .object_setting(issue_get_setting_contents, issue_set_setting_contents); + + builder.object_user_setting::(); + + builder + .value(issue_value_creation_date) + .value(issue_value_comment_count) + .value(issue_get_setting_name) + .value(issue_value_author); + + builder + .operation(create_issue_request) + .operation(query_issues_request) + .operation(edit_issue_request) + .operation(issue_post_comment_request); + + state +} + +#[derive(Clone, Debug)] +pub struct IssuesPluginState { + pub pool: PgPool, +} diff --git a/plugins/giterated-issues/src/main.rs b/plugins/giterated-issues/src/main.rs new file mode 100644 index 0000000..51c98db --- /dev/null +++ b/plugins/giterated-issues/src/main.rs @@ -0,0 +1,45 @@ +use std::str::FromStr; + +use giterated_issues::operations::CreateIssueRequest; +use giterated_models::{ + instance::Instance, + object::{GiteratedObject, ObjectRequest}, + operation::GiteratedOperation, + repository::Repository, + user::User, +}; +use giterated_plugin::{handle::PluginHandle, new_stack::Runtime}; +use tracing::Level; + +#[tokio::main] +pub async fn main() { + tracing_subscriber::fmt() + .pretty() + .with_thread_names(true) + .with_max_level(Level::TRACE) + .init(); + + let mut handle = PluginHandle::from_dylib("giterated_issues.dll").unwrap(); + + let mut runtime = Runtime::<()>::new(); + + runtime.insert_plugin(handle); + + let operation = CreateIssueRequest { + name: String::from("test issue"), + contents: String::from("hey!"), + author: User::from_str("amber:giterated.dev").unwrap(), + }; + + match runtime.handle_typed( + Repository::from_str("barson:giterated.dev/foo@giterated.dev").unwrap(), + operation, + ) { + Ok(success) => { + println!("Success in create issue: {:?}", success) + } + Err(err) => { + println!("Error in create issue: {:?}", err) + } + } +} diff --git a/plugins/giterated-issues/src/operations.rs b/plugins/giterated-issues/src/operations.rs new file mode 100644 index 0000000..1978b94 --- /dev/null +++ b/plugins/giterated-issues/src/operations.rs @@ -0,0 +1,75 @@ +use giterated_models::{operation::GiteratedOperation, repository::Repository, user::User}; +use serde::{Deserialize, Serialize}; + +use crate::Issue; + +/// Create an [`Issue`] on a [`Repository`]. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct CreateIssueRequest { + pub name: String, + pub contents: String, + pub author: User, +} + +impl GiteratedOperation for CreateIssueRequest { + type Success = Issue; + + type Failure = IssueCreationError; +} + +#[derive(Clone, Debug, Serialize, Deserialize, thiserror::Error)] +#[error("failed to create issue")] +pub struct IssueCreationError; + +/// Query for [`Issue`]s on a [`Repository`]. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct QueryIssuesRequest {} + +impl GiteratedOperation for QueryIssuesRequest { + type Success = Vec; + + type Failure = IssueQueryError; +} + +#[derive(Clone, Debug, Serialize, Deserialize, thiserror::Error)] +#[error("failed to query issues")] +pub struct IssueQueryError; + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct IssueEditRequest { + // Might not be needed :) +} + +impl GiteratedOperation for IssueEditRequest { + type Success = (); + + type Failure = IssueEditError; +} + +#[derive(Clone, Debug, Serialize, Deserialize, thiserror::Error)] +#[error("failed to edit issue")] +pub struct IssueEditError; + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct IssuePostCommentRequest { + pub contents: String, + pub visibility: CommentVisibility, +} + +impl GiteratedOperation for IssuePostCommentRequest { + type Success = u32; + + type Failure = IssuePostCommentError; +} + +#[derive(Clone, Debug, Serialize, Deserialize, thiserror::Error)] +#[error("failed to post comment")] +pub struct IssuePostCommentError; + +#[derive(PartialEq, Eq, Debug, Hash, Serialize, Deserialize, Clone, sqlx::Type)] +#[sqlx(type_name = "comment_visibility", rename_all = "lowercase")] +pub enum CommentVisibility { + Public, + Maintainers, + Private, +} diff --git a/plugins/giterated-issues/src/setting.rs b/plugins/giterated-issues/src/setting.rs new file mode 100644 index 0000000..0f3d08d --- /dev/null +++ b/plugins/giterated-issues/src/setting.rs @@ -0,0 +1,28 @@ +use giterated_models::settings::Setting; +use serde::{Deserialize, Serialize}; + +use crate::value::Name; + +impl Setting for Name { + fn name() -> &'static str { + "name" + } +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct Contents(String); + +impl Setting for Contents { + fn name() -> &'static str { + "contents" + } +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct NotificationsOverride(pub bool); + +impl Setting for NotificationsOverride { + fn name() -> &'static str { + "notifications_override" + } +} diff --git a/plugins/giterated-issues/src/value.rs b/plugins/giterated-issues/src/value.rs new file mode 100644 index 0000000..553227f --- /dev/null +++ b/plugins/giterated-issues/src/value.rs @@ -0,0 +1,48 @@ +use giterated_models::{user::User, value::GiteratedObjectValue}; +use serde::{Deserialize, Serialize}; + +use crate::Issue; + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct CreationDate(pub String); + +impl GiteratedObjectValue for CreationDate { + type Object = Issue; + + fn value_name() -> &'static str { + "creation_date" + } +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct CommentCount(pub u32); + +impl GiteratedObjectValue for CommentCount { + type Object = Issue; + + fn value_name() -> &'static str { + "comment_count" + } +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct Name(pub String); + +impl GiteratedObjectValue for Name { + type Object = Issue; + + fn value_name() -> &'static str { + "name" + } +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct Author(pub User); + +impl GiteratedObjectValue for Author { + type Object = Issue; + + fn value_name() -> &'static str { + "owner" + } +} diff --git a/plugins/giterated-protocol/Cargo.toml b/plugins/giterated-protocol/Cargo.toml new file mode 100644 index 0000000..99353b6 --- /dev/null +++ b/plugins/giterated-protocol/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "giterated-protocol" +version = "0.1.0" +edition = "2021" + +[lib] +name = "giterated_protocol" +path = "src/lib.rs" +crate-type = ["dylib", "rlib"] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +giterated-plugin = { path = "../../giterated-plugin" } +giterated-models = { path = "../../giterated-models" } +serde = { version = "1.0", features = [ "derive" ]} +anyhow = "1" +giterated-plugin-sys = { path = "../../giterated-plugin/giterated-plugin-sys" } +toml = { version = "0.8" } +tracing = "0.1" +tracing-subscriber = "0.3" +serde_json = "1.0" +rand = "0.8" +rsa = {version = "0.9", features = ["sha2"]} +tokio-tungstenite = { version = "0.20" } +tokio = { version = "1.32.0", features = ["full"] } +thiserror = "1" +bincode = "1.3" +futures-util = "0.3" +async-trait = "0.1" \ No newline at end of file diff --git a/plugins/giterated-protocol/src/handlers.rs b/plugins/giterated-protocol/src/handlers.rs new file mode 100644 index 0000000..02178ee --- /dev/null +++ b/plugins/giterated-protocol/src/handlers.rs @@ -0,0 +1,255 @@ +use std::{fmt::Display, net::SocketAddr, str::FromStr, sync::Arc}; + +use anyhow::Error; +use futures_util::{SinkExt, StreamExt}; +use giterated_models::{ + error::{NetworkOperationError, OperationError}, + instance::Instance, + object::GiteratedObject, + operation::GiteratedOperation, +}; +use giterated_plugin::{ + new_stack::{runtime_handler::RuntimeHandle, Runtime}, + AnyFailure, AnyObject, AnyOperation, AnySuccess, +}; +use serde::{Deserialize, Serialize}; +use tokio::net::TcpStream; +use tokio_tungstenite::{connect_async, tungstenite::Message, MaybeTlsStream, WebSocketStream}; + +use crate::{Authenticated, GiteratedMessage, ProtocolState, RemoteError}; + +pub async fn handle_network_operation( + state: ProtocolState, + object: NetworkedObject, + operation: NetworkedOperation, + runtime: RuntimeHandle, +) -> Result, OperationError>> { + trace!("Handle network operation {}", operation.name); + + runtime + .handle_serialized(&object.0, &operation.name, &operation.payload) + .await +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct NetworkedObject(pub String); + +impl FromStr for NetworkedObject { + type Err = (); + + fn from_str(s: &str) -> Result { + Ok(NetworkedObject(s.to_string())) + } +} + +impl Display for NetworkedObject { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(&self.0) + } +} + +impl GiteratedObject for NetworkedObject { + fn object_name() -> &'static str { + "networked_object" + } + + fn from_object_str(_object_str: &str) -> Result { + todo!() + } + + fn home_uri(&self) -> String { + todo!() + } +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct NetworkedOperation { + pub name: String, + pub payload: Vec, +} + +impl NetworkedOperation { + pub fn new(name: String, payload: Vec) -> Self { + Self { name, payload } + } +} + +impl GiteratedOperation for NetworkedOperation { + type Success = Vec; + + type Failure = Vec; + + fn operation_name() -> &'static str { + "networked_operation" + } +} + +/// Handler which will attempt to resolve any operation that doesn't resolve locally +/// against a remote instance. +pub async fn try_handle_with_remote( + state: ProtocolState, + object: AnyObject, + operation: AnyOperation, +) -> Result> { + // if object.is::() { + // return Err(OperationError::Unhandled); + // } + // trace!( + // "Try handling object operation {}::{} with remote", + // object.kind(), + // operation.kind().operation_name + // ); + // TODO: + // Ideally we support pass-through on object types that aren't used locally. + // For now, we aren't worrying about that. + let object_meta = object.vtable().clone(); + + let operation_meta = operation.vtable().clone(); + + // trace!( + // "Serializing with {}::{}", + // operation.kind().object_name, + // operation.kind().operation_name + // ); + + let object_home_uri = unsafe { (object_meta.home_uri)(&object) }; + + if let Some(home_uri) = state.home_uri { + if &home_uri == object_home_uri.as_ref() { + // This isn't a remote request, requests aren't supposed to hit this layer + // if they're not remote. + // warn!("Try handling object operation {}::{}, resolved object home uri as local home uri. This is a bug.", object.kind(), + // operation.kind().operation_name); + + return Err(OperationError::Unhandled); + } + } + + // trace!( + // "Handling object operation {}::{} sending payload", + // object.kind(), + // operation.kind().operation_name + // ); + + let object = NetworkedObject(unsafe { (object_meta.to_str)(object).as_ref().to_string() }); + + let payload = unsafe { (operation_meta.serialize)(&operation) }.unwrap(); + let payload = Vec::from(payload.as_ref()); + + let operation = NetworkedOperation::new(operation_meta.kind().to_string(), payload); + + // let authenticated = Authenticated::new(object, operation); + + let message = GiteratedMessage { + object, + operation: NetworkedOperation::operation_name().to_string(), + payload: operation, + }; + + let authenticated = Authenticated::new(message); + + let mut socket: WebSocketStream> = connect_to( + &Instance::from_str(&object_home_uri).unwrap(), + &Some(("127.0.0.1:1111").parse().unwrap()), + ) + .await + .unwrap(); + + // TODO AUTH + + let result: Result, OperationError>> = + send_expect(&mut socket, authenticated).await; + + match result { + Ok(success) => { + let success = unsafe { (operation_meta.deserialize_success)(&success) }.unwrap(); + + Ok(success) + } + Err(err) => Err(match err { + OperationError::Operation(failure) => { + let failure = unsafe { (operation_meta.deserialize_failure)(&failure) }.unwrap(); + + OperationError::Operation(failure) + } + OperationError::Internal(internal) => OperationError::Internal(internal), + OperationError::Unhandled => OperationError::Unhandled, + }), + } +} + +type Socket = WebSocketStream>; + +async fn connect_to( + instance: &Instance, + + socket_addr: &Option, +) -> Result { + if let Some(addr) = socket_addr { + info!( + "Connecting to {}", + format!("ws://{}/.giterated/daemon/", addr) + ); + + let (websocket, _response) = + connect_async(&format!("ws://{}/.giterated/daemon/", addr)).await?; + + info!("Connection established with {}", addr); + + Ok(websocket) + } else { + info!( + "Connecting to {}", + format!("wss://{}/.giterated/daemon/", instance.0) + ); + + let (websocket, _response) = + connect_async(&format!("wss://{}/.giterated/daemon/", instance.0)).await?; + + info!("Connection established with {}", instance.0); + + Ok(websocket) + } +} + +async fn send_expect>( + socket: &mut Socket, + message: Authenticated, +) -> Result, OperationError>> { + let payload = bincode::serialize(&message.into_payload()).unwrap(); + + socket.send(Message::Binary(payload)).await.unwrap(); + + while let Some(message) = socket.next().await { + let payload = match message.unwrap() { + Message::Binary(payload) => payload, + + _ => { + continue; + } + }; + + let raw_result = + bincode::deserialize::, NetworkOperationError>>>(&payload) + .map_err(|e| OperationError::Internal(Error::from(e)))?; + + trace!( + "Received response for networked operation {}::{}.", + O::object_name(), + D::operation_name() + ); + + return match raw_result { + Ok(success) => Ok(success), + Err(err) => Err(match err { + NetworkOperationError::Operation(operation_error) => { + OperationError::Operation(operation_error) + } + NetworkOperationError::Internal => OperationError::Internal(RemoteError.into()), + NetworkOperationError::Unhandled => OperationError::Unhandled, + }), + }; + } + + panic!() +} diff --git a/plugins/giterated-protocol/src/lib.rs b/plugins/giterated-protocol/src/lib.rs new file mode 100644 index 0000000..4eacc08 --- /dev/null +++ b/plugins/giterated-protocol/src/lib.rs @@ -0,0 +1,300 @@ +use std::{ops::Deref, str::FromStr, sync::Arc}; + +use giterated_models::{ + authenticated::{InstanceSignature, UserAuthenticationToken}, + instance::Instance, + object::GiteratedObject, + operation::GiteratedOperation, + user::User, +}; +use handlers::{NetworkedObject, NetworkedOperation}; +use object::NetworkAnyObject; +use operations::NetworkAnyOperation; +use rsa::{ + pkcs1::DecodeRsaPrivateKey, + pss::SigningKey, + sha2::Sha256, + signature::{RandomizedSigner, SignatureEncoding}, + RsaPrivateKey, +}; +use serde::{Deserialize, Serialize}; +use std::fmt::Debug; + +pub mod handlers; +pub mod object; +pub mod operations; + +#[macro_use] +extern crate tracing; + +#[derive(Clone)] +pub struct StackOperationState { + pub our_instance: Instance, + pub instance: Option, + pub user: Option, +} + +#[derive(Clone, Debug)] +pub struct AuthenticatedInstance(Instance); + +impl AuthenticatedInstance { + pub fn new(instance: Instance) -> Self { + AuthenticatedInstance(instance) + } +} + +impl Deref for AuthenticatedInstance { + type Target = Instance; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +#[derive(Clone, Debug)] +pub struct AuthenticatedUser(User); + +impl AuthenticatedUser { + pub fn new(user: User) -> Self { + AuthenticatedUser(user) + } +} + +impl Deref for AuthenticatedUser { + type Target = User; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +#[derive(Clone)] +pub struct NetworkOperationState { + authentication: Vec>, +} + +impl NetworkOperationState { + pub fn new() -> Self { + Self { + authentication: vec![], + } + } + + pub fn authenticate( + &mut self, + provider: impl AuthenticationSourceProviders + Send + Sync + 'static, + ) { + self.authentication.push(Arc::new(provider)) + } +} + +#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)] +pub struct AuthenticatedPayload { + pub source: Vec, + pub object: String, + pub operation: String, + pub payload: Vec, +} + +impl AuthenticatedPayload { + pub fn into_message(self) -> GiteratedMessage { + GiteratedMessage { + object: NetworkedObject::from_str(&self.object).unwrap(), + operation: self.operation, + payload: serde_json::from_slice(&self.payload).unwrap(), + } + } +} + +pub trait AuthenticationSourceProvider: Debug { + fn authenticate(&self, payload: &Vec) -> AuthenticationSource; +} + +pub trait AuthenticationSourceProviders: Debug { + fn authenticate_all(&self, payload: &Vec) -> Vec; +} + +impl AuthenticationSourceProviders for A +where + A: AuthenticationSourceProvider, +{ + fn authenticate_all(&self, payload: &Vec) -> Vec { + vec![self.authenticate(payload)] + } +} + +impl AuthenticationSourceProviders for (A, B) +where + A: AuthenticationSourceProvider, + B: AuthenticationSourceProvider, +{ + fn authenticate_all(&self, payload: &Vec) -> Vec { + let (first, second) = self; + + vec![first.authenticate(payload), second.authenticate(payload)] + } +} + +#[derive(Clone, Debug)] +pub struct UserAuthenticator { + pub user: User, + pub token: UserAuthenticationToken, +} + +impl AuthenticationSourceProvider for UserAuthenticator { + fn authenticate(&self, _payload: &Vec) -> AuthenticationSource { + AuthenticationSource::User { + user: self.user.clone(), + token: self.token.clone(), + } + } +} + +#[derive(Debug, Clone)] +pub struct InstanceAuthenticator { + pub instance: Instance, + pub private_key: String, +} + +impl AuthenticationSourceProvider for InstanceAuthenticator { + fn authenticate(&self, payload: &Vec) -> AuthenticationSource { + let mut rng = rand::thread_rng(); + + let private_key = RsaPrivateKey::from_pkcs1_pem(&self.private_key).unwrap(); + let signing_key = SigningKey::::new(private_key); + let signature = signing_key.sign_with_rng(&mut rng, payload); + + AuthenticationSource::Instance { + instance: self.instance.clone(), + // TODO: Actually parse signature from private key + signature: InstanceSignature(signature.to_bytes().into_vec()), + } + } +} + +#[derive(Clone, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] +pub enum AuthenticationSource { + User { + user: User, + token: UserAuthenticationToken, + }, + Instance { + instance: Instance, + signature: InstanceSignature, + }, +} + +#[derive(Serialize)] +#[serde(bound(deserialize = "O: GiteratedObject, V: GiteratedOperation"))] +pub struct GiteratedMessage> { + #[serde(with = "string")] + pub object: O, + pub operation: String, + pub payload: V, +} + +#[allow(unused)] +mod string { + use std::fmt::Display; + use std::str::FromStr; + + use serde::{de, Deserialize, Deserializer, Serializer}; + + pub fn serialize(value: &T, serializer: S) -> Result + where + T: Display, + S: Serializer, + { + serializer.collect_str(value) + } + + pub fn deserialize<'de, T, D>(deserializer: D) -> Result + where + T: FromStr, + T::Err: Display, + D: Deserializer<'de>, + { + String::deserialize(deserializer)? + .parse() + .map_err(de::Error::custom) + } +} + +impl GiteratedMessage { + pub fn try_into>( + &self, + ) -> Result, ()> { + let object = O::from_object_str(&self.object.0).map_err(|_| ())?; + let payload = serde_json::from_slice::(&self.payload.0).map_err(|_| ())?; + + Ok(GiteratedMessage { + object, + operation: self.operation.clone(), + payload, + }) + } +} + +impl + Debug, O: GiteratedObject + Debug> Debug + for GiteratedMessage +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("GiteratedMessage") + .field("object", &self.object) + .field("operation", &self.operation) + .field("payload", &self.payload) + .finish() + } +} + +#[derive(Debug, Clone, thiserror::Error)] +#[error("a remote internal error occurred")] +pub struct RemoteError; + +#[derive(Debug, thiserror::Error)] +#[error("a remote internal error occurred")] + +pub struct NetworkError; + +#[derive(Debug)] +pub struct Authenticated> { + pub source: Vec>, + pub message: GiteratedMessage, +} + +impl> Authenticated { + pub fn new(message: GiteratedMessage) -> Self { + Self { + source: vec![], + message, + } + } + + pub fn append_authentication( + &mut self, + authentication: Arc, + ) { + self.source.push(authentication); + } + + pub fn into_payload(mut self) -> AuthenticatedPayload { + let payload = serde_json::to_vec(&self.message.payload).unwrap(); + + AuthenticatedPayload { + object: self.message.object.to_string(), + operation: self.message.operation, + source: self + .source + .drain(..) + .map(|provider| provider.as_ref().authenticate_all(&payload)) + .flatten() + .collect::>(), + payload, + } + } +} + +#[derive(Clone, Debug)] +pub struct ProtocolState { + pub home_uri: Option, +} diff --git a/plugins/giterated-protocol/src/object.rs b/plugins/giterated-protocol/src/object.rs new file mode 100644 index 0000000..558f1d6 --- /dev/null +++ b/plugins/giterated-protocol/src/object.rs @@ -0,0 +1,38 @@ +use std::{convert::Infallible, fmt::Display, str::FromStr}; + +use anyhow::Error; +use giterated_models::object::GiteratedObject; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(transparent)] +#[repr(transparent)] +pub struct NetworkAnyObject(pub String); + +impl GiteratedObject for NetworkAnyObject { + fn object_name() -> &'static str { + "network_object" + } + + fn from_object_str(object_str: &str) -> Result { + Ok(Self(object_str.to_string())) + } + + fn home_uri(&self) -> String { + todo!() + } +} + +impl Display for NetworkAnyObject { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(&self.0) + } +} + +impl FromStr for NetworkAnyObject { + type Err = Infallible; + + fn from_str(s: &str) -> Result { + Ok(Self(s.to_owned())) + } +} diff --git a/plugins/giterated-protocol/src/operations.rs b/plugins/giterated-protocol/src/operations.rs new file mode 100644 index 0000000..6e54db4 --- /dev/null +++ b/plugins/giterated-protocol/src/operations.rs @@ -0,0 +1,13 @@ +use giterated_models::{object::GiteratedObject, operation::GiteratedOperation}; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(transparent)] +#[repr(transparent)] +pub struct NetworkAnyOperation(pub Vec); + +impl GiteratedOperation for NetworkAnyOperation { + type Success = Vec; + + type Failure = Vec; +}