diff --git a/Cargo.lock b/Cargo.lock index 1d0524d..a422ee3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -820,11 +820,17 @@ dependencies = [ name = "giterated-protocol" version = "0.1.0" dependencies = [ + "async-trait", + "bincode", + "futures-util", "giterated-stack", "rand", "rsa", "serde", "serde_json", + "thiserror", + "tokio", + "tokio-tungstenite", "tracing", ] diff --git a/giterated-daemon/src/client.rs b/giterated-daemon/src/client.rs index d206c22..dccb2f3 100644 --- a/giterated-daemon/src/client.rs +++ b/giterated-daemon/src/client.rs @@ -46,6 +46,8 @@ pub async fn client_wrapper( } }; + trace!("Read payload from client"); + let payload = match bincode::deserialize::(&payload) { Ok(payload) => payload, Err(e) => { @@ -58,6 +60,11 @@ pub async fn client_wrapper( } }; + trace!( + "Deserialized payload for operation {} from client", + payload.operation + ); + let operation_state = StackOperationState { our_instance: our_instance.clone(), runtime: runtime.clone(), @@ -92,7 +99,15 @@ pub async fn handle_client_message( .await .as_internal_error_with_context("handling client message")?; - let networked_operation = NetworkedOperation::new(payload.object, payload.payload); + let message: giterated_protocol::GiteratedMessage = + payload.into_message(); + + let networked_operation = NetworkedOperation::new( + message.payload.name.clone(), + message.payload.payload.clone(), + ); + + trace!("Calling handler for networked operation"); networked_object .request(networked_operation, &operation_state) diff --git a/giterated-daemon/src/main.rs b/giterated-daemon/src/main.rs index a65f070..602261c 100644 --- a/giterated-daemon/src/main.rs +++ b/giterated-daemon/src/main.rs @@ -11,6 +11,7 @@ use giterated_daemon::{ use giterated_models::instance::Instance; +use giterated_protocol::NetworkedSubstack; use giterated_stack::GiteratedStackBuilder; use sqlx::{postgres::PgConnectOptions, ConnectOptions, PgPool}; use std::{net::SocketAddr, str::FromStr, sync::Arc}; @@ -100,6 +101,15 @@ async fn main() -> Result<(), Error> { let cache_backend = CacheSubstack::default(); runtime.merge_builder(cache_backend.into_substack()); + let networked_stack = NetworkedSubstack { + home_uri: Some( + Instance::from_str(config["giterated"]["instance"].as_str().unwrap()) + .unwrap() + .0, + ), + }; + runtime.merge_builder(networked_stack.into_server_substack()); + let runtime = runtime.finish(); stack_cell diff --git a/giterated-models/src/instance/mod.rs b/giterated-models/src/instance/mod.rs index 3aebd12..fd5a8d9 100644 --- a/giterated-models/src/instance/mod.rs +++ b/giterated-models/src/instance/mod.rs @@ -45,6 +45,10 @@ impl GiteratedObject for Instance { fn from_object_str(object_str: &str) -> Result { Ok(Instance::from_str(object_str).unwrap()) } + + fn home_uri(&self) -> String { + self.0.clone() + } } impl Display for Instance { diff --git a/giterated-models/src/object.rs b/giterated-models/src/object.rs index 4568873..2e712d6 100644 --- a/giterated-models/src/object.rs +++ b/giterated-models/src/object.rs @@ -58,6 +58,7 @@ impl< pub trait GiteratedObject: Send + Display + FromStr + Sync + Clone { fn object_name() -> &'static str; + fn home_uri(&self) -> String; fn from_object_str(object_str: &str) -> Result; } diff --git a/giterated-models/src/object/operations.rs b/giterated-models/src/object/operations.rs index 532983e..51d13b2 100644 --- a/giterated-models/src/object/operations.rs +++ b/giterated-models/src/object/operations.rs @@ -37,6 +37,10 @@ impl GiteratedObject for NetworkAnyObject { fn from_object_str(object_str: &str) -> Result { Ok(Self(object_str.to_string())) } + + fn home_uri(&self) -> String { + todo!() + } } impl Display for NetworkAnyObject { diff --git a/giterated-models/src/repository/mod.rs b/giterated-models/src/repository/mod.rs index a998b0f..2eb9579 100644 --- a/giterated-models/src/repository/mod.rs +++ b/giterated-models/src/repository/mod.rs @@ -63,6 +63,10 @@ impl GiteratedObject for Repository { fn from_object_str(object_str: &str) -> Result { Ok(Repository::from_str(object_str)?) } + + fn home_uri(&self) -> String { + self.instance.home_uri() + } } impl TryFrom for Repository { diff --git a/giterated-models/src/user/mod.rs b/giterated-models/src/user/mod.rs index 1cf1a06..7af737e 100644 --- a/giterated-models/src/user/mod.rs +++ b/giterated-models/src/user/mod.rs @@ -49,6 +49,10 @@ impl GiteratedObject for User { fn from_object_str(object_str: &str) -> Result { Ok(User::from_str(object_str)?) } + + fn home_uri(&self) -> String { + self.instance.home_uri() + } } impl Display for User { diff --git a/giterated-protocol/Cargo.toml b/giterated-protocol/Cargo.toml index fc87fe8..9512b23 100644 --- a/giterated-protocol/Cargo.toml +++ b/giterated-protocol/Cargo.toml @@ -18,4 +18,10 @@ serde = { version = "1.0.188", features = [ "derive" ]} tracing = "0.1" rand = "0.8" rsa = {version = "0.9", features = ["sha2"]} -serde_json = "1.0" \ No newline at end of file +serde_json = "1.0" +async-trait = "0.1" +tokio-tungstenite = { version = "0.20" } +tokio = { version = "1.32.0", features = ["full"] } +thiserror = "1" +bincode = "1.3" +futures-util = "0.3" diff --git a/giterated-protocol/src/lib.rs b/giterated-protocol/src/lib.rs index bf27728..69d3cc5 100644 --- a/giterated-protocol/src/lib.rs +++ b/giterated-protocol/src/lib.rs @@ -9,6 +9,7 @@ use giterated_stack::models::{ Error, GiteratedObject, GiteratedOperation, Instance, InstanceSignature, User, UserAuthenticationToken, }; +use giterated_stack::{GiteratedStack, HandlerResolvable, MissingValue}; use rsa::pkcs1::DecodeRsaPrivateKey; use rsa::pkcs1v15::SigningKey; use rsa::sha2::Sha256; @@ -17,12 +18,22 @@ use rsa::RsaPrivateKey; use serde::{Deserialize, Serialize}; pub use substack::{NetworkedObject, NetworkedOperation, NetworkedSubstack}; -#[derive(Clone, Default)] +#[derive(Clone)] pub struct NetworkOperationState { + runtime: GiteratedStack, authentication: Vec>, } impl NetworkOperationState { + pub fn new(stack: GiteratedStack) -> Self { + Self { + runtime: stack, + authentication: vec![], + } + } +} + +impl NetworkOperationState { pub fn authenticate( &mut self, provider: impl AuthenticationSourceProviders + Send + Sync + 'static, @@ -40,11 +51,11 @@ pub struct AuthenticatedPayload { } impl AuthenticatedPayload { - pub fn into_message(self) -> GiteratedMessage { + pub fn into_message(self) -> GiteratedMessage { GiteratedMessage { - object: NetworkAnyObject(self.object), + object: NetworkedObject::from_str(&self.object).unwrap(), operation: self.operation, - payload: NetworkAnyOperation(self.payload), + payload: serde_json::from_slice(&self.payload).unwrap(), } } } @@ -199,12 +210,16 @@ pub struct NetworkAnyObject(pub String); impl GiteratedObject for NetworkAnyObject { fn object_name() -> &'static str { - "any" + "network_object" } fn from_object_str(object_str: &str) -> Result { Ok(Self(object_str.to_string())) } + + fn home_uri(&self) -> String { + todo!() + } } impl Display for NetworkAnyObject { @@ -231,3 +246,29 @@ impl GiteratedOperation for NetworkAnyOperation { type Failure = Vec; } + +#[async_trait::async_trait(?Send)] +impl HandlerResolvable<(R1,), NetworkOperationState> for GiteratedStack { + type Error = MissingValue; + + async fn from_handler_state( + _required_parameters: &(R1,), + operation_state: &NetworkOperationState, + ) -> Result { + Ok(operation_state.runtime.clone()) + } +} + +#[async_trait::async_trait(?Send)] +impl HandlerResolvable<(R1, R2), NetworkOperationState> + for GiteratedStack +{ + type Error = MissingValue; + + async fn from_handler_state( + _required_parameters: &(R1, R2), + operation_state: &NetworkOperationState, + ) -> Result { + Ok(operation_state.runtime.clone()) + } +} diff --git a/giterated-protocol/src/substack.rs b/giterated-protocol/src/substack.rs index 8f66635..6eed88e 100644 --- a/giterated-protocol/src/substack.rs +++ b/giterated-protocol/src/substack.rs @@ -1,14 +1,22 @@ -use std::{fmt::Display, str::FromStr, sync::Arc}; +use std::{fmt::Display, net::SocketAddr, str::FromStr, sync::Arc}; +use futures_util::{sink::SinkExt, StreamExt}; use giterated_stack::{ - models::{Error, GiteratedObject, GiteratedOperation, IntoInternalError, OperationError}, - AnyFailure, AnyObject, AnyOperation, AnySuccess, GiteratedStack, GiteratedStackInner, - ObjectOperationPair, OperationState, StackOperationState, SubstackBuilder, + models::{ + Error, GiteratedObject, GiteratedOperation, Instance, IntoInternalError, + NetworkOperationError, OperationError, + }, + AnyFailure, AnyObject, AnyOperation, AnySuccess, GiteratedStack, ObjectOperationPair, + OperationState, StackOperationState, SubstackBuilder, }; use serde::{Deserialize, Serialize}; -use tracing::{trace, warn}; +use tokio::net::TcpStream; +use tokio_tungstenite::{connect_async, tungstenite::Message, MaybeTlsStream, WebSocketStream}; +use tracing::{info, trace, warn}; -use crate::NetworkOperationState; +use crate::{ + AuthenticatedPayload, AuthenticationSourceProviders, GiteratedMessage, NetworkOperationState, +}; /// A Giterated substack that attempts to resolve with a remote, networked Giterated Daemon. /// @@ -51,7 +59,7 @@ use crate::NetworkOperationState; /// TODO: The above docs are 100% false about the network protocol type #[derive(Clone)] pub struct NetworkedSubstack { - home_uri: Option, + pub home_uri: Option, } impl Default for NetworkedSubstack { @@ -64,7 +72,8 @@ impl NetworkedSubstack { pub fn into_server_substack(self) -> SubstackBuilder { let mut stack = SubstackBuilder::new(self); - stack.operation(handle_network_operation::); + stack.object::(); + stack.operation(handle_network_operation); // TODO: optional stack.dynamic_operation(try_handle_with_remote); @@ -73,7 +82,10 @@ impl NetworkedSubstack { } pub fn into_client_substack(self) -> SubstackBuilder { - let mut stack = SubstackBuilder::new(self); + let mut stack: SubstackBuilder = + SubstackBuilder::new(self); + + stack.object::(); // TODO: optional stack.dynamic_operation(try_handle_with_remote); @@ -89,12 +101,15 @@ pub async fn handle_network_operation( OperationState(operation_state): OperationState, stack: GiteratedStack, ) -> Result, OperationError>> { - trace!("Handle network operation"); + trace!("Handle network operation {}", operation.name); let mut result = None; for (_, object_meta) in &stack.inner.metadata.objects { + if object_meta.name == NetworkedObject::object_name() { + continue; + } + if let Ok(object) = (object_meta.from_str)(&object.0) { - // TODO: This is definitely going to resolve us result = Some((object, object_meta)); break; } @@ -103,8 +118,9 @@ pub async fn handle_network_operation( let (object, object_meta) = result.ok_or_else(|| OperationError::Unhandled)?; trace!( - "Resolved object type {} for network operation.", - object_meta.name + "Resolved object type {} for network operation {}.", + object_meta.name, + operation.name ); let operation_meta = stack @@ -123,6 +139,7 @@ pub async fn handle_network_operation( operation_meta.name ); + info!("Operation: {:?}", operation.payload); let operation = (operation_meta.deserialize)(&operation.payload) .as_internal_error_with_context(format!( "deserializing object operation {}::{}", @@ -140,23 +157,48 @@ pub async fn handle_network_operation( .await; match result { - Ok(success) => Ok((operation_meta.serialize_success)(success) - .as_internal_error_with_context(format!( - "serializing success for object operation {}::{}", - object_meta.name, operation_meta.name - ))?), - Err(err) => Err(match err { - OperationError::Operation(failure) => OperationError::Operation( - (operation_meta.serialize_error)(failure).as_internal_error_with_context( + Ok(success) => { + trace!( + "Network operation {}::{} was successful", + object_meta.name, + operation_meta.name + ); + + Ok( + (operation_meta.serialize_success)(success).as_internal_error_with_context( format!( - "serializing error for object operation {}::{}", + "serializing success for object operation {}::{}", object_meta.name, operation_meta.name ), )?, - ), - OperationError::Internal(internal) => OperationError::Internal(internal), - OperationError::Unhandled => OperationError::Unhandled, - }), + ) + } + Err(err) => { + trace!( + "Network operation {}::{} failed", + object_meta.name, + operation_meta.name + ); + Err(match err { + OperationError::Operation(failure) => OperationError::Operation( + (operation_meta.serialize_error)(failure).as_internal_error_with_context( + format!( + "serializing error for object operation {}::{}", + object_meta.name, operation_meta.name + ), + )?, + ), + OperationError::Internal(internal) => { + warn!( + "A networked operation encountered an internal error: {:#?}", + internal + ); + + OperationError::Internal(internal) + } + OperationError::Unhandled => OperationError::Unhandled, + }) + } } } @@ -166,36 +208,40 @@ pub struct NetworkedObject(pub String); impl FromStr for NetworkedObject { type Err = (); - fn from_str(_s: &str) -> Result { - todo!() + fn from_str(s: &str) -> Result { + Ok(NetworkedObject(s.to_string())) } } impl Display for NetworkedObject { - fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - todo!() + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(&self.0) } } impl GiteratedObject for NetworkedObject { fn object_name() -> &'static str { - todo!() + "networked_object" } fn from_object_str(_object_str: &str) -> Result { todo!() } + + fn home_uri(&self) -> String { + todo!() + } } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct NetworkedOperation { - name: String, - payload: Vec, + pub name: String, + pub payload: Vec, } impl NetworkedOperation { - pub fn new(_name: String, _payload: Vec) -> Self { - todo!() + pub fn new(name: String, payload: Vec) -> Self { + Self { name, payload } } } @@ -205,19 +251,21 @@ impl GiteratedOperation for NetworkedOperation { type Failure = Vec; fn operation_name() -> &'static str { - "network_operation" + "networked_operation" } } /// Handler which will attempt to resolve any operation that doesn't resolve locally /// against a remote instance. -pub async fn try_handle_with_remote( +pub async fn try_handle_with_remote( object: AnyObject, operation: AnyOperation, state: NetworkedSubstack, - _operation_state: StackOperationState, - stack: Arc, + _stack: GiteratedStack, ) -> Result> { + if object.is::() { + return Err(OperationError::Unhandled); + } trace!( "Try handling object operation {}::{} with remote", object.kind(), @@ -226,17 +274,15 @@ pub async fn try_handle_with_remote( // TODO: // Ideally we support pass-through on object types that aren't used locally. // For now, we aren't worrying about that. - let object_meta = stack - .metadata - .objects - .get(object.kind()) - .ok_or_else(|| OperationError::Unhandled)?; + let object_meta = object.meta().clone(); - let _operation_meta = stack - .metadata - .operations - .get(&operation.kind()) - .ok_or_else(|| OperationError::Unhandled)?; + let operation_meta = operation.meta().clone(); + + trace!( + "Serializing with {}::{}", + operation.kind().object_name, + operation.kind().operation_name + ); let object_home_uri = (object_meta.home_uri)(object.clone()); @@ -251,7 +297,191 @@ pub async fn try_handle_with_remote( } } - // Blah blah connect and do the stuff + trace!( + "Handling object operation {}::{} sending payload", + object.kind(), + operation.kind().operation_name + ); + + let object = NetworkedObject((object_meta.to_str)(object).to_string()); + let operation = NetworkedOperation::new( + operation_meta.name.clone(), + (operation_meta.serialize)(operation.clone()).as_internal_error_with_context(format!( + "try serializing object operation {}::{} for remote", + object_meta.name, operation_meta.name + ))?, + ); + + // let authenticated = Authenticated::new(object, operation); + + let message = GiteratedMessage { + object, + operation: NetworkedOperation::operation_name().to_string(), + payload: operation, + }; + + let authenticated = Authenticated::new(message); + + let mut socket: WebSocketStream> = connect_to( + &Instance::from_str(&object_home_uri).unwrap(), + &Some(("127.0.0.1:1111").parse().unwrap()), + ) + .await + .as_internal_error()?; + + // TODO AUTH - todo!() + let result: Result, OperationError>> = + send_expect(&mut socket, authenticated).await; + + match result { + Ok(success) => { + let success = (operation_meta.deserialize_success)(success) + .as_internal_error_with_context(format!( + "try deserializing object operation success {}::{} for remote", + object_meta.name, operation_meta.name + ))?; + + Ok(success) + } + Err(err) => Err(match err { + OperationError::Operation(failure) => { + let failure = (operation_meta.deserialize_failure)(failure) + .as_internal_error_with_context(format!( + "try deserializing object operation failure {}::{} for remote", + object_meta.name, operation_meta.name + ))?; + + OperationError::Operation(failure) + } + OperationError::Internal(internal) => OperationError::Internal(internal), + OperationError::Unhandled => OperationError::Unhandled, + }), + } +} + +type Socket = WebSocketStream>; + +async fn connect_to( + instance: &Instance, + + socket_addr: &Option, +) -> Result { + if let Some(addr) = socket_addr { + info!( + "Connecting to {}", + format!("ws://{}/.giterated/daemon/", addr) + ); + + let (websocket, _response) = + connect_async(&format!("ws://{}/.giterated/daemon/", addr)).await?; + + info!("Connection established with {}", addr); + + Ok(websocket) + } else { + info!( + "Connecting to {}", + format!("wss://{}/.giterated/daemon/", instance.0) + ); + + let (websocket, _response) = + connect_async(&format!("wss://{}/.giterated/daemon/", instance.0)).await?; + + info!("Connection established with {}", instance.0); + + Ok(websocket) + } +} + +async fn send_expect>( + socket: &mut Socket, + message: Authenticated, +) -> Result, OperationError>> { + let payload = bincode::serialize(&message.into_payload()).unwrap(); + + socket + .send(Message::Binary(payload)) + .await + .as_internal_error()?; + + while let Some(message) = socket.next().await { + let payload = match message.as_internal_error()? { + Message::Binary(payload) => payload, + + _ => { + continue; + } + }; + + let raw_result = + bincode::deserialize::, NetworkOperationError>>>(&payload) + .map_err(|e| OperationError::Internal(Error::from(e)))?; + + trace!( + "Received response for networked operation {}::{}.", + O::object_name(), + D::operation_name() + ); + + return match raw_result { + Ok(success) => Ok(success), + Err(err) => Err(match err { + NetworkOperationError::Operation(operation_error) => { + OperationError::Operation(operation_error) + } + NetworkOperationError::Internal => OperationError::Internal(RemoteError.into()), + NetworkOperationError::Unhandled => OperationError::Unhandled, + }), + }; + } + + panic!() +} + +#[derive(Debug, Clone, thiserror::Error)] +#[error("a remote internal error occurred")] +pub struct RemoteError; + +#[derive(Debug, thiserror::Error)] +#[error("a remote internal error occurred")] + +pub struct NetworkError; + +#[derive(Debug)] +pub struct Authenticated> { + pub source: Vec>, + pub message: GiteratedMessage, +} + +impl> Authenticated { + pub fn new(message: GiteratedMessage) -> Self { + Self { + source: vec![], + message, + } + } + + pub fn append_authentication( + &mut self, + authentication: Arc, + ) { + self.source.push(authentication); + } + + pub fn into_payload(mut self) -> AuthenticatedPayload { + let payload = serde_json::to_vec(&self.message.payload).unwrap(); + + AuthenticatedPayload { + object: self.message.object.to_string(), + operation: self.message.operation, + source: self + .source + .drain(..) + .map(|provider| provider.as_ref().authenticate_all(&payload)) + .flatten() + .collect::>(), + payload, + } + } } diff --git a/giterated-stack/src/dynamic.rs b/giterated-stack/src/dynamic.rs index c686283..cecf318 100644 --- a/giterated-stack/src/dynamic.rs +++ b/giterated-stack/src/dynamic.rs @@ -5,11 +5,14 @@ use giterated_models::{ value::GiteratedObjectValue, }; -use crate::{ObjectOperationPair, ObjectSettingPair, ObjectValuePair, ValueMeta}; +use crate::{ + ObjectMeta, ObjectOperationPair, ObjectSettingPair, ObjectValuePair, OperationMeta, ValueMeta, +}; #[derive(Clone)] pub struct AnyObject { inner: Arc, + meta: Arc, kind: &'static str, } @@ -17,6 +20,7 @@ impl AnyObject { pub fn new(object: O) -> Self { Self { inner: Arc::new(object) as _, + meta: Arc::new(ObjectMeta::new::()), kind: O::object_name(), } } @@ -28,6 +32,10 @@ impl AnyObject { pub fn kind(&self) -> &'static str { self.kind } + + pub fn meta(&self) -> &Arc { + &self.meta + } } impl Deref for AnyObject { @@ -41,13 +49,17 @@ impl Deref for AnyObject { #[derive(Clone)] pub struct AnyOperation { inner: Arc, + meta: Arc, kind: ObjectOperationPair<'static>, } impl AnyOperation { - pub fn new + 'static>(operation: D) -> Self { + pub fn new + 'static>( + operation: D, + ) -> Self { Self { inner: Arc::new(operation) as _, + meta: Arc::new(OperationMeta::new::()), kind: ObjectOperationPair::from_types::(), } } @@ -62,6 +74,10 @@ impl AnyOperation { pub fn kind(&self) -> ObjectOperationPair<'static> { self.kind } + + pub fn meta(&self) -> &Arc { + &self.meta + } } impl Deref for AnyOperation { diff --git a/giterated-stack/src/handler/mod.rs b/giterated-stack/src/handler/mod.rs index f6119b4..30fa024 100644 --- a/giterated-stack/src/handler/mod.rs +++ b/giterated-stack/src/handler/mod.rs @@ -213,7 +213,10 @@ impl HandlerResolvable for AuthenticatedUser { _required_parameters: &R, operation_state: &StackOperationState, ) -> Result { - operation_state.user.clone().ok_or_else(|| MissingValue) + operation_state + .user + .clone() + .ok_or_else(|| MissingValue("AuthenticatedUser")) } } @@ -225,6 +228,9 @@ impl HandlerResolvable for AuthenticatedInstance { _required_parameters: &R, operation_state: &StackOperationState, ) -> Result { - operation_state.instance.clone().ok_or_else(|| MissingValue) + operation_state + .instance + .clone() + .ok_or_else(|| MissingValue("AuthenticatedInstance")) } } diff --git a/giterated-stack/src/lib.rs b/giterated-stack/src/lib.rs index 2a9b493..9213869 100644 --- a/giterated-stack/src/lib.rs +++ b/giterated-stack/src/lib.rs @@ -17,7 +17,7 @@ pub mod update; pub mod models { pub use anyhow::Error; pub use giterated_models::authenticated::*; - pub use giterated_models::error::{IntoInternalError, OperationError}; + pub use giterated_models::error::{IntoInternalError, NetworkOperationError, OperationError}; pub use giterated_models::instance::Instance; pub use giterated_models::object::GiteratedObject; pub use giterated_models::operation::GiteratedOperation; @@ -114,8 +114,8 @@ impl> FromOperationState } #[derive(Debug, thiserror::Error, Serialize, Deserialize)] -#[error("missing value")] -pub struct MissingValue; +#[error("missing value {0}")] +pub struct MissingValue(&'static str); #[async_trait::async_trait(?Send)] impl + Send + Sync> FromOperationState @@ -128,7 +128,10 @@ impl + Send + Sync> FromOperationSt _operation: &D, state: &StackOperationState, ) -> Result> { - state.user.clone().ok_or(ExtractorError(MissingValue)) + state + .user + .clone() + .ok_or(ExtractorError(MissingValue("AuthenticatedUser"))) } } @@ -143,7 +146,10 @@ impl + Send + Sync> FromOperationSt _operation: &D, state: &StackOperationState, ) -> Result> { - state.instance.clone().ok_or(ExtractorError(MissingValue)) + state + .instance + .clone() + .ok_or(ExtractorError(MissingValue("AuthenticatedInstance"))) } } @@ -208,7 +214,10 @@ impl AuthorizedOperation for SetSetting { authorize_for: &User, operation_state: &StackOperationState, ) -> Result> { - let authenticated_user = operation_state.user.as_ref().ok_or(MissingValue)?; + let authenticated_user = operation_state + .user + .as_ref() + .ok_or(MissingValue("AuthenticatedUser"))?; Ok(authorize_for == authenticated_user.deref()) } @@ -223,7 +232,10 @@ impl AuthorizedOperation for GetSetting { authorize_for: &User, operation_state: &StackOperationState, ) -> Result> { - let authenticated_user = operation_state.user.as_ref().ok_or(MissingValue)?; + let authenticated_user = operation_state + .user + .as_ref() + .ok_or(MissingValue("AuthenticatedUser"))?; Ok(authorize_for == authenticated_user.deref()) } @@ -241,7 +253,7 @@ impl AuthorizedOperation for SetSetting { let authenticated_user = operation_state .user .as_ref() - .ok_or_else(|| anyhow::Error::from(MissingValue))?; + .ok_or_else(|| anyhow::Error::from(MissingValue("AuthenticatedUser")))?; let mut object = operation_state .runtime @@ -278,7 +290,7 @@ impl AuthorizedOperation for GetSetting { let authenticated_user = operation_state .user .as_ref() - .ok_or_else(|| anyhow::Error::from(MissingValue))?; + .ok_or_else(|| anyhow::Error::from(MissingValue("AuthenticatedUser")))?; let mut object = operation_state .runtime @@ -466,13 +478,13 @@ where pub struct OperationState(pub OS); #[async_trait::async_trait(?Send)] -impl HandlerResolvable for OperationState { +impl HandlerResolvable for OperationState { type Error = MissingValue; async fn from_handler_state( _required_parameters: &P, - _operation_state: &OS, + operation_state: &OS, ) -> Result { - todo!() + Ok(Self(operation_state.clone())) } } diff --git a/giterated-stack/src/meta/mod.rs b/giterated-stack/src/meta/mod.rs index bcab915..62ed475 100644 --- a/giterated-stack/src/meta/mod.rs +++ b/giterated-stack/src/meta/mod.rs @@ -1,11 +1,11 @@ -use std::{any::Any, collections::HashMap, str::FromStr}; +use std::{any::Any, collections::HashMap, str::FromStr, sync::Arc}; use futures_util::{future::LocalBoxFuture, FutureExt}; use giterated_models::{ object::GiteratedObject, operation::GiteratedOperation, - settings::Setting, - value::{GetValueTyped, GiteratedObjectValue}, + settings::{SetSetting, Setting}, + value::{GetValue, GetValueTyped, GiteratedObjectValue}, }; use serde_json::Value; use tracing::trace; @@ -91,6 +91,11 @@ impl RuntimeMetadata { V::value_name() ); } + + self.operations.insert( + ObjectOperationPair::from_types::(), + OperationMeta::new::(), + ); } pub fn register_setting(&mut self) { @@ -109,6 +114,11 @@ impl RuntimeMetadata { } else { trace!("Registration of setting {}.", S::name()); } + + self.operations.insert( + ObjectOperationPair::from_types::(), + OperationMeta::new::(), + ); } pub fn append(&mut self, other: Self) { @@ -176,10 +186,13 @@ impl ValueMeta { pub struct OperationMeta { pub name: String, pub object_kind: String, + pub serialize: fn(AnyOperation) -> Result, serde_json::Error>, pub deserialize: fn(&[u8]) -> Result, pub any_is_same: fn(&dyn Any) -> bool, pub serialize_success: fn(AnySuccess) -> Result, serde_json::Error>, pub serialize_error: fn(AnyFailure) -> Result, serde_json::Error>, + pub deserialize_success: fn(Vec) -> Result, + pub deserialize_failure: fn(Vec) -> Result, } pub trait IntoOperationMeta { @@ -187,14 +200,17 @@ pub trait IntoOperationMeta { fn deserialize(buffer: &[u8]) -> Result; fn serialize_success(success: AnySuccess) -> Result, serde_json::Error>; fn serialize_failure(failure: AnyFailure) -> Result, serde_json::Error>; + fn deserialize_success(success: Vec) -> Result; + fn deserialize_failure(failure: Vec) -> Result; fn any_is_same(other: &dyn Any) -> bool; + fn serialize(operation: AnyOperation) -> Result, serde_json::Error>; } impl IntoOperationMeta for D where D::Failure: 'static, D::Success: 'static, - O: GiteratedObject, + O: GiteratedObject + 'static, D: GiteratedOperation + 'static, { fn name() -> String { @@ -218,6 +234,24 @@ where fn any_is_same(other: &dyn Any) -> bool { other.is::() } + + fn serialize(operation: AnyOperation) -> Result, serde_json::Error> { + let operation: &D = operation.downcast_ref().unwrap(); + + serde_json::to_vec(operation) + } + + fn deserialize_success(success: Vec) -> Result { + let success: D::Success = serde_json::from_slice(&success)?; + + Ok(AnySuccess(Arc::new(success))) + } + + fn deserialize_failure(failure: Vec) -> Result { + let failure: D::Failure = serde_json::from_slice(&failure)?; + + Ok(AnyFailure(Arc::new(failure))) + } } impl OperationMeta { @@ -229,6 +263,9 @@ impl OperationMeta { serialize_error: I::serialize_failure, object_kind: O::object_name().to_string(), any_is_same: I::any_is_same, + serialize: I::serialize, + deserialize_success: I::deserialize_success, + deserialize_failure: I::deserialize_failure, } } } @@ -256,8 +293,10 @@ impl IntoObjectMeta for O { other.is::() } - fn home_uri(_object: AnyObject) -> String { - todo!() + fn home_uri(object: AnyObject) -> String { + let object: &O = object.downcast_ref().unwrap(); + + object.home_uri() } } @@ -276,7 +315,7 @@ impl ObjectMeta { object.to_string() }), any_is_same: I::any_is_same, - home_uri: I::home_uri, + home_uri: ::home_uri, } } } diff --git a/giterated-stack/src/stack.rs b/giterated-stack/src/stack.rs index 5605eef..3357901 100644 --- a/giterated-stack/src/stack.rs +++ b/giterated-stack/src/stack.rs @@ -3,10 +3,15 @@ use std::any::Any; use std::fmt::Debug; use std::marker::PhantomData; use std::ops::Deref; +use std::str::FromStr; use std::{collections::HashMap, sync::Arc}; +use anyhow::Error; use giterated_models::error::IntoInternalError; -use giterated_models::settings::Setting; +use giterated_models::instance::Instance; +use giterated_models::object::ObjectResponse; +use giterated_models::settings::{SetSettingError, Setting}; + use giterated_models::value::GetValue; use giterated_models::{ error::OperationError, @@ -86,11 +91,27 @@ pub struct GiteratedStackBuilder { inner: GiteratedStackInner, } -impl Default for GiteratedStackBuilder { +impl Default for GiteratedStackBuilder +where + GiteratedStack: HandlerResolvable<(Instance, ObjectRequest), OS>, + as HandlerResolvable<(Instance, ObjectRequest), OS>>::Error: Into, +{ fn default() -> Self { - Self { + let mut stack = Self { inner: Default::default(), - } + }; + + #[derive(Clone)] + struct InstanceResolver; + + let mut builder: SubstackBuilder = + SubstackBuilder::new(InstanceResolver); + + builder.object::(); + + stack.merge_builder(builder); + + stack } } @@ -383,7 +404,7 @@ impl ObjectBackend for GiteratedStack async fn object_operation( &self, in_object: O, - operation_name: &str, + _operation_name: &str, payload: D, operation_state: &OS, ) -> Result> @@ -393,10 +414,130 @@ impl ObjectBackend for GiteratedStack D::Success: Clone, D::Failure: Clone, { + trace!( + "Object operation for {}::{}", + O::object_name(), + D::operation_name() + ); // Erase object and operation types. let object = AnyObject::new(in_object.clone()); let operation = AnyOperation::new(payload); + let raw_result = self + .new_operation_func(object, operation, operation_state.clone()) + .await; + + // Convert the dynamic result back into its concrete type + match raw_result { + Ok(result) => Ok(result.0.downcast_ref::().unwrap().clone()), + Err(err) => Err(match err { + OperationError::Internal(internal) => { + OperationError::Internal(internal.context(format!( + "operation {}::{} handler outcome", + O::object_name(), + D::operation_name() + ))) + } + OperationError::Operation(boxed_error) => OperationError::Operation( + boxed_error.0.downcast_ref::().unwrap().clone(), + ), + OperationError::Unhandled => OperationError::Unhandled, + }), + } + } + + async fn get_object( + &self, + object_str: &str, + operation_state: &OS, + ) -> Result, OperationError> + where + O: GiteratedObject + Debug + 'static, + { + // TODO: Authorization? + for (object_name, object_meta) in self.inner.metadata.objects.iter() { + if object_name != O::object_name() { + continue; + } + + if let Ok(object) = (object_meta.from_str)(object_str) { + return Ok(unsafe { + Object::new_unchecked(object.downcast_ref::().unwrap().clone(), self.clone()) + }); + } + } + + if let Some(handler) = self.operation_handlers.get(&ObjectOperationPair { + object_name: "any", + operation_name: "any", + }) { + let result = handler + .handle( + ( + AnyObject::new(Instance::from_str("giterated.dev").unwrap()), + AnyOperation::new(ObjectRequest(object_str.to_string())), + ), + operation_state.clone(), + ) + .await; + + match result { + Ok(success) => { + let object: &ObjectResponse = success.0.downcast_ref().unwrap(); + + return Ok(unsafe { + Object::new_unchecked(O::from_object_str(&object.0).unwrap(), self.clone()) + }); + } + Err(err) => match err { + OperationError::Operation(failure) => { + let failure: &ObjectRequestError = failure.0.downcast_ref().unwrap(); + + return Err(OperationError::Operation(failure.clone())); + } + OperationError::Internal(internal) => { + return Err(OperationError::Internal(internal)) + } + OperationError::Unhandled => {} + }, + }; + } + + Err(OperationError::Unhandled) + } +} + +// Placeholder +impl GiteratedStack { + pub async fn new_operation_func( + &self, + object: AnyObject, + operation: AnyOperation, + operation_state: OS, + ) -> Result> { + let operation_name = operation.kind().operation_name; + + if let Some(handler_tree) = self.inner.operation_handlers.get(&ObjectOperationPair { + object_name: "any", + operation_name: "any", + }) { + match handler_tree + .handle((object.clone(), operation.clone()), operation_state.clone()) + .await + { + Ok(success) => return Ok(success), + Err(err) => match err { + OperationError::Operation(operation) => { + return Err(OperationError::Operation(operation)) + } + OperationError::Internal(internal) => { + return Err(OperationError::Internal(internal)) + } + OperationError::Unhandled => {} + }, + } + } + // We need to hijack get_value, set_setting, and get_setting. if operation_name == "get_value" { let get_value = operation @@ -408,17 +549,13 @@ impl ObjectBackend for GiteratedStack .metadata .values .get(&ObjectValuePair { - object_kind: O::object_name(), + object_kind: object.kind(), value_kind: &get_value.value_name, }) .ok_or_else(|| OperationError::Unhandled)?; let value_name = value_meta.name.clone(); - trace!( - "Handling get_value for {}::{}", - O::object_name(), - value_name - ); + trace!("Handling get_value for {}::{}", object.kind(), value_name); if let Some(handler) = self.inner.value_getters.get(&ObjectValuePair { object_kind: "any", @@ -432,8 +569,8 @@ impl ObjectBackend for GiteratedStack .await { Ok(success) => { - self.value_update(in_object, success.clone(), operation_state) - .await; + // self.value_update(in_object, success.clone(), operation_state) + // .await; return Ok(*(Box::new((value_meta.serialize)(success).unwrap()) as Box) @@ -443,13 +580,7 @@ impl ObjectBackend for GiteratedStack Err(err) => { match err { OperationError::Operation(operation_error) => { - return Err(OperationError::Operation( - operation_error - .0 - .downcast_ref::() - .unwrap() - .clone(), - )); + return Err(OperationError::Operation(operation_error)); } OperationError::Internal(internal) => { // This DOES NOT result in an early return @@ -464,7 +595,7 @@ impl ObjectBackend for GiteratedStack } if let Some(handler) = self.inner.value_getters.get(&ObjectValuePair { - object_kind: O::object_name(), + object_kind: object.kind(), value_kind: "any", }) { match handler @@ -475,8 +606,8 @@ impl ObjectBackend for GiteratedStack .await { Ok(success) => { - self.value_update(in_object, success.clone(), operation_state) - .await; + // self.value_update(in_object, success.clone(), operation_state) + // .await; return Ok(*(Box::new((value_meta.serialize)(success).unwrap()) as Box) @@ -486,13 +617,7 @@ impl ObjectBackend for GiteratedStack Err(err) => { match err { OperationError::Operation(operation_error) => { - return Err(OperationError::Operation( - operation_error - .0 - .downcast_ref::() - .unwrap() - .clone(), - )); + return Err(OperationError::Operation(operation_error)); } OperationError::Internal(internal) => { // This DOES NOT result in an early return @@ -518,8 +643,8 @@ impl ObjectBackend for GiteratedStack .await { Ok(success) => { - self.value_update(in_object, success.clone(), operation_state) - .await; + // self.value_update(in_object, success.clone(), operation_state) + // .await; return Ok(*(Box::new((value_meta.serialize)(success).unwrap()) as Box) @@ -529,13 +654,7 @@ impl ObjectBackend for GiteratedStack Err(err) => { match err { OperationError::Operation(operation_error) => { - return Err(OperationError::Operation( - operation_error - .0 - .downcast_ref::() - .unwrap() - .clone(), - )); + return Err(OperationError::Operation(operation_error)); } OperationError::Internal(internal) => { // This DOES NOT result in an early return @@ -550,7 +669,7 @@ impl ObjectBackend for GiteratedStack } if let Some(handler) = self.inner.value_getters.get(&ObjectValuePair { - object_kind: O::object_name(), + object_kind: object.kind(), value_kind: &get_value.value_name, }) { match handler @@ -561,24 +680,18 @@ impl ObjectBackend for GiteratedStack .await { Ok(success) => { - self.value_update(in_object, success.clone(), operation_state) - .await; + // self.value_update(in_object, success.clone(), operation_state) + // .await; - return Ok(*(Box::new((value_meta.serialize)(success).unwrap()) - as Box) - .downcast() - .unwrap()); + return Ok(AnySuccess(Arc::new( + (value_meta.serialize)(success) + .as_internal_error_with_context("serializing value")?, + ))); } Err(err) => { match err { OperationError::Operation(operation_error) => { - return Err(OperationError::Operation( - operation_error - .0 - .downcast_ref::() - .unwrap() - .clone(), - )); + return Err(OperationError::Operation(operation_error)); } OperationError::Internal(internal) => { // This DOES NOT result in an early return @@ -597,20 +710,32 @@ impl ObjectBackend for GiteratedStack let raw_result = self .get_setting( - object, - O::object_name().to_string(), + object.clone(), + object.kind().to_string(), get_setting.clone(), - operation_state, + &operation_state, ) .await; + let setting_meta = self + .metadata + .settings + .get(&ObjectSettingPair { + object_kind: object.kind(), + setting_name: &get_setting.setting_name, + }) + .ok_or_else(|| OperationError::Unhandled)?; + return match raw_result { Ok(success) => { // Success is the setting type, serialize it // let serialized = (setting_meta.serialize)(success).unwrap(); // Ok(serde_json::to_vec(&serialized).unwrap()) - Ok(success.downcast_ref::().unwrap().clone()) + // Ok(success.downcast_ref::().unwrap().clone()) + Ok(AnySuccess(Arc::new( + (setting_meta.serialize)(success).unwrap(), + ))) } Err(err) => Err(match err { OperationError::Operation(failure) => { @@ -620,7 +745,7 @@ impl ObjectBackend for GiteratedStack OperationError::Internal(internal) => { OperationError::Internal(internal.context(format!( "{}::get_setting::<{}> handler outcome", - O::object_name(), + object.kind(), setting_name ))) } @@ -628,94 +753,157 @@ impl ObjectBackend for GiteratedStack }), }; } else if operation.is::() { - todo!() - } else if operation.is::() { - todo!() - } + let operation: &SetSetting = operation.downcast_ref().unwrap(); + let object_type = object.kind(); - // Resolve the operation from the known operations table. - let operation_type = { - let mut operation_type = None; + trace!( + "Handling {}::set_setting for {}", + object_type, + operation.setting_name + ); - for (target, operation_meta) in self.inner.metadata.operations.iter() { - // Skip elements that we know will not match - if target.object_name != O::object_name() { - continue; + let setting_meta = self + .metadata + .settings + .get(&ObjectSettingPair { + object_kind: &object_type, + setting_name: &operation.setting_name, + }) + // TODO: Check this + .ok_or(OperationError::Operation(AnyFailure(Arc::new( + SetSettingError::InvalidSetting( + operation.setting_name.clone(), + object_type.to_string().clone(), + ), + ))))?; + + let setting = (setting_meta.deserialize)(operation.value.clone()) + .as_internal_error_with_context(format!( + "deserializing setting {} for object {}", + operation.setting_name, object_type + ))?; + + trace!( + "Deserialized setting {} for object {}", + operation.setting_name, + object_type, + ); + + for provider in self.metadata_providers.iter() { + if provider.provides_for(object.deref()) { + trace!( + "Resolved setting provider for setting {} for object {}", + operation.setting_name, + object_type, + ); + + let object_meta = self + .metadata + .objects + .get(object_type) + .ok_or_else(|| OperationError::Unhandled)?; + + let raw_result = provider + .write(object.clone(), object_meta, setting.clone(), setting_meta) + .await; + + return match raw_result { + Ok(_) => { + warn!("Setting updated not implemented"); + // (setting_meta.setting_updated)( + // object, + // setting, + // self.clone(), + // operation_state, + // ) + // .await; + + Ok(AnySuccess(Arc::new(()))) + } + Err(e) => Err(OperationError::Internal(e.context(format!( + "writing object {} setting {}", + object_type, operation.setting_name + )))), + }; } - if target.operation_name != operation_name { + trace!( + "Failed to resolve setting provider for setting {} for object {}", + operation.setting_name, + object_type, + ); + } + } else if operation.is::() { + let object_request: &ObjectRequest = operation.downcast_ref().unwrap(); + trace!("handling object request for {}", object_request.0); + + // TODO: Authorization? + for (_object_name, object_meta) in self.inner.metadata.objects.iter() { + if object_meta.name == "networked_object" { + // TODO: HACK continue; } - if (operation_meta.any_is_same)(&operation) { - operation_type = Some(target.clone()); - break; + if let Ok(object) = (object_meta.from_str)(&object_request.0) { + trace!("object request resolved as type {}", object_meta.name); + + return Ok(AnySuccess(Arc::new(ObjectResponse((object_meta.to_str)( + object, + ))))); + } + } + + if let Some(handler) = self.operation_handlers.get(&ObjectOperationPair { + object_name: "any", + operation_name: "any", + }) { + let result = handler + .handle( + ( + AnyObject::new(Instance::from_str("giterated.dev").unwrap()), + AnyOperation::new(ObjectRequest(object_request.0.to_string())), + ), + operation_state.clone(), + ) + .await; + + match result { + Ok(success) => { + let object: &ObjectResponse = success.0.downcast_ref().unwrap(); + + return Ok(AnySuccess(Arc::new(object.clone()))); + } + Err(_err) => { + todo!() + } } } - operation_type + return Err(OperationError::Unhandled); } - .ok_or_else(|| OperationError::Unhandled)?; + + trace!( + "Object operation for {}::{} is not special case", + object.kind(), + operation_name + ); // Resolve the handler from our handler tree let handler_tree = self .inner .operation_handlers - .get(&operation_type) + .get(&operation.kind()) .ok_or_else(|| OperationError::Unhandled)?; - let raw_result = handler_tree - .handle((object, operation), operation_state.clone()) - .await; - - // Convert the dynamic result back into its concrete type - match raw_result { - Ok(result) => Ok(result.0.downcast_ref::().unwrap().clone()), - Err(err) => Err(match err { - OperationError::Internal(internal) => { - OperationError::Internal(internal.context(format!( - "operation {}::{} handler outcome", - operation_type.object_name, operation_type.operation_name - ))) - } - OperationError::Operation(boxed_error) => OperationError::Operation( - boxed_error.0.downcast_ref::().unwrap().clone(), - ), - OperationError::Unhandled => OperationError::Unhandled, - }), - } - } - - async fn get_object( - &self, - object_str: &str, - _operation_state: &OS, - ) -> Result, OperationError> - where - O: GiteratedObject + Debug + 'static, - { - // TODO: Authorization? - for (_object_name, object_meta) in self.inner.metadata.objects.iter() { - if let Ok(object) = (object_meta.from_str)(object_str) { - return Ok(unsafe { - Object::new_unchecked(object.downcast_ref::().unwrap().clone(), self.clone()) - }); - } - } - - Err(OperationError::Unhandled) - } -} + trace!( + "Object operation for {}::{} handler tree resolved", + object.kind(), + operation_name + ); -// Placeholder -impl GiteratedStack { - pub async fn new_operation_func( - &self, - _object: AnyObject, - _operation: AnyOperation, - _operation_state: OS, - ) -> Result> { - todo!() + handler_tree + .handle((object, operation), operation_state.clone()) + .await } } diff --git a/giterated-stack/src/substack.rs b/giterated-stack/src/substack.rs index 3d914fe..994527b 100644 --- a/giterated-stack/src/substack.rs +++ b/giterated-stack/src/substack.rs @@ -1,5 +1,6 @@ use std::{collections::HashMap, marker::PhantomData, sync::Arc}; +use anyhow::Error; use futures_util::FutureExt; use giterated_models::{ error::OperationError, @@ -13,10 +14,10 @@ use tracing::{info, trace}; use crate::{ handler::HandlerWrapper, provider::MetadataProvider, AnyFailure, AnyObject, AnyOperation, - AnySetting, AnySuccess, AnyValue, GiteratedStack, GiteratedStackState, IntoGiteratedHandler, - MaybeDynamicObject, MaybeDynamicValue, ObjectOperationPair, ObjectSettingPair, ObjectValuePair, - OperationHandler, OperationState, RuntimeMetadata, SettingChange, SettingGetter, - StackOperationState, ValueChange, ValueGetter, + AnySetting, AnySuccess, AnyValue, GiteratedStack, GiteratedStackState, HandlerResolvable, + IntoGiteratedHandler, MaybeDynamicObject, MaybeDynamicValue, ObjectOperationPair, + ObjectSettingPair, ObjectValuePair, OperationHandler, OperationState, RuntimeMetadata, + SettingChange, SettingGetter, StackOperationState, ValueChange, ValueGetter, }; pub struct SubstackBuilder { @@ -184,14 +185,17 @@ impl SubstackBuilder SubstackBuilder { /// Register a [`GiteratedObject`] type with the runtime. /// /// # Type Registration /// This will register the provided object type. - pub fn object(&mut self) -> &mut Self { + pub fn object(&mut self) -> &mut Self + where + GiteratedStack: HandlerResolvable<(Instance, ObjectRequest), OS>, + as HandlerResolvable<(Instance, ObjectRequest), OS>>::Error: + Into, + { self.metadata.register_object::(); // Insert handler so ObjectRequest is handled properly @@ -200,7 +204,7 @@ impl SubstackBuilder { move |_object: Instance, operation: ObjectRequest, _state: S, - stack: GiteratedStack| { + stack: GiteratedStack| { let operation = operation.clone(); async move { for (_object_name, object_meta) in stack.inner.metadata.objects.iter() { @@ -217,7 +221,9 @@ impl SubstackBuilder { self } +} +impl SubstackBuilder { /// Register a [`Setting`] type with the runtime. /// /// # Type Registration @@ -305,8 +311,28 @@ impl SubstackBuilder { // Placeholder impl SubstackBuilder { - pub fn dynamic_operation(&mut self, _handler: H) -> &mut Self { - tracing::error!("Dynamic unimplemented"); + pub fn dynamic_operation(&mut self, handler: H) -> &mut Self + where + H: IntoGiteratedHandler< + (AnyObject, AnyOperation), + A, + S, + OS, + Result>, + > + Send + + Sync + + 'static, + OS: Clone + Send + Sync + 'static, + { + let wrapped = HandlerWrapper::new(self.state.clone(), handler); + + self.operation_handlers.insert( + ObjectOperationPair { + object_name: "any", + operation_name: "any", + }, + wrapped, + ); self }