diff --git a/Cargo.lock b/Cargo.lock index 31bd776..42aa525 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -771,6 +771,7 @@ dependencies = [ "git2", "giterated-api", "giterated-models", + "giterated-stack", "jsonwebtoken", "log", "rand", @@ -816,6 +817,15 @@ dependencies = [ [[package]] name = "giterated-stack" version = "0.1.0" +dependencies = [ + "async-trait", + "bincode", + "futures-util", + "giterated-models", + "serde", + "serde_json", + "tracing", +] [[package]] name = "h2" diff --git a/giterated-daemon/Cargo.toml b/giterated-daemon/Cargo.toml index edafcd6..32b13cd 100644 --- a/giterated-daemon/Cargo.toml +++ b/giterated-daemon/Cargo.toml @@ -24,6 +24,7 @@ aes-gcm = "0.10.2" semver = {version = "*", features = ["serde"]} giterated-models = { path = "../giterated-models" } giterated-api = { path = "../../giterated-api" } +giterated-stack = { path = "../giterated-stack" } deadpool = "*" bincode = "*" diff --git a/giterated-daemon/src/backend/git.rs b/giterated-daemon/src/backend/git.rs index 63a6895..7431062 100644 --- a/giterated-daemon/src/backend/git.rs +++ b/giterated-daemon/src/backend/git.rs @@ -710,24 +710,33 @@ impl RepositoryBackend for GitBackend { .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()))?; + 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()))?; + 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()))?; + 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(), - ))?; + .map_err(|_| { + GitBackendError::FailedDiffing(oid_old.to_string(), oid_new.to_string()) + })?; // Should be safe to unwrap? let stats = diff.stats().unwrap(); diff --git a/giterated-daemon/src/connection/wrapper.rs b/giterated-daemon/src/connection/wrapper.rs index 4119af3..3b95032 100644 --- a/giterated-daemon/src/connection/wrapper.rs +++ b/giterated-daemon/src/connection/wrapper.rs @@ -60,6 +60,8 @@ pub async fn connection_wrapper( let _handshaked = false; + let backend = backend.into_backend(); + loop { let mut socket = connection_state.socket.lock().await; let message = socket.next().await; diff --git a/giterated-daemon/src/database_backend/handler.rs b/giterated-daemon/src/database_backend/handler.rs index ab9b6ca..b93fa93 100644 --- a/giterated-daemon/src/database_backend/handler.rs +++ b/giterated-daemon/src/database_backend/handler.rs @@ -1,11 +1,9 @@ -use std::{collections::HashMap, error::Error, pin::Pin, sync::Arc}; +use std::error::Error; -use futures_util::{future::BoxFuture, Future, FutureExt}; +use futures_util::{future::BoxFuture, FutureExt}; use giterated_models::{ error::{GetValueError, OperationError, RepositoryError, UserError}, - object::{AnyObject, GiteratedObject}, object_backend::ObjectBackend, - operation::{AnyOperation, GiteratedOperation}, repository::{ Commit, DefaultBranch, Description, LatestCommit, Repository, RepositoryCommitBeforeRequest, RepositoryDiff, RepositoryDiffRequest, RepositoryFile, @@ -19,113 +17,6 @@ use giterated_models::{ use super::DatabaseBackend; -#[async_trait::async_trait] -pub trait GiteratedOperationHandler< - O: GiteratedObject, - D: GiteratedOperation, - S: Send + Sync + Clone, -> -{ - fn operation_name(&self) -> &str; - fn object_name(&self) -> &str; - - async fn handle( - &self, - object: &O, - operation: D, - state: S, - ) -> Result>; -} - -#[async_trait::async_trait] -impl GiteratedOperationHandler for F -where - F: FnMut( - &O, - D, - S, - ) -> Pin< - Box>> + Send>, - > + Send - + Sync - + Clone, - O: GiteratedObject + Send + Sync, - D: GiteratedOperation + 'static, - >::Failure: Send, - S: Send + Sync + Clone + 'static, -{ - fn operation_name(&self) -> &str { - D::operation_name() - } - - fn object_name(&self) -> &str { - O::object_name() - } - - async fn handle( - &self, - object: &O, - operation: D, - state: S, - ) -> Result> { - self.clone()(object, operation, state).await - } -} - -pub struct OperationWrapper( - Box< - dyn Fn( - AnyObject, - AnyOperation, - S, - ) - -> Pin, OperationError>>> + Send>> - + Send - + Sync, - >, -); - -impl OperationWrapper { - pub fn new< - O: GiteratedObject + Send + Sync, - D: GiteratedOperation + 'static, - F: GiteratedOperationHandler + Send + Sync + 'static + Clone, - >( - handler: F, - ) -> Self { - let handler = Arc::new(Box::pin(handler)); - Self(Box::new(move |any_object, any_operation, state| { - let handler = handler.clone(); - async move { - let handler = handler.clone(); - let object: O = O::from_object_str(&any_object.0).unwrap(); - let operation: D = serde_json::from_value(any_operation.0.clone()).unwrap(); - - let result = handler.handle(&object, operation, state).await; - result - .map(|success| serde_json::to_vec(&success).unwrap()) - .map_err(|err| match err { - OperationError::Operation(err) => { - OperationError::Operation(serde_json::to_vec(&err).unwrap()) - } - OperationError::Internal(internal) => OperationError::Internal(internal), - OperationError::Unhandled => OperationError::Unhandled, - }) - } - .boxed() - })) - } - - async fn handle( - &mut self, - object: AnyObject, - operation: AnyOperation, - state: S, - ) -> Result, OperationError>> { - self.0(object, operation, state).await - } -} - pub fn user_get_repositories( object: &User, _operation: UserRepositoriesRequest, @@ -208,9 +99,10 @@ pub fn repository_info( state: DatabaseBackend, ) -> BoxFuture<'static, Result>> { let object = object.clone(); + let backend = state.into_backend(); async move { - let mut object = state + let mut object = backend .get_object::(&object.to_string()) .await .unwrap(); @@ -259,9 +151,10 @@ pub fn repository_file_from_id( state: DatabaseBackend, ) -> BoxFuture<'static, Result>> { let object = object.clone(); + let backend = state.into_backend(); async move { - let object = state + let object = backend .get_object::(&object.to_string()) .await .unwrap(); @@ -288,9 +181,10 @@ pub fn repository_diff( state: DatabaseBackend, ) -> BoxFuture<'static, Result>> { let object = object.clone(); + let backend = state.into_backend(); async move { - let object = state + let object = backend .get_object::(&object.to_string()) .await .unwrap(); @@ -313,9 +207,10 @@ pub fn repository_commit_before( state: DatabaseBackend, ) -> BoxFuture<'static, Result>> { let object = object.clone(); + let backend = state.into_backend(); async move { - let object = state + let object = backend .get_object::(&object.to_string()) .await .unwrap(); @@ -390,53 +285,3 @@ pub fn repository_set_setting( } .boxed() } - -pub struct OperationHandlers { - operations: HashMap>, -} - -impl Default for OperationHandlers { - fn default() -> Self { - Self { - operations: HashMap::new(), - } - } -} - -impl OperationHandlers { - pub fn insert< - O: GiteratedObject + Send + Sync, - D: GiteratedOperation + 'static, - H: GiteratedOperationHandler + Send + Sync + 'static + Clone, - >( - &mut self, - handler: H, - ) -> &mut Self { - let operation_name = handler.operation_name().to_string(); - - let wrapped = OperationWrapper::new(handler); - - self.operations.insert(operation_name, wrapped); - - self - } - - pub async fn handle( - &mut self, - object: &O, - operation_name: &str, - operation: AnyOperation, - state: S, - ) -> Result, OperationError>> { - if let Some(handler) = self.operations.get_mut(operation_name) { - handler - .handle(AnyObject(object.to_string()), operation, state) - .await - } else { - Err(OperationError::Internal(format!( - "unknown operation: {}", - operation_name - ))) - } - } -} diff --git a/giterated-daemon/src/database_backend/mod.rs b/giterated-daemon/src/database_backend/mod.rs index 1914c54..7d75a07 100644 --- a/giterated-daemon/src/database_backend/mod.rs +++ b/giterated-daemon/src/database_backend/mod.rs @@ -1,16 +1,16 @@ pub mod handler; -use std::{str::FromStr, sync::Arc}; +use std::sync::Arc; use giterated_models::error::OperationError; use giterated_models::instance::Instance; -use giterated_models::object::{ - AnyObject, GiteratedObject, Object, ObjectRequest, ObjectRequestError, -}; +use giterated_models::object::{GiteratedObject, Object, ObjectRequestError}; use giterated_models::object_backend::ObjectBackend; use giterated_models::operation::GiteratedOperation; use giterated_models::repository::Repository; use giterated_models::user::User; +use giterated_stack::handler::GiteratedBackend; +use giterated_stack::OperationHandlers; use std::fmt::Debug; use tokio::sync::Mutex; @@ -19,7 +19,7 @@ use crate::backend::{RepositoryBackend, UserBackend}; use self::handler::{ repository_commit_before, repository_diff, repository_file_from_id, repository_get_setting, repository_get_value, repository_info, repository_set_setting, user_get_repositories, - user_get_setting, user_get_value, user_set_setting, OperationHandlers, + user_get_setting, user_get_value, user_set_setting, }; #[derive(Clone, Debug)] @@ -65,6 +65,28 @@ impl DatabaseBackend { repository_backend, } } + + pub fn into_backend(&self) -> GiteratedBackend { + let mut handlers = OperationHandlers::default(); + + handlers + .insert(user_get_repositories) + .insert(user_get_value) + .insert(user_get_setting) + .insert(user_set_setting) + .insert(repository_info) + .insert(repository_file_from_id) + .insert(repository_diff) + .insert(repository_commit_before) + .insert(repository_get_value) + .insert(repository_get_setting) + .insert(repository_set_setting) + .register_object::() + .register_object::() + .register_object::(); + + GiteratedBackend::new(self.clone(), handlers) + } } impl Debug for DatabaseBackend { @@ -73,193 +95,7 @@ impl Debug for DatabaseBackend { } } -#[async_trait::async_trait] -impl ObjectBackend for DatabaseBackend { - async fn object_operation + Debug>( - &self, - object: O, - operation: &str, - payload: D, - ) -> Result> { - let serialized = - serde_json::to_value(payload).map_err(|e| OperationError::Internal(e.to_string()))?; - let object = object.to_string(); - if let Ok(user) = User::from_str(&object) { - let mut handler = OperationHandlers::default(); - - handler - .insert(user_get_repositories) - .insert(user_get_value) - .insert(user_get_setting) - .insert(user_set_setting); - - match handler - .handle( - &user, - operation, - serde_json::from_value(serialized.clone()).unwrap(), - self.clone(), - ) - .await - { - Ok(result) => Ok(serde_json::from_slice(&result) - .map_err(|e| OperationError::Internal(e.to_string()))?), - Err(err) => match err { - OperationError::Internal(internal) => { - warn!( - "Internal Error: {:?}", - OperationError::<()>::Internal(internal.clone()) - ); - - Err(OperationError::Internal(internal)) - } - OperationError::Unhandled => Err(OperationError::Unhandled), - OperationError::Operation(err) => Err(OperationError::Operation( - serde_json::from_slice(&err) - .map_err(|e| OperationError::Internal(e.to_string()))?, - )), - }, - } - } else if let Ok(repository) = Repository::from_str(&object) { - let mut handler = OperationHandlers::default(); - - handler - .insert(repository_info) - .insert(repository_file_from_id) - .insert(repository_diff) - .insert(repository_commit_before) - .insert(repository_get_value) - .insert(repository_get_setting) - .insert(repository_set_setting); - - match handler - .handle( - &repository, - operation, - serde_json::from_value(serialized.clone()).unwrap(), - self.clone(), - ) - .await - { - Ok(result) => Ok(serde_json::from_slice(&result) - .map_err(|e| OperationError::Internal(e.to_string()))?), - Err(err) => match err { - OperationError::Internal(internal) => { - warn!( - "Internal Error: {:?}", - OperationError::<()>::Internal(internal.clone()) - ); - - Err(OperationError::Internal(internal)) - } - OperationError::Unhandled => Err(OperationError::Unhandled), - OperationError::Operation(err) => Err(OperationError::Operation( - serde_json::from_slice(&err) - .map_err(|e| OperationError::Internal(e.to_string()))?, - )), - }, - } - } else if let Ok(instance) = Instance::from_str(&object) { - if instance != self.our_instance { - // We don't handle other objects currently - return Err(OperationError::Unhandled); - } - - if let Ok(object_request) = serde_json::from_value::(serialized.clone()) - { - let result = self.get_object::(&object_request.0).await; - - let result = result - .map(|success| serde_json::to_vec(&success.object()).unwrap()) - .map_err(|err| match err { - OperationError::Operation(err) => { - OperationError::Operation(serde_json::to_vec(&err).unwrap()) - } - OperationError::Internal(internal) => OperationError::Internal(internal), - OperationError::Unhandled => OperationError::Unhandled, - }); - - match result { - Ok(result) => Ok(serde_json::from_slice(&result) - .map_err(|e| OperationError::Internal(e.to_string()))?), - Err(err) => match err { - OperationError::Internal(internal) => { - warn!( - "Internal Error: {:?}", - OperationError::<()>::Internal(internal.clone()) - ); - - Err(OperationError::Internal(internal)) - } - OperationError::Unhandled => Err(OperationError::Unhandled), - OperationError::Operation(err) => Err(OperationError::Operation( - serde_json::from_slice(&err) - .map_err(|e| OperationError::Internal(e.to_string()))?, - )), - }, - } - } else { - Err(OperationError::Unhandled) - } - } else { - Err(OperationError::Unhandled) - } - } - - async fn get_object( - &self, - object_str: &str, - ) -> Result, OperationError> { - if let Ok(user) = User::from_str(object_str) { - let mut user_backend = self.user_backend.lock().await; - - if user_backend - .exists(&user) - .await - .map_err(|e| OperationError::Internal(e.to_string()))? - { - Ok(unsafe { - Object::new_unchecked( - O::from_object_str(object_str) - .map_err(|e| ObjectRequestError::Deserialization(e.to_string()))?, - self.clone(), - ) - }) - } else { - return Err(OperationError::Unhandled); - } - } else if let Ok(repository) = Repository::from_str(object_str) { - let mut repository_backend = self.repository_backend.lock().await; - - if repository_backend - .exists(&repository) - .await - .map_err(|e| OperationError::Internal(e.to_string()))? - { - Ok(unsafe { - Object::new_unchecked( - O::from_object_str(object_str) - .map_err(|e| ObjectRequestError::Deserialization(e.to_string()))?, - self.clone(), - ) - }) - } else { - return Err(OperationError::Unhandled); - } - } else if let Ok(instance) = Instance::from_str(object_str) { - if instance != self.our_instance { - // We don't handle other objects currently - return Err(OperationError::Unhandled); - } - - return Err(OperationError::Unhandled); - } else { - // Invalid object type - return Err(OperationError::Unhandled); - } - } -} - +// TODO: These should be on the stack // These tests verify that the essential handling of the database backend is // functional and correct. #[cfg(test)] @@ -284,6 +120,7 @@ mod test { use giterated_models::settings::AnySetting; use giterated_models::user::{DisplayName, User}; use giterated_models::value::{AnyValue, GiteratedObjectValue}; + use giterated_stack::handler::GiteratedBackend; use serde_json::Value; use tokio::sync::Mutex; @@ -428,12 +265,13 @@ mod test { } } - fn test_backend() -> DatabaseBackend { + fn test_backend() -> GiteratedBackend { DatabaseBackend { our_instance: Instance::from_str("testing.giterated.dev").unwrap(), user_backend: Arc::new(Mutex::new(TestUserDatabaseBackend)) as _, repository_backend: Arc::new(Mutex::new(TestUserRepositoryBackend)) as _, } + .into_backend() } #[tokio::test] diff --git a/giterated-models/src/object/operations.rs b/giterated-models/src/object/operations.rs index 3e6aac0..9080d4d 100644 --- a/giterated-models/src/object/operations.rs +++ b/giterated-models/src/object/operations.rs @@ -6,7 +6,7 @@ use crate::{instance::Instance, operation::GiteratedOperation}; use super::GiteratedObject; -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, Clone)] pub struct ObjectRequest(pub String); #[derive(Serialize, Deserialize)] diff --git a/giterated-models/src/user/mod.rs b/giterated-models/src/user/mod.rs index 290510a..23e7680 100644 --- a/giterated-models/src/user/mod.rs +++ b/giterated-models/src/user/mod.rs @@ -47,7 +47,7 @@ impl GiteratedObject for User { } fn from_object_str(object_str: &str) -> Result { - Ok(User::from_str(object_str).unwrap()) + Ok(User::from_str(object_str)?) } } diff --git a/giterated-stack/Cargo.toml b/giterated-stack/Cargo.toml index 7650919..97c43f6 100644 --- a/giterated-stack/Cargo.toml +++ b/giterated-stack/Cargo.toml @@ -6,3 +6,10 @@ 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 = "*" +futures-util = "*" +tracing = "*" \ No newline at end of file diff --git a/giterated-stack/README.md b/giterated-stack/README.md index c3358cf..ea62c8d 100644 --- a/giterated-stack/README.md +++ b/giterated-stack/README.md @@ -48,4 +48,14 @@ impl GiteratedOperation for FooOperation { } ``` -These trait implementations further guard operation handlers from being incorrect, as a function with mismatched parameters or return type is not a valid operation handler. \ No newline at end of file +These trait implementations further guard operation handlers from being incorrect, as a function with mismatched parameters or return type is not a valid operation handler. + +## Routing Operations to Handlers + +All incoming operations are processed through the `TODO` type. This type will, at runtime, generate a LUT that matches object types to their operation handlers. + +At this time, it is not valid for multiple handlers to handle the same operation on the same object type. The `TODO` type also verifies this is not the case at startup to prevent conflicts at runtime. + +## Defining a new Operation + +A new operation can be implemented on any valid `GiteratedObject` type. This means it is easy to extend the behaviour of existing Object types to add new behaviours in-tree, and custom behaviours with plugins. \ No newline at end of file diff --git a/giterated-stack/src/handler.rs b/giterated-stack/src/handler.rs new file mode 100644 index 0000000..a2c48d3 --- /dev/null +++ b/giterated-stack/src/handler.rs @@ -0,0 +1,149 @@ +use giterated_models::{ + error::OperationError, + object::{ + AnyObject, GiteratedObject, Object, ObjectRequest, ObjectRequestError, ObjectResponse, + }, + object_backend::ObjectBackend, + operation::{AnyOperation, GiteratedOperation}, +}; +use std::{fmt::Debug, str::FromStr, sync::Arc}; +use tracing::warn; + +use crate::{state::HandlerState, OperationHandlers}; + +#[derive(Clone)] +pub struct GiteratedBackend { + state: S, + handlers: Arc>, +} + +impl GiteratedBackend { + pub fn new(state: S, handlers: OperationHandlers) -> Self { + Self { + state, + handlers: Arc::new(handlers), + } + } +} + +#[async_trait::async_trait] +impl ObjectBackend for GiteratedBackend { + async fn object_operation( + &self, + object: O, + operation: &str, + payload: D, + ) -> Result> + where + O: GiteratedObject + Debug, + D: GiteratedOperation + Debug, + { + let serialized = + serde_json::to_value(payload).map_err(|e| OperationError::Internal(e.to_string()))?; + let object = object.to_string(); + + if operation == ObjectRequest::operation_name() { + // We're doing an object request + let raw_result = self + .handlers + .resolve_object( + AnyObject(object.clone()), + serde_json::from_value(serialized).unwrap(), + self.state.clone(), + ) + .await; + + return match raw_result { + Ok(result) => Ok(serde_json::from_slice(&result) + .map_err(|e| OperationError::Internal(e.to_string()))?), + Err(err) => match err { + OperationError::Internal(internal) => { + warn!( + "Internal Error: {:?}", + OperationError::<()>::Internal(internal.clone()) + ); + + Err(OperationError::Internal(internal)) + } + OperationError::Unhandled => Err(OperationError::Unhandled), + OperationError::Operation(err) => Err(OperationError::Operation( + serde_json::from_slice(&err) + .map_err(|e| OperationError::Internal(e.to_string()))?, + )), + }, + }; + } + + let raw_result = self + .handlers + .handle( + &AnyObject(object), + operation, + AnyOperation(serialized), + self.state.clone(), + ) + .await; + + match raw_result { + Ok(result) => Ok(serde_json::from_slice(&result) + .map_err(|e| OperationError::Internal(e.to_string()))?), + Err(err) => match err { + OperationError::Internal(internal) => { + warn!( + "Internal Error: {:?}", + OperationError::<()>::Internal(internal.clone()) + ); + + Err(OperationError::Internal(internal)) + } + OperationError::Unhandled => Err(OperationError::Unhandled), + OperationError::Operation(err) => Err(OperationError::Operation( + serde_json::from_slice(&err) + .map_err(|e| OperationError::Internal(e.to_string()))?, + )), + }, + } + } + + async fn get_object( + &self, + object_str: &str, + ) -> Result, OperationError> { + let raw_result = self + .handlers + .resolve_object( + AnyObject("giterated.dev".to_string()), + ObjectRequest(object_str.to_string()), + self.state.clone(), + ) + .await; + + let object: ObjectResponse = match raw_result { + Ok(result) => Ok(serde_json::from_slice(&result) + .map_err(|e| OperationError::Internal(e.to_string()))?), + Err(err) => match err { + OperationError::Internal(internal) => { + warn!( + "Internal Error: {:?}", + OperationError::<()>::Internal(internal.clone()) + ); + + Err(OperationError::Internal(internal)) + } + OperationError::Unhandled => Err(OperationError::Unhandled), + OperationError::Operation(err) => Err(OperationError::Operation( + serde_json::from_slice(&err) + .map_err(|e| OperationError::Internal(e.to_string()))?, + )), + }, + }?; + + unsafe { + Ok(Object::new_unchecked( + O::from_str(&object.0) + .map_err(|_| OperationError::Internal("deserialize failure".to_string()))?, + self.clone(), + )) + } + } +} diff --git a/giterated-stack/src/lib.rs b/giterated-stack/src/lib.rs new file mode 100644 index 0000000..8a3d012 --- /dev/null +++ b/giterated-stack/src/lib.rs @@ -0,0 +1,223 @@ +pub mod handler; +pub mod state; + +use std::{future::Future, pin::Pin, sync::Arc}; + +use futures_util::FutureExt; +use giterated_models::{ + error::OperationError, + instance::Instance, + object::{AnyObject, GiteratedObject, ObjectRequest, ObjectResponse}, + operation::{AnyOperation, GiteratedOperation}, +}; + +pub struct OperationHandlers { + operations: Vec>, + get_object: Vec>, +} + +impl Default for OperationHandlers { + fn default() -> Self { + Self { + operations: Vec::new(), + get_object: Vec::new(), + } + } +} + +impl OperationHandlers { + pub fn insert< + O: GiteratedObject + Send + Sync, + D: GiteratedOperation + 'static, + H: GiteratedOperationHandler + Send + Sync + 'static + Clone, + >( + &mut self, + handler: H, + ) -> &mut Self { + let _operation_name = handler.operation_name().to_string(); + + let wrapped = OperationWrapper::new(handler); + + self.operations.push(wrapped); + + self + } + + pub fn register_object(&mut self) -> &mut Self { + let closure = |_: &Instance, operation: ObjectRequest, _state| { + async move { + if O::from_str(&operation.0).is_ok() { + Ok(ObjectResponse(operation.0)) + } else { + Err(OperationError::Unhandled) + } + } + .boxed() + }; + + let wrapped = OperationWrapper::new(closure); + + self.get_object.push(wrapped); + + self + } + + pub async fn handle( + &self, + object: &O, + _operation_name: &str, + operation: AnyOperation, + state: S, + ) -> Result, OperationError>> { + for handler in self.operations.iter() { + return match handler + .handle( + AnyObject(object.to_string()), + operation.clone(), + state.clone(), + ) + .await + { + Ok(ok) => Ok(ok), + Err(err) => Err(match err { + OperationError::Operation(err) => OperationError::Operation(err), + OperationError::Internal(err) => OperationError::Internal(err), + OperationError::Unhandled => continue, + }), + }; + } + + Err(OperationError::Unhandled) + } + + pub async fn resolve_object( + &self, + instance: AnyObject, + request: ObjectRequest, + state: S, + ) -> Result, OperationError>> { + for handler in self.get_object.iter() { + if let Ok(response) = handler + .handle( + instance.clone(), + AnyOperation(serde_json::to_value(request.clone()).unwrap()), + state.clone(), + ) + .await + { + return Ok(response); + } + } + + Err(OperationError::Unhandled) + } +} + +#[async_trait::async_trait] +pub trait GiteratedOperationHandler< + O: GiteratedObject, + D: GiteratedOperation, + S: Send + Sync + Clone, +> +{ + fn operation_name(&self) -> &str; + fn object_name(&self) -> &str; + + async fn handle( + &self, + object: &O, + operation: D, + state: S, + ) -> Result>; +} + +#[async_trait::async_trait] +impl GiteratedOperationHandler for F +where + F: FnMut( + &O, + D, + S, + ) -> Pin< + Box>> + Send>, + > + Send + + Sync + + Clone, + O: GiteratedObject + Send + Sync, + D: GiteratedOperation + 'static, + >::Failure: Send, + S: Send + Sync + Clone + 'static, +{ + fn operation_name(&self) -> &str { + D::operation_name() + } + + fn object_name(&self) -> &str { + O::object_name() + } + + async fn handle( + &self, + object: &O, + operation: D, + state: S, + ) -> Result> { + self.clone()(object, operation, state).await + } +} + +pub struct OperationWrapper( + Box< + dyn Fn( + AnyObject, + AnyOperation, + S, + ) + -> Pin, OperationError>>> + Send>> + + Send + + Sync, + >, +); + +impl OperationWrapper { + pub fn new< + O: GiteratedObject + Send + Sync, + D: GiteratedOperation + 'static, + F: GiteratedOperationHandler + Send + Sync + 'static + Clone, + >( + handler: F, + ) -> Self { + let handler = Arc::new(Box::pin(handler)); + Self(Box::new(move |any_object, any_operation, state| { + let handler = handler.clone(); + async move { + let handler = handler.clone(); + let object: O = + O::from_object_str(&any_object.0).map_err(|_| OperationError::Unhandled)?; + let operation: D = serde_json::from_value(any_operation.0.clone()) + .map_err(|_| OperationError::Unhandled)?; + + let result = handler.handle(&object, operation, state).await; + result + .map(|success| serde_json::to_vec(&success).unwrap()) + .map_err(|err| match err { + OperationError::Operation(err) => { + OperationError::Operation(serde_json::to_vec(&err).unwrap()) + } + OperationError::Internal(internal) => OperationError::Internal(internal), + OperationError::Unhandled => OperationError::Unhandled, + }) + } + .boxed() + })) + } + + async fn handle( + &self, + object: AnyObject, + operation: AnyOperation, + state: S, + ) -> Result, OperationError>> { + self.0(object, operation, state).await + } +} diff --git a/giterated-stack/src/main.rs b/giterated-stack/src/main.rs deleted file mode 100644 index e7a11a9..0000000 --- a/giterated-stack/src/main.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - println!("Hello, world!"); -} diff --git a/giterated-stack/src/state.rs b/giterated-stack/src/state.rs new file mode 100644 index 0000000..50ca121 --- /dev/null +++ b/giterated-stack/src/state.rs @@ -0,0 +1,12 @@ +/// A type which can be passed into a stateful handler. +/// +/// # Trait Bounds +/// This trait is bound on `Send + Sync`, as well as `Clone`. Handler states are +/// cloned many times, and should be inexpensive to clone. +/// +/// # Blanket Impl +/// This trait is blanket-impl'd on any type that meets the requirements. You do not need +/// to manually mark your state types with it. +pub trait HandlerState: Send + Sync + Clone + 'static {} + +impl HandlerState for T where T: Send + Sync + Clone + 'static {}