diff --git a/Cargo.lock b/Cargo.lock index db53d30..e6cfbdf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -746,7 +746,7 @@ dependencies = [ [[package]] name = "giterated-daemon" -version = "0.0.6" +version = "0.1.0" dependencies = [ "aes-gcm", "anyhow", diff --git a/giterated-daemon/Cargo.toml b/giterated-daemon/Cargo.toml index 3410bde..252ec3b 100644 --- a/giterated-daemon/Cargo.toml +++ b/giterated-daemon/Cargo.toml @@ -1,7 +1,14 @@ [package] name = "giterated-daemon" -version = "0.0.6" +version = "0.1.0" +authors = ["Amber Kowalski"] edition = "2021" +rust-version = "1.70.0" +description = "Giterated's Data Models" +homepage = "https://giterated.dev/ambee/giterated" +repository = "https://giterated.dev/ambee/giterated" +license = "MIT OR Apache-2.0" +keywords = ["giterated"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/giterated-daemon/src/backend/git.rs b/giterated-daemon/src/backend/git.rs index b7d037f..a9861c2 100644 --- a/giterated-daemon/src/backend/git.rs +++ b/giterated-daemon/src/backend/git.rs @@ -5,21 +5,21 @@ use git2::BranchType; use giterated_models::instance::{Instance, RepositoryCreateRequest}; use giterated_models::repository::{ - AccessList, Commit, DefaultBranch, Description, IssueLabel, LatestCommit, Repository, - RepositoryBranch, RepositoryBranchesRequest, RepositoryChunkLine, - RepositoryCommitBeforeRequest, RepositoryCommitFromIdRequest, RepositoryDiff, - RepositoryDiffFile, RepositoryDiffFileChunk, RepositoryDiffFileInfo, RepositoryDiffFileStatus, - RepositoryDiffPatchRequest, RepositoryDiffRequest, RepositoryFile, RepositoryFileFromIdRequest, + 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::settings::{AnySetting, Setting}; -use giterated_models::user::{User, UserParseError}; -use giterated_models::value::{AnyValue, GiteratedObjectValue}; -use giterated_stack::AuthenticatedUser; -use serde_json::Value; + +use giterated_models::user::User; + +use giterated_stack::{AuthenticatedUser, GiteratedStack}; + use sqlx::PgPool; use std::ops::Deref; use std::{ @@ -27,9 +27,9 @@ use std::{ sync::Arc, }; use thiserror::Error; -use tokio::sync::Mutex; +use tokio::sync::OnceCell; -use super::{IssuesBackend, MetadataBackend, RepositoryBackend}; +use super::{IssuesBackend, RepositoryBackend}; // TODO: Handle this //region database structures @@ -52,7 +52,7 @@ impl GitRepository { &self, our_instance: &Instance, user: &Option, - settings: &Arc>, + stack: &GiteratedStack, ) -> bool { if matches!(self.visibility, RepositoryVisibility::Public) { return true; @@ -70,32 +70,20 @@ impl GitRepository { } if matches!(self.visibility, RepositoryVisibility::Private) { - // Check if the user can view - let mut settings = settings.lock().await; - - let access_list = settings - .repository_get( - &Repository { - owner: self.owner_user.clone(), - name: self.name.clone(), - instance: our_instance.clone(), - }, - AccessList::name(), - ) - .await; - - let access_list: AccessList = match access_list { - Ok(list) => serde_json::from_value(list.0).unwrap(), - Err(_) => { - return false; - } - }; + // 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() - .find(|access_list_user| *access_list_user == user.deref()) - .is_some() + .any(|access_list_user| access_list_user == user.deref()) } else { false } @@ -165,7 +153,7 @@ pub struct GitBackend { pub pg_pool: PgPool, pub repository_folder: String, pub instance: Instance, - pub settings_provider: Arc>, + pub stack: Arc>>, } impl GitBackend { @@ -173,13 +161,15 @@ impl GitBackend { pg_pool: &PgPool, repository_folder: &str, instance: impl ToOwned, - settings_provider: Arc>, + stack: Arc>>, ) -> Self { + let instance = instance.to_owned(); + Self { pg_pool: pg_pool.clone(), repository_folder: repository_folder.to_string(), - instance: instance.to_owned(), - settings_provider, + instance, + stack, } } @@ -256,7 +246,7 @@ impl GitBackend { .can_user_view_repository( &self.instance, &Some(requester.clone()), - &self.settings_provider, + self.stack.get().unwrap(), ) .await { @@ -276,7 +266,7 @@ impl GitBackend { match repository.open_git2_repository(&self.repository_folder) { Ok(git) => Ok(git), - Err(err) => return Err(err), + Err(err) => Err(err), } } @@ -368,10 +358,9 @@ impl GitBackend { 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()) + return Err( + Box::new(GitBackendError::RefNotFound(rev.unwrap().to_string())).into(), + ) } } } @@ -381,10 +370,7 @@ impl GitBackend { } /// Gets the last commit in a rev - pub fn get_last_commit_in_rev( - git: &git2::Repository, - rev: &str, - ) -> anyhow::Result { + 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 @@ -392,11 +378,12 @@ impl GitBackend { revwalk.set_sorting(git2::Sort::TIME)?; revwalk.push(oid)?; - if let Some(commit_oid) = revwalk.next() { - if let Ok(commit_oid) = commit_oid { - if let Ok(commit) = git.find_commit(commit_oid).map_err(|_| GitBackendError::CommitNotFound(commit_oid.to_string())) { - return Ok(Commit::from(commit)); - } + 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)); } } @@ -416,7 +403,7 @@ impl RepositoryBackend for GitBackend { .await { Ok(repository - .can_user_view_repository(&self.instance, requester, &self.settings_provider) + .can_user_view_repository(&self.instance, requester, self.stack.get().unwrap()) .await) } else { Ok(false) @@ -468,45 +455,29 @@ impl RepositoryBackend for GitBackend { 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(), }; - let mut settings_backend = self.settings_provider.lock().await; - settings_backend - .repository_write( + stack + .write_setting( &repository, - Description::name(), - AnySetting( - serde_json::to_value(Description( - request.description.clone().unwrap_or_default(), - )) - .unwrap(), - ), + Description(request.description.clone().unwrap_or_default()), ) .await .unwrap(); - settings_backend - .repository_write( - &repository, - Visibility::name(), - AnySetting( - serde_json::to_value(Visibility(request.visibility.clone())).unwrap(), - ), - ) + + stack + .write_setting(&repository, Visibility(request.visibility.clone())) .await .unwrap(); - settings_backend - .repository_write( - &repository, - DefaultBranch::name(), - AnySetting( - serde_json::to_value(DefaultBranch(request.default_branch.clone())) - .unwrap(), - ), - ) + + stack + .write_setting(&repository, DefaultBranch(request.default_branch.clone())) .await .unwrap(); @@ -526,49 +497,6 @@ impl RepositoryBackend for GitBackend { } } - async fn get_value( - &mut self, - repository: &Repository, - name: &str, - ) -> Result, Error> { - Ok(unsafe { - if name == Description::value_name() { - AnyValue::from_raw(self.get_setting(repository, Description::name()).await?.0) - } else if name == Visibility::value_name() { - AnyValue::from_raw(self.get_setting(repository, Visibility::name()).await?.0) - } else if name == DefaultBranch::value_name() { - AnyValue::from_raw(self.get_setting(repository, DefaultBranch::name()).await?.0) - } else if name == LatestCommit::value_name() { - AnyValue::from_raw(serde_json::to_value(LatestCommit(None)).unwrap()) - } else { - return Err(UserParseError.into()); - } - }) - } - - async fn get_setting( - &mut self, - repository: &Repository, - name: &str, - ) -> Result { - let mut provider = self.settings_provider.lock().await; - - Ok(provider.repository_get(repository, name).await?) - } - - async fn write_setting( - &mut self, - repository: &Repository, - name: &str, - setting: &Value, - ) -> Result<(), Error> { - let mut provider = self.settings_provider.lock().await; - - provider - .repository_write(repository, name, AnySetting(setting.clone())) - .await - } - async fn repository_file_inspect( &mut self, requester: &Option, @@ -579,7 +507,7 @@ impl RepositoryBackend for GitBackend { .open_repository_and_check_permissions(&repository.owner, &repository.name, requester) .await?; - let tree_id = Self::get_oid_from_reference(&git, request.rev.as_ref().map(|s| s.as_str()))?; + let tree_id = Self::get_oid_from_reference(&git, request.rev.as_deref())?; // unwrap might be dangerous? // Get the commit from the oid @@ -701,7 +629,7 @@ impl RepositoryBackend for GitBackend { .open_repository_and_check_permissions(&repository.owner, &repository.name, requester) .await?; - let tree_id = Self::get_oid_from_reference(&git, request.rev.as_ref().map(|s| s.as_str()))?; + let tree_id = Self::get_oid_from_reference(&git, request.rev.as_deref())?; // unwrap might be dangerous? // Get the commit from the oid @@ -796,7 +724,7 @@ impl RepositoryBackend for GitBackend { .open_repository_and_check_permissions(&repository.owner, &repository.name, requester) .await?; - let tree_id = Self::get_oid_from_reference(&git, request.rev.as_ref().map(|s| s.as_str()))?; + let tree_id = Self::get_oid_from_reference(&git, request.rev.as_deref())?; // unwrap might be dangerous? // Get the commit from the oid @@ -842,12 +770,17 @@ impl RepositoryBackend for GitBackend { }; // TODO: Non UTF-8? - let commit = GitBackend::get_last_commit_in_rev(&git, branch.0.get().name().unwrap()).ok(); + let commit = + GitBackend::get_last_commit_in_rev(&git, branch.0.get().name().unwrap()).ok(); // TODO: Implement stale with configurable age - let mut stale = false; - - branches.push(RepositoryBranch {name:name.to_string(), stale, last_commit: commit }) + let stale = false; + + branches.push(RepositoryBranch { + name: name.to_string(), + stale, + last_commit: commit, + }) } Ok(branches) @@ -1072,12 +1005,10 @@ impl RepositoryBackend for GitBackend { revwalk.set_sorting(git2::Sort::TIME)?; revwalk.push(commit.id())?; - if let Some(next) = revwalk.next() { - if let Ok(before_commit_oid) = 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)); - } + 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)); } } diff --git a/giterated-daemon/src/backend/mod.rs b/giterated-daemon/src/backend/mod.rs index d01bfc3..39a233e 100644 --- a/giterated-daemon/src/backend/mod.rs +++ b/giterated-daemon/src/backend/mod.rs @@ -96,18 +96,6 @@ pub trait RepositoryBackend { repository: &Repository, request: &RepositoryBranchesRequest, ) -> Result, Error>; - async fn get_value( - &mut self, - user: &Repository, - name: &str, - ) -> Result, Error>; - async fn get_setting(&mut self, user: &Repository, name: &str) -> Result; - async fn write_setting( - &mut self, - repository: &Repository, - name: &str, - setting: &Value, - ) -> Result<(), Error>; async fn exists( &mut self, requester: &Option, diff --git a/giterated-daemon/src/backend/settings.rs b/giterated-daemon/src/backend/settings.rs index ad5f26c..13d5967 100644 --- a/giterated-daemon/src/backend/settings.rs +++ b/giterated-daemon/src/backend/settings.rs @@ -1,15 +1,20 @@ +use std::sync::Arc; + use anyhow::Error; use giterated_models::repository::Repository; use giterated_models::settings::AnySetting; use giterated_models::user::User; +use giterated_stack::GiteratedStack; use sqlx::PgPool; +use tokio::sync::OnceCell; use super::MetadataBackend; pub struct DatabaseSettings { pub pg_pool: PgPool, + pub stack: Arc>>, } #[async_trait::async_trait] @@ -75,7 +80,7 @@ impl MetadataBackend for DatabaseSettings { #[allow(unused)] #[derive(Debug, sqlx::FromRow)] -struct UserSettingRow { +pub struct UserSettingRow { pub username: String, pub name: String, pub value: String, @@ -83,7 +88,7 @@ struct UserSettingRow { #[allow(unused)] #[derive(Debug, sqlx::FromRow)] -struct RepositorySettingRow { +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 index f9149a3..e984f0c 100644 --- a/giterated-daemon/src/backend/user.rs +++ b/giterated-daemon/src/backend/user.rs @@ -240,7 +240,7 @@ impl AuthBackend for UserAuth { username: user.username, instance: self.this_instance.clone(), }, - &source, + source, ) .await; diff --git a/giterated-daemon/src/connection/wrapper.rs b/giterated-daemon/src/connection/wrapper.rs index b24a03a..2fc86ff 100644 --- a/giterated-daemon/src/connection/wrapper.rs +++ b/giterated-daemon/src/connection/wrapper.rs @@ -102,7 +102,7 @@ pub async fn connection_wrapper( signature, } = source { - let public_key = key_cache.get(&instance).await.unwrap(); + let public_key = key_cache.get(instance).await.unwrap(); let public_key = RsaPublicKey::from_pkcs1_pem(&public_key).unwrap(); let verifying_key = VerifyingKey::::new(public_key); @@ -130,7 +130,7 @@ pub async fn connection_wrapper( for source in &message.source { if let AuthenticationSource::User { user, token } = source { // Get token - let public_key = key_cache.get(&verified_instance).await.unwrap(); + let public_key = key_cache.get(verified_instance).await.unwrap(); let token: TokenData = jsonwebtoken::decode( token.as_ref(), diff --git a/giterated-daemon/src/database_backend/handler.rs b/giterated-daemon/src/database_backend/handler.rs index a66a84d..4b35608 100644 --- a/giterated-daemon/src/database_backend/handler.rs +++ b/giterated-daemon/src/database_backend/handler.rs @@ -444,44 +444,6 @@ pub fn repository_commit_before( .boxed_local() } -pub fn repository_get_value( - object: &Repository, - operation: GetValueTyped>, - state: DatabaseBackend, -) -> LocalBoxFuture<'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 - .as_internal_error()?; - - Ok(value) - } - .boxed_local() -} - -pub fn repository_get_setting( - object: &Repository, - operation: GetSetting, - state: DatabaseBackend, -) -> LocalBoxFuture<'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 - .as_internal_error()?; - - Ok(value.0) - } - .boxed_local() -} - pub fn instance_authentication_request( object: &Instance, operation: AuthenticationTokenRequest, @@ -534,121 +496,106 @@ pub fn instance_create_repository_request( pub fn user_get_value_display_name( object: &User, - operation: GetValueTyped, - state: DatabaseBackend, - // _requester: AuthorizedUser, + _operation: GetValueTyped, + _state: DatabaseBackend, + stack: Arc, // _requester: AuthorizedUser, ) -> LocalBoxFuture<'static, Result>> { let object = object.clone(); async move { - let mut backend = state.user_backend.lock().await; - - let raw_value = backend - .get_value(&object, &operation.value_name) + stack + .new_get_setting::<_, DisplayName>(&object) .await - .as_internal_error()?; - - Ok(serde_json::from_value(raw_value.into_inner()).as_internal_error()?) + .as_internal_error() } .boxed_local() } pub fn user_get_value_bio( object: &User, - operation: GetValueTyped, - state: DatabaseBackend, + _operation: GetValueTyped, + _state: DatabaseBackend, + stack: Arc, ) -> LocalBoxFuture<'static, Result>> { let object = object.clone(); async move { - let mut backend = state.user_backend.lock().await; - - let raw_value = backend - .get_value(&object, &operation.value_name) + stack + .new_get_setting::<_, Bio>(&object) .await - .as_internal_error()?; - - Ok(serde_json::from_value(raw_value.into_inner()).as_internal_error()?) + .as_internal_error() } .boxed_local() } pub fn repository_get_value_description( object: &Repository, - operation: GetValueTyped, - state: DatabaseBackend, + _operation: GetValueTyped, + _state: DatabaseBackend, + stack: Arc, ) -> LocalBoxFuture<'static, Result>> { let object = object.clone(); async move { - let mut backend = state.repository_backend.lock().await; - - let raw_value = backend - .get_value(&object, &operation.value_name) + stack + .new_get_setting::<_, Description>(&object) .await - .as_internal_error()?; - - Ok(serde_json::from_value(raw_value.into_inner()).as_internal_error()?) + .as_internal_error() } .boxed_local() } pub fn repository_get_value_visibility( object: &Repository, - operation: GetValueTyped, - state: DatabaseBackend, + _operation: GetValueTyped, + _state: DatabaseBackend, + stack: Arc, ) -> LocalBoxFuture<'static, Result>> { let object = object.clone(); async move { - let mut backend = state.repository_backend.lock().await; - - let raw_value = backend - .get_value(&object, &operation.value_name) + stack + .new_get_setting::<_, Visibility>(&object) .await - .as_internal_error()?; - - Ok(serde_json::from_value(raw_value.into_inner()).as_internal_error()?) + .as_internal_error() } .boxed_local() } pub fn repository_get_default_branch( object: &Repository, - operation: GetValueTyped, - state: DatabaseBackend, + _operation: GetValueTyped, + _state: DatabaseBackend, + stack: Arc, ) -> LocalBoxFuture<'static, Result>> { let object = object.clone(); async move { - let mut backend = state.repository_backend.lock().await; - - let raw_value = backend - .get_value(&object, &operation.value_name) + stack + .new_get_setting::<_, DefaultBranch>(&object) .await - .as_internal_error()?; - - Ok(serde_json::from_value(raw_value.into_inner()).as_internal_error()?) + .as_internal_error() } .boxed_local() } pub fn repository_get_latest_commit( object: &Repository, - operation: GetValueTyped, + _operation: GetValueTyped, state: DatabaseBackend, + _stack: Arc, ) -> LocalBoxFuture<'static, Result>> { - let object = object.clone(); + let _object = object.clone(); async move { - let mut backend = state.repository_backend.lock().await; + let _backend = state.repository_backend.lock().await; - let raw_value = backend - .get_value(&object, &operation.value_name) - .await - .as_internal_error()?; + // stack + // .new_get_setting::<_, LatestCommit>(&*object) + // .await + // .as_internal_error() - Ok(serde_json::from_value(raw_value.into_inner()).as_internal_error()?) + todo!() } .boxed_local() } diff --git a/giterated-daemon/src/database_backend/mod.rs b/giterated-daemon/src/database_backend/mod.rs index 544134e..256545f 100644 --- a/giterated-daemon/src/database_backend/mod.rs +++ b/giterated-daemon/src/database_backend/mod.rs @@ -1,8 +1,10 @@ pub mod handler; pub mod updates; +use std::any::Any; use std::sync::Arc; +use anyhow::Context; use giterated_models::error::OperationError; use giterated_models::instance::Instance; use giterated_models::object::{GiteratedObject, Object, ObjectRequestError}; @@ -10,11 +12,15 @@ use giterated_models::object_backend::ObjectBackend; use giterated_models::operation::GiteratedOperation; use giterated_models::repository::{DefaultBranch, Description, Repository, Visibility}; use giterated_models::user::{Bio, DisplayName, User}; -use giterated_stack::StackOperationState; -use giterated_stack::SubstackBuilder; +use giterated_stack::provider::MetadataProvider; +use giterated_stack::{GiteratedStack, ObjectMeta, SubstackBuilder}; +use giterated_stack::{SettingMeta, StackOperationState}; +use serde_json::Value; +use sqlx::PgPool; use std::fmt::Debug; -use tokio::sync::Mutex; +use tokio::sync::{Mutex, OnceCell}; +use crate::backend::settings::{RepositorySettingRow, UserSettingRow}; use crate::backend::{RepositoryBackend, UserBackend}; use self::handler::{ @@ -22,9 +28,9 @@ use self::handler::{ 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_default_branch, repository_get_latest_commit, - repository_get_setting, repository_get_statistics, repository_get_value_description, - repository_get_value_visibility, repository_info, repository_last_commit_of_file, - user_get_repositories, user_get_setting, user_get_value_bio, user_get_value_display_name, + repository_get_statistics, repository_get_value_description, repository_get_value_visibility, + repository_info, repository_last_commit_of_file, user_get_repositories, user_get_value_bio, + user_get_value_display_name, }; #[derive(Clone, Debug)] @@ -57,8 +63,10 @@ impl ObjectBackend for Foobackend { #[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 { @@ -66,30 +74,36 @@ impl DatabaseBackend { 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); + let mut builder = SubstackBuilder::::new(self.clone()); + + builder.object_metadata_provider(Box::new(self)); builder .object::() - .object_settings(repository_get_setting) .object::() - .object_settings(user_get_setting) .object::(); + // Register value settings, which are settings that directly correspond to + // value types. builder - .setting::() - .setting::() - .setting::() - .setting::() - .setting::(); + .value_setting::() + .value_setting::() + .value_setting::() + .value_setting::() + .value_setting::(); builder .value(user_get_value_bio) @@ -124,3 +138,74 @@ impl Debug for DatabaseBackend { 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: &(dyn Any + Send + Sync), + _object_meta: &ObjectMeta, + setting: &(dyn Any + Send + Sync), + 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: &(dyn Any + Send + Sync), + _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 index 95ff671..bcde63e 100644 --- a/giterated-daemon/src/database_backend/updates.rs +++ b/giterated-daemon/src/database_backend/updates.rs @@ -1,15 +1,12 @@ use futures_util::{future::BoxFuture, FutureExt}; use giterated_models::{ - repository::{DefaultBranch, Description, Repository}, - settings::{AnySetting, Setting}, - update::ValueUpdate, + repository::{Description, Repository}, + settings::AnySetting, user::User, - value::{AnyValue, GiteratedObjectValue}, + value::AnyValue, }; use giterated_stack::{AuthorizedUser, StackOperationState}; -use super::DatabaseBackend; - pub fn user_set_value( _object: User, _value_name: String, @@ -54,34 +51,26 @@ pub fn repository_set_description( 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, -) -> BoxFuture<'static, Result<(), ()>> { - async move { - let mut repository_backend = backend.repository_backend.lock().await; +// 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(); - repository_backend - .write_setting( - &object, - DefaultBranch::name(), - &serde_json::to_value(default_branch.clone()).unwrap(), - ) - .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()) }, +// }; - 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() -} +// // Submit value update back to the daemon +// // state.value_update(set_value); +// Ok(()) +// } +// .boxed() +// } diff --git a/giterated-daemon/src/keys.rs b/giterated-daemon/src/keys.rs index 260e978..b82d3c0 100644 --- a/giterated-daemon/src/keys.rs +++ b/giterated-daemon/src/keys.rs @@ -12,7 +12,7 @@ pub struct PublicKeyCache { impl PublicKeyCache { pub async fn get(&mut self, instance: &Instance) -> Result { if let Some(key) = self.keys.get(instance) { - return Ok(key.clone()); + Ok(key.clone()) } else { let key = reqwest::get(format!("https://{}/.giterated/pubkey.pem", instance)) .await? diff --git a/giterated-daemon/src/main.rs b/giterated-daemon/src/main.rs index bfebbaf..78cecad 100644 --- a/giterated-daemon/src/main.rs +++ b/giterated-daemon/src/main.rs @@ -19,7 +19,7 @@ use tokio::{ fs::File, io::{AsyncRead, AsyncReadExt, AsyncWrite}, net::{TcpListener, TcpStream}, - sync::Mutex, + sync::{Mutex, OnceCell}, }; use tokio_tungstenite::{accept_async, WebSocketStream}; use tokio_util::task::LocalPoolHandle; @@ -53,8 +53,11 @@ async fn main() -> Result<(), Error> { sqlx::migrate!().run(&db_pool).await?; info!("Connected"); + let stack_cell = Arc::new(OnceCell::default()); + let settings = Arc::new(Mutex::new(DatabaseSettings { pg_pool: db_pool.clone(), + stack: stack_cell.clone(), })); let repository_backend: Arc> = @@ -67,7 +70,7 @@ async fn main() -> Result<(), Error> { ), instance: Instance::from_str(config["giterated"]["instance"].as_str().unwrap()) .unwrap(), - settings_provider: settings.clone(), + stack: stack_cell.clone(), })); let token_granter = Arc::new(Mutex::new(AuthenticationTokenGranter { @@ -88,6 +91,8 @@ async fn main() -> Result<(), Error> { Instance::from_str(config["giterated"]["instance"].as_str().unwrap()).unwrap(), user_backend.clone(), repository_backend.clone(), + db_pool.clone(), + stack_cell.clone(), ); let mut runtime = GiteratedStack::default(); @@ -97,6 +102,10 @@ async fn main() -> Result<(), Error> { let runtime = Arc::new(runtime); + stack_cell + .set(runtime.clone()) + .expect("failed to store global daemon stack"); + let operation_state = { StackOperationState { our_instance: Instance::from_str(config["giterated"]["instance"].as_str().unwrap()) diff --git a/giterated-models/Cargo.toml b/giterated-models/Cargo.toml index 784e16a..d6680f1 100644 --- a/giterated-models/Cargo.toml +++ b/giterated-models/Cargo.toml @@ -1,7 +1,17 @@ [package] name = "giterated-models" version = "0.1.0" +authors = ["Amber Kowalski"] edition = "2021" +rust-version = "1.70.0" +description = "Giterated's Data Models" +homepage = "https://giterated.dev/ambee/giterated" +repository = "https://giterated.dev/ambee/giterated" +license = "MIT OR Apache-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 diff --git a/giterated-stack/Cargo.toml b/giterated-stack/Cargo.toml index f4c4068..4e1f00b 100644 --- a/giterated-stack/Cargo.toml +++ b/giterated-stack/Cargo.toml @@ -1,7 +1,17 @@ [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 diff --git a/giterated-stack/src/handler.rs b/giterated-stack/src/handler.rs index 113a7d8..64c25d9 100644 --- a/giterated-stack/src/handler.rs +++ b/giterated-stack/src/handler.rs @@ -1,6 +1,5 @@ use std::{any::Any, collections::HashMap, sync::Arc}; -use anyhow::Context; use futures_util::FutureExt; use giterated_models::{ authenticated::AuthenticatedPayload, @@ -16,11 +15,13 @@ use giterated_models::{ value::{AnyValue, GetValue, GetValueTyped, GiteratedObjectValue}, }; +use serde::{Deserialize, Serialize}; use tracing::trace; use crate::{ - GiteratedOperationHandler, ObjectMeta, ObjectOperationPair, ObjectValuePair, OperationMeta, - OperationWrapper, SettingMeta, StackOperationState, ValueMeta, + provider::MetadataProvider, GiteratedOperationHandler, MissingValue, ObjectMeta, + ObjectOperationPair, ObjectSettingPair, ObjectValuePair, OperationMeta, OperationWrapper, + SettingMeta, SettingUpdate, StackOperationState, ValueMeta, }; /// Temporary name for the next generation of Giterated stack @@ -29,13 +30,25 @@ pub struct GiteratedStack { operation_handlers: HashMap, value_getters: HashMap, setting_getters: HashMap, + value_change: HashMap, + setting_change: HashMap, + metadata_providers: Vec>, metadata: RuntimeMetadata, } +impl Debug for GiteratedStack { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("GiteratedStack").finish() + } +} + +#[derive(Clone)] +pub struct ValueChangeEvent(Arc); + impl GiteratedStack { pub fn merge_builder( &mut self, - builder: SubstackBuilder, + mut builder: SubstackBuilder, ) -> &mut Self { for (target, handler) in builder.operation_handlers { let tree = self.get_or_create_tree(&target); @@ -51,11 +64,162 @@ impl GiteratedStack { assert!(self.setting_getters.insert(target, handler).is_none()); } + for (target, handler) in builder.value_change { + self.value_change.insert(target, handler); + } + + for (target, handler) in builder.setting_change { + self.setting_change.insert(target, handler); + } + + self.metadata_providers + .append(&mut builder.metadata_providers); + self.metadata.append(builder.metadata); self } + pub async fn value_update( + &self, + object: O, + new_value: V, + operation_state: &StackOperationState, + ) where + O: GiteratedObject + 'static, + V: GiteratedObjectValue + 'static, + { + trace!("value updated {}::{}", O::object_name(), V::value_name()); + let target = ObjectValuePair::from_types::(); + + if let Some(handler) = self.value_change.get(&target) { + // TODO + let _ = handler + .handle( + &(Box::new(object) as _), + &(Box::new(ValueChangedShim { new_value }) as _), + operation_state, + ) + .await; + } + } + + pub async fn setting_update( + &self, + object: O, + new_setting: S, + operation_state: &StackOperationState, + ) 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.setting_change.get(&target) { + let _ = handler + .handle( + &(Box::new(object) as _), + &(Box::new(SettingUpdate(new_setting)) as _), + operation_state, + ) + .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, + S: Setting + 'static + Clone, + { + for provider in self.metadata_providers.iter() { + if provider.provides_for(object as &dyn Any) { + let setting_meta = self + .metadata + .settings + .get(&ObjectSettingPair { + object_kind: O::object_name().to_string(), + setting_name: S::name().to_string(), + }) + .ok_or_else(|| OperationError::Unhandled)?; + + let object_meta = self + .metadata + .objects + .get(O::object_name()) + .ok_or_else(|| OperationError::Unhandled)?; + + let result = provider + .write(object, object_meta, &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, + S: Setting + 'static, + { + for provider in self.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 + .metadata + .settings + .get(&ObjectSettingPair { + object_kind: O::object_name().to_string(), + setting_name: S::name().to_string(), + }) + .ok_or_else(|| OperationError::Unhandled)?; + + let object_meta = self + .metadata + .objects + .get(O::object_name()) + .ok_or_else(|| OperationError::Unhandled)?; + + let value = provider + .read(object, 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) + } + fn get_or_create_tree(&mut self, target: &ObjectOperationPair) -> &mut HandlerTree { if self.operation_handlers.contains_key(target) { self.operation_handlers.get_mut(target).unwrap() @@ -68,16 +232,11 @@ impl GiteratedStack { } } +#[derive(Default)] pub struct HandlerTree { elements: Vec, } -impl Default for HandlerTree { - fn default() -> Self { - Self { elements: vec![] } - } -} - impl HandlerTree { pub fn push(&mut self, handler: OperationWrapper) { self.elements.push(handler); @@ -90,7 +249,7 @@ impl HandlerTree { operation_state: &StackOperationState, ) -> Result, OperationError>> { for handler in self.elements.iter() { - match handler.handle(object, &operation, operation_state).await { + match handler.handle(object, operation, operation_state).await { Ok(success) => return Ok(success), Err(err) => match err { OperationError::Operation(failure) => { @@ -114,7 +273,7 @@ struct RuntimeMetadata { objects: HashMap, operations: HashMap, values: HashMap, - settings: HashMap, + settings: HashMap, } /// Defines a type that is a valid Giterated runtime state. @@ -130,6 +289,10 @@ pub struct SubstackBuilder { value_getters: HashMap, setting_getters: HashMap, metadata: RuntimeMetadata, + value_change: HashMap, + metadata_providers: Vec>, + setting_change: HashMap, + state: S, } @@ -140,6 +303,9 @@ impl SubstackBuilder { value_getters: Default::default(), setting_getters: Default::default(), metadata: Default::default(), + value_change: Default::default(), + metadata_providers: Default::default(), + setting_change: Default::default(), state, } } @@ -210,8 +376,51 @@ impl SubstackBuilder { /// /// # Type Registration /// This will register the provided setting type. - pub fn setting(&mut self) -> &mut Self { - self.metadata.register_setting::(); + 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 { + object_kind: O::object_name().to_string(), + setting_name: T::name().to_string(), + }, + OperationWrapper::new( + move |object: &O, + setting: SettingUpdate, + _state: (), + operation_state: StackOperationState, + stack: Arc| { + trace!( + "value setting updated {}::{}", + O::object_name(), + T::value_name() + ); + let object = object.clone(); + async move { + stack + .value_update(object, setting.0, &operation_state) + .await; + Ok(()) + } + .boxed_local() + }, + (), + ), + ); self } @@ -242,7 +451,7 @@ impl SubstackBuilder { _state: S, operation_state: StackOperationState, stack: Arc| { - let stack = stack.clone(); + let stack = stack; let object_name = handler_object_name; let value_name = handler_value_name; let object = object.clone(); @@ -310,6 +519,42 @@ impl SubstackBuilder { self } + + pub fn value_change(&mut self, handler: F) -> &mut Self + where + O: GiteratedObject + 'static, + F: GiteratedOperationHandler, S> + Clone + 'static + Send + Sync, + V: GiteratedObjectValue + Clone + 'static, + { + let object_name = handler.object_name().to_string(); + + let wrapped = OperationWrapper::new(handler, self.state.clone()); + + assert!(self.setting_getters.insert(object_name, wrapped).is_none()); + + self + } + + pub fn object_metadata_provider(&mut self, provider: Box) -> &mut Self { + self.metadata_providers.push(provider); + + self + } +} + +#[derive(Serialize, Deserialize, Clone)] +pub struct ValueChangedShim { + new_value: V, +} + +impl GiteratedOperation for ValueChangedShim +where + O: GiteratedObject, + V: GiteratedObjectValue, +{ + type Success = V; + + type Failure = MissingValue; } impl RuntimeMetadata { @@ -338,8 +583,8 @@ impl RuntimeMetadata { .operations .insert( ObjectOperationPair { - object_name: object_name.clone(), - operation_name: operation_name.clone(), + object_name: object_name, + operation_name: operation_name, }, OperationMeta::new::(), ) @@ -372,8 +617,8 @@ impl RuntimeMetadata { .values .insert( ObjectValuePair { - object_kind: object_name.clone(), - value_kind: value_name.clone(), + object_kind: object_name, + value_kind: value_name, }, ValueMeta::new::(), ) @@ -393,12 +638,16 @@ impl RuntimeMetadata { } } - fn register_setting(&mut self) { - let setting_name = S::name().to_string(); - + fn register_setting(&mut self) { if self .settings - .insert(setting_name.clone(), SettingMeta::new::()) + .insert( + ObjectSettingPair { + object_kind: O::object_name().to_string(), + setting_name: S::name().to_string(), + }, + SettingMeta::new::(), + ) .is_some() { trace!( @@ -479,7 +728,10 @@ impl GiteratedStack { let setting_meta = self .metadata .settings - .get(&operation.setting_name) + .get(&ObjectSettingPair { + object_kind: object_type.clone(), + setting_name: operation.setting_name.clone(), + }) .ok_or_else(|| OperationError::Unhandled)?; let raw_result = self .get_setting(object, object_type.clone(), operation, operation_state) @@ -487,7 +739,7 @@ impl GiteratedStack { return match raw_result { Ok(success) => { // Success is the setting type, serialize it - let serialized = (setting_meta.serialize)(success).unwrap(); + let serialized = (setting_meta.serialize)(&(*success)).unwrap(); Ok(serde_json::to_vec(&serialized).unwrap()) } @@ -507,6 +759,79 @@ impl GiteratedStack { OperationError::Unhandled => OperationError::Unhandled, }), }; + } else if message.operation == "set_setting" { + let operation: SetSetting = serde_json::from_slice(&message.payload.0).unwrap(); + + trace!( + "Handling network {}::set_setting for {}", + object_type, + operation.setting_name + ); + + let setting_meta = self + .metadata + .settings + .get(&ObjectSettingPair { + object_kind: object_type.clone(), + setting_name: operation.setting_name.clone(), + }) + .unwrap(); + + let setting = (setting_meta.deserialize)(operation.value.0) + .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.as_ref()) { + 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), object_meta, &(*setting), setting_meta) + .await; + + return match raw_result { + Ok(_) => { + (setting_meta.setting_updated)( + object, + setting, + operation_state.runtime.clone(), + operation_state, + ) + .await; + + Ok(serde_json::to_vec(&()).unwrap()) + } + 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, + ); + } } let target = ObjectOperationPair { @@ -606,7 +931,7 @@ impl GiteratedStack { } return match getter - .handle(&(object), &((value_meta.typed_get)()), &operation_state) + .handle(&(object), &((value_meta.typed_get)()), operation_state) .await { Ok(success) => { @@ -656,10 +981,46 @@ impl GiteratedStack { pub async fn network_set_setting( &self, - _operation: SetSetting, - _operation_state: &StackOperationState, + object: Box, + object_kind: String, + operation: SetSetting, + operation_state: &StackOperationState, ) -> Result, OperationError>> { - todo!() + trace!( + "Handling network {}::set_setting for {}", + object_kind, + operation.setting_name + ); + + let target = ObjectSettingPair { + object_kind: object_kind.clone(), + setting_name: operation.setting_name.clone(), + }; + + let handler = self.setting_change.get(&target).unwrap(); + + let raw_result = handler + .handle(&object, &(Box::new(operation) as _), operation_state) + .await; + + match raw_result { + Ok(_) => { + // Serialize success, which is the value type itself + let serialized = serde_json::to_vec(&()).as_internal_error()?; + + Ok(serialized) + } + Err(err) => Err(match err { + OperationError::Operation(failure) => { + // Failure is sourced from GetValue operation, but this is hardcoded for now + let failure: GetValueError = *failure.downcast().unwrap(); + + OperationError::Operation(serde_json::to_vec(&failure).as_internal_error()?) + } + OperationError::Internal(internal) => OperationError::Internal(internal), + OperationError::Unhandled => OperationError::Unhandled, + }), + } } } @@ -712,7 +1073,7 @@ impl ObjectBackend for Arc { } return match getter - .handle(&(object), &((value_meta.typed_get)()), &operation_state) + .handle(&(object), &((value_meta.typed_get)()), operation_state) .await { Ok(success) => Ok(*success.downcast().unwrap()), diff --git a/giterated-stack/src/lib.rs b/giterated-stack/src/lib.rs index 5f2a99c..676ef9e 100644 --- a/giterated-stack/src/lib.rs +++ b/giterated-stack/src/lib.rs @@ -1,7 +1,9 @@ mod handler; mod meta; +pub mod provider; pub use handler::{GiteratedStack, GiteratedStackState, *}; pub use meta::*; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; pub mod state; pub mod update; @@ -18,9 +20,9 @@ use giterated_models::{ object_backend::ObjectBackend, operation::GiteratedOperation, repository::{AccessList, Repository}, - settings::{GetSetting, SetSetting}, + settings::{GetSetting, SetSetting, Setting}, user::User, - value::GetValue, + value::{GetValue, GiteratedObjectValue}, }; #[derive(Clone, Debug, Hash, Eq, PartialEq)] @@ -29,12 +31,46 @@ struct ObjectOperationPair { pub operation_name: String, } +impl ObjectOperationPair { + #[allow(unused)] + pub fn from_types>() -> Self { + Self { + object_name: O::object_name().to_string(), + operation_name: D::operation_name().to_string(), + } + } +} + #[derive(Clone, Debug, Hash, Eq, PartialEq)] pub struct ObjectValuePair { pub object_kind: String, pub value_kind: String, } +impl ObjectValuePair { + pub fn from_types>() -> Self { + Self { + object_kind: O::object_name().to_string(), + value_kind: D::value_name().to_string(), + } + } +} + +#[derive(Clone, Debug, Hash, Eq, PartialEq)] +pub struct ObjectSettingPair { + pub object_kind: String, + pub setting_name: String, +} + +impl ObjectSettingPair { + pub fn from_types() -> Self { + Self { + object_kind: O::object_name().to_string(), + setting_name: S::name().to_string(), + } + } +} + #[async_trait::async_trait(?Send)] pub trait GiteratedOperationHandler< L, @@ -359,7 +395,7 @@ impl> FromOperationState } } -#[derive(Debug, thiserror::Error)] +#[derive(Debug, thiserror::Error, Serialize, Deserialize)] #[error("missing value")] pub struct MissingValue; @@ -374,10 +410,7 @@ impl + Send + Sync> FromOperationSt _operation: &D, state: &StackOperationState, ) -> Result> { - state - .user - .clone() - .ok_or_else(|| ExtractorError(MissingValue)) + state.user.clone().ok_or(ExtractorError(MissingValue)) } } @@ -392,10 +425,7 @@ impl + Send + Sync> FromOperationSt _operation: &D, state: &StackOperationState, ) -> Result> { - state - .instance - .clone() - .ok_or_else(|| ExtractorError(MissingValue)) + state.instance.clone().ok_or(ExtractorError(MissingValue)) } } @@ -460,7 +490,7 @@ impl AuthorizedOperation for SetSetting { authorize_for: &User, operation_state: &StackOperationState, ) -> Result> { - let authenticated_user = operation_state.user.as_ref().ok_or_else(|| MissingValue)?; + let authenticated_user = operation_state.user.as_ref().ok_or(MissingValue)?; Ok(authorize_for == authenticated_user.deref()) } @@ -475,7 +505,7 @@ impl AuthorizedOperation for GetSetting { authorize_for: &User, operation_state: &StackOperationState, ) -> Result> { - let authenticated_user = operation_state.user.as_ref().ok_or_else(|| MissingValue)?; + let authenticated_user = operation_state.user.as_ref().ok_or(MissingValue)?; Ok(authorize_for == authenticated_user.deref()) } @@ -499,18 +529,17 @@ impl AuthorizedOperation for SetSetting { .runtime .get_object::(&authorize_for.to_string(), operation_state) .await - .map_err(|err| anyhow::Error::from(err))?; + .map_err(anyhow::Error::from)?; let access_list = object .get_setting::(operation_state) .await - .map_err(|err| anyhow::Error::from(err))?; + .map_err(anyhow::Error::from)?; if access_list .0 .iter() - .find(|user| *user == authenticated_user.deref()) - .is_some() + .any(|user| user == authenticated_user.deref()) { Ok(true) } else { @@ -537,18 +566,17 @@ impl AuthorizedOperation for GetSetting { .runtime .get_object::(&authorize_for.to_string(), operation_state) .await - .map_err(|err| anyhow::Error::from(err))?; + .map_err(anyhow::Error::from)?; let access_list = object .get_setting::(operation_state) .await - .map_err(|err| anyhow::Error::from(err))?; + .map_err(anyhow::Error::from)?; if access_list .0 .iter() - .find(|user| *user == authenticated_user.deref()) - .is_some() + .any(|user| user == authenticated_user.deref()) { Ok(true) } else { @@ -710,3 +738,18 @@ impl Deref for AuthenticatedUser { &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 = (); +} diff --git a/giterated-stack/src/meta/mod.rs b/giterated-stack/src/meta/mod.rs index 562e0ab..bfe18cc 100644 --- a/giterated-stack/src/meta/mod.rs +++ b/giterated-stack/src/meta/mod.rs @@ -1,5 +1,6 @@ -use std::{any::Any, str::FromStr}; +use std::{any::Any, str::FromStr, sync::Arc}; +use futures_util::{future::LocalBoxFuture, FutureExt}; use giterated_models::{ object::GiteratedObject, operation::GiteratedOperation, @@ -8,13 +9,14 @@ use giterated_models::{ }; use serde_json::Value; +use crate::{GiteratedStack, StackOperationState}; + pub struct ValueMeta { pub name: String, - pub deserialize: Box Result, serde_json::Error> + Send + Sync>, - pub serialize: - Box) -> Result, serde_json::Error> + Send + Sync>, - pub typed_get: Box Box + Send + Sync>, - pub is_get_value_typed: Box) -> bool + Send + Sync>, + pub deserialize: fn(&[u8]) -> Result, serde_json::Error>, + pub serialize: fn(Box) -> Result, serde_json::Error>, + pub typed_get: fn() -> Box, + pub is_get_value_typed: fn(&Box) -> bool, } pub trait IntoValueMeta { @@ -31,13 +33,13 @@ impl + 'static> IntoValu } fn deserialize(buffer: &[u8]) -> Result, serde_json::Error> { - Ok(Box::new(serde_json::from_slice(&buffer)?)) + Ok(Box::new(serde_json::from_slice(buffer)?)) } fn serialize(value: Box) -> Result, serde_json::Error> { let value = value.downcast::().unwrap(); - Ok(serde_json::to_vec(&*value)?) + serde_json::to_vec(&*value) } fn typed_get() -> Box { @@ -56,10 +58,10 @@ impl ValueMeta { pub fn new() -> Self { Self { name: I::name(), - deserialize: Box::new(I::deserialize) as _, - serialize: Box::new(I::serialize) as _, - typed_get: Box::new(I::typed_get) as _, - is_get_value_typed: Box::new(I::is_get_value_typed) as _, + deserialize: I::deserialize, + serialize: I::serialize, + typed_get: I::typed_get, + is_get_value_typed: I::is_get_value_typed, } } } @@ -67,13 +69,10 @@ impl ValueMeta { pub struct OperationMeta { pub name: String, pub object_kind: String, - pub deserialize: - Box Result, serde_json::Error> + Send + Sync>, - pub any_is_same: Box bool + Send + Sync>, - pub serialize_success: - Box) -> Result, serde_json::Error> + Send + Sync>, - pub serialize_error: - Box) -> Result, serde_json::Error> + Send + Sync>, + pub deserialize: fn(&[u8]) -> Result, serde_json::Error>, + pub any_is_same: fn(&dyn Any) -> bool, + pub serialize_success: fn(Box) -> Result, serde_json::Error>, + pub serialize_error: fn(Box) -> Result, serde_json::Error>, } pub trait IntoOperationMeta { @@ -118,11 +117,11 @@ impl OperationMeta { pub fn new + 'static>() -> Self { Self { name: I::name(), - deserialize: Box::new(I::deserialize) as _, - serialize_success: Box::new(I::serialize_success) as _, - serialize_error: Box::new(I::serialize_failure) as _, + deserialize: I::deserialize, + serialize_success: I::serialize_success, + serialize_error: I::serialize_failure, object_kind: O::object_name().to_string(), - any_is_same: Box::new(I::any_is_same) as _, + any_is_same: I::any_is_same, } } } @@ -130,7 +129,7 @@ impl OperationMeta { pub struct ObjectMeta { pub name: String, pub from_str: Box Result, ()> + Send + Sync>, - pub any_is_same: Box bool + Send + Sync>, + pub any_is_same: fn(&dyn Any) -> bool, } pub trait IntoObjectMeta: FromStr { @@ -157,44 +156,74 @@ impl ObjectMeta { Ok(Box::new(object) as Box) }), - any_is_same: Box::new(I::any_is_same) as _, + any_is_same: I::any_is_same, } } } pub struct SettingMeta { pub name: String, - pub deserialize: Box Result, serde_json::Error> + Send + Sync>, - pub serialize: - Box) -> Result + Send + Sync>, + pub deserialize: fn(Value) -> Result, serde_json::Error>, + pub serialize: fn(&(dyn Any + Send + Sync)) -> Result, + pub setting_updated: for<'fut> fn( + Box, + Box, + Arc, + &StackOperationState, + ) -> LocalBoxFuture<'_, ()>, } -pub trait IntoSettingMeta { +pub trait IntoSettingMeta { fn name() -> String; - fn deserialize(buffer: &[u8]) -> Result, serde_json::Error>; - fn serialize(setting: Box) -> Result; + fn deserialize(value: Value) -> Result, serde_json::Error>; + fn serialize(setting: &(dyn Any + Send + Sync)) -> Result; + fn setting_updated( + object: Box, + setting: Box, + stack: Arc, + operation_state: &StackOperationState, + ) -> LocalBoxFuture<'_, ()>; } -impl IntoSettingMeta for S { +impl IntoSettingMeta for S { fn name() -> String { S::name().to_string() } - fn deserialize(buffer: &[u8]) -> Result, serde_json::Error> { - Ok(Box::new(serde_json::from_slice(buffer)?)) + fn deserialize(value: Value) -> Result, serde_json::Error> { + Ok(Box::new(serde_json::from_value::(value)?)) + } + + fn serialize(setting: &(dyn Any + Send + Sync)) -> Result { + serde_json::to_value(setting.downcast_ref::().unwrap()) } - fn serialize(setting: Box) -> Result { - Ok(*setting.downcast::().unwrap()) + fn setting_updated( + object: Box, + setting: Box, + stack: Arc, + operation_state: &StackOperationState, + ) -> LocalBoxFuture<'_, ()> { + async move { + stack + .setting_update( + *object.downcast::().unwrap(), + *setting.downcast::().unwrap(), + operation_state, + ) + .await + } + .boxed_local() } } impl SettingMeta { - pub fn new() -> Self { + pub fn new + 'static>() -> Self { Self { name: I::name(), - deserialize: Box::new(I::deserialize) as _, - serialize: Box::new(I::serialize) as _, + 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 new file mode 100644 index 0000000..4fec98f --- /dev/null +++ b/giterated-stack/src/provider/metadata.rs @@ -0,0 +1,25 @@ +use std::any::Any; + +use anyhow::Error; + +use serde_json::Value; + +use crate::{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: &(dyn Any + Send + Sync), + object_meta: &ObjectMeta, + setting: &(dyn Any + Send + Sync), + setting_meta: &SettingMeta, + ) -> Result<(), Error>; + async fn read( + &self, + object: &(dyn Any + Send + Sync), + object_meta: &ObjectMeta, + setting_meta: &SettingMeta, + ) -> Result; +} diff --git a/giterated-stack/src/provider/mod.rs b/giterated-stack/src/provider/mod.rs new file mode 100644 index 0000000..6dda9f9 --- /dev/null +++ b/giterated-stack/src/provider/mod.rs @@ -0,0 +1,2 @@ +mod metadata; +pub use metadata::MetadataProvider;