use std::{collections::HashMap, error::Error, pin::Pin, sync::Arc}; use futures_util::{future::BoxFuture, Future, FutureExt}; use giterated_models::{ error::{GetValueError, OperationError, RepositoryError, UserError}, object::{AnyObject, GiteratedObject}, object_backend::ObjectBackend, operation::{AnyOperation, GiteratedOperation}, repository::{ DefaultBranch, Description, LatestCommit, Repository, RepositoryFileInspectRequest, RepositoryInfoRequest, RepositorySummary, RepositoryView, Visibility, }, settings::{AnySetting, GetSetting, GetSettingError, SetSetting, SetSettingError}, user::{User, UserRepositoriesRequest}, value::{AnyValue, GetValue}, }; 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, state: DatabaseBackend, ) -> BoxFuture<'static, Result, OperationError>> { let object = object.clone(); async move { let mut user_backend = state.user_backend.lock().await; let repositories = user_backend .repositories_for_user(None, &object) .await .map_err(|e| OperationError::Internal(e.to_string()))?; Ok(repositories) } .boxed() } pub fn user_get_value( object: &User, operation: GetValue>, state: DatabaseBackend, ) -> BoxFuture<'static, Result, OperationError>> { let object = object.clone(); async move { let mut user_backend = state.user_backend.lock().await; let value = user_backend .get_value(&object, &operation.value_name) .await .map_err(|e| OperationError::Internal(e.to_string()))?; Ok(value) } .boxed() } pub fn user_get_setting( object: &User, operation: GetSetting, state: DatabaseBackend, ) -> BoxFuture<'static, Result>> { let object = object.clone(); async move { let mut user_backend = state.user_backend.lock().await; let value = user_backend .get_setting(&object, &operation.setting_name) .await .map_err(|e| OperationError::Internal(e.to_string()))?; Ok(value) } .boxed() } pub fn user_set_setting( object: &User, operation: SetSetting, state: DatabaseBackend, ) -> BoxFuture<'static, Result<(), OperationError>> { let object = object.clone(); async move { let mut user_backend = state.user_backend.lock().await; let value = user_backend .write_setting(&object, &operation.setting_name, &operation.value.0) .await .map_err(|e| OperationError::Internal(e.to_string()))?; Ok(value) } .boxed() } pub fn repository_info( object: &Repository, operation: RepositoryInfoRequest, state: DatabaseBackend, ) -> BoxFuture<'static, Result>> { let object = object.clone(); async move { let mut object = state .get_object::(&object.to_string()) .await .unwrap(); let mut repository_backend = state.repository_backend.lock().await; let tree = repository_backend .repository_file_inspect( None, object.object(), &RepositoryFileInspectRequest { extra_metadata: operation.extra_metadata, path: operation.path, rev: operation.rev.clone(), }, ) .await .map_err(|err| OperationError::Internal(format!("{:?}", err)))?; drop(repository_backend); let _visibility = object.get::().await.map_err(|e| { OperationError::Internal(format!("{:?}: {}", e.source(), e.to_string())) })?; let _default_branch = object.get::().await.map_err(|e| { OperationError::Internal(format!("{:?}: {}", e.source(), e.to_string())) })?; let info = RepositoryView { name: object.object().name.clone(), owner: object.object().owner.clone(), description: object.get::().await.ok(), visibility: object .get::() .await .map_err(|e| OperationError::Internal(e.to_string()))?, default_branch: object .get::() .await .map_err(|e| OperationError::Internal(e.to_string()))?, // 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::().await.ok(), tree_rev: operation.rev, tree, }; Ok(info) } .boxed() } pub fn repository_get_value( object: &Repository, operation: GetValue>, state: DatabaseBackend, ) -> BoxFuture<'static, Result, OperationError>> { let object = object.clone(); async move { let mut repository_backend = state.repository_backend.lock().await; let value = repository_backend .get_value(&object, &operation.value_name) .await .map_err(|e| { OperationError::Internal(format!("error getting value: {}", e.to_string())) })?; Ok(value) } .boxed() } pub fn repository_get_setting( object: &Repository, operation: GetSetting, state: DatabaseBackend, ) -> BoxFuture<'static, Result>> { let object = object.clone(); async move { let mut repository_backend = state.repository_backend.lock().await; let value = repository_backend .get_setting(&object, &operation.setting_name) .await .map_err(|e| OperationError::Internal(e.to_string()))?; Ok(value) } .boxed() } pub fn repository_set_setting( object: &Repository, operation: SetSetting, state: DatabaseBackend, ) -> BoxFuture<'static, Result<(), OperationError>> { let object = object.clone(); async move { let mut repository_backend = state.repository_backend.lock().await; let value = repository_backend .write_setting(&object, &operation.setting_name, &operation.value.0) .await .map_err(|e| OperationError::Internal(e.to_string()))?; Ok(value) } .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 ))) } } }