use std::{fmt::Display, str::FromStr, sync::Arc}; use giterated_stack::{ models::{Error, GiteratedObject, GiteratedOperation, IntoInternalError, OperationError}, AnyFailure, AnyObject, AnyOperation, AnySuccess, GiteratedStack, GiteratedStackInner, ObjectOperationPair, OperationState, StackOperationState, SubstackBuilder, }; use serde::{Deserialize, Serialize}; use tracing::{trace, warn}; use crate::NetworkOperationState; /// A Giterated substack that attempts to resolve with a remote, networked Giterated Daemon. /// /// # Usage /// /// Convert the [`NetworkedSubstack`] into a [`SubStackBuilder`] and merge it with /// a runtime. /// /// ``` /// let mut runtime = GiteratedStack::default(); /// /// let network_substack = NetworkedSubstack::default(); /// /// runtime.merge_builder(network_substack.into_substack()); /// ``` /// /// To handle messages that are sourced from the network, use [`NetworkedObject`] and [`NetworkedOperation`]. /// /// These are wrappers around the raw payloads from the network. The return payload from handling [`NetworkedOperation`] is then /// sent back to the requester. /// /// ``` /// // Start with a network payload /// let network_payload: AuthenticatedPayload = { todo!() }; /// /// let networked_object = runtime.get_object::(network_payload.object).await?; /// let operation_name = payload.operation; /// let networked_operation = NetworkedOperation(payload); /// /// // Operation state depends on the authentication in the payload, it /// // isn't relevant here. /// let operation_state = StackOperationState::default(); /// /// let result = networked_object.request(networked_operation, &operation_state); /// /// // `result` is Result, OperationError> which is also the type that /// // giterated's networked protocol uses for responses, so you can send it directly. /// ``` /// /// TODO: The above docs are 100% false about the network protocol type #[derive(Clone)] pub struct NetworkedSubstack { home_uri: Option, } impl Default for NetworkedSubstack { fn default() -> Self { Self { home_uri: None } } } impl NetworkedSubstack { pub fn into_server_substack(self) -> SubstackBuilder { let mut stack = SubstackBuilder::new(self); stack.operation(handle_network_operation::); // TODO: optional stack.dynamic_operation(try_handle_with_remote); stack } pub fn into_client_substack(self) -> SubstackBuilder { let mut stack = SubstackBuilder::new(self); // TODO: optional stack.dynamic_operation(try_handle_with_remote); stack } } pub async fn handle_network_operation( object: NetworkedObject, operation: NetworkedOperation, _state: NetworkedSubstack, OperationState(operation_state): OperationState, stack: GiteratedStack, ) -> Result, OperationError>> { trace!("Handle network operation"); let mut result = None; for (_, object_meta) in &stack.inner.metadata.objects { if let Ok(object) = (object_meta.from_str)(&object.0) { // TODO: This is definitely going to resolve us result = Some((object, object_meta)); break; } } let (object, object_meta) = result.ok_or_else(|| OperationError::Unhandled)?; trace!( "Resolved object type {} for network operation.", object_meta.name ); let operation_meta = stack .inner .metadata .operations .get(&ObjectOperationPair { object_name: &object_meta.name, operation_name: &operation.name, }) .ok_or_else(|| OperationError::Unhandled)?; trace!( "Resolved operation {}::{} for network operation.", object_meta.name, operation_meta.name ); let operation = (operation_meta.deserialize)(&operation.payload) .as_internal_error_with_context(format!( "deserializing object operation {}::{}", object_meta.name, operation_meta.name ))?; trace!( "Deserialized operation {}::{} for network operation.", object_meta.name, operation_meta.name ); let result = stack .new_operation_func(object, operation, operation_state) .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( format!( "serializing error for object operation {}::{}", object_meta.name, operation_meta.name ), )?, ), OperationError::Internal(internal) => OperationError::Internal(internal), OperationError::Unhandled => OperationError::Unhandled, }), } } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct NetworkedObject(pub String); impl FromStr for NetworkedObject { type Err = (); fn from_str(_s: &str) -> Result { todo!() } } impl Display for NetworkedObject { fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { todo!() } } impl GiteratedObject for NetworkedObject { fn object_name() -> &'static str { todo!() } fn from_object_str(_object_str: &str) -> Result { todo!() } } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct NetworkedOperation { name: String, payload: Vec, } impl NetworkedOperation { pub fn new(_name: String, _payload: Vec) -> Self { todo!() } } impl GiteratedOperation for NetworkedOperation { type Success = Vec; type Failure = Vec; fn operation_name() -> &'static str { "network_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( object: AnyObject, operation: AnyOperation, state: NetworkedSubstack, _operation_state: StackOperationState, stack: Arc, ) -> Result> { trace!( "Try handling object operation {}::{} with remote", object.kind(), operation.kind().operation_name ); // TODO: // Ideally we support pass-through on object types that aren't used locally. // For now, we aren't worrying about that. let object_meta = stack .metadata .objects .get(object.kind()) .ok_or_else(|| OperationError::Unhandled)?; let _operation_meta = stack .metadata .operations .get(&operation.kind()) .ok_or_else(|| OperationError::Unhandled)?; let object_home_uri = (object_meta.home_uri)(object.clone()); if let Some(home_uri) = state.home_uri { if home_uri == object_home_uri { // This isn't a remote request, requests aren't supposed to hit this layer // if they're not remote. warn!("Try handling object operation {}::{}, resolved object home uri as local home uri. This is a bug.", object.kind(), operation.kind().operation_name); return Err(OperationError::Unhandled); } } // Blah blah connect and do the stuff todo!() }