From 9f6032e2e1a28863923f7a855e1eb2a6e12e6347 Mon Sep 17 00:00:00 2001 From: Timur Gordon <31495328+timurgordon@users.noreply.github.com> Date: Thu, 20 Nov 2025 08:52:11 +0100 Subject: [PATCH] add zdfz models and macros for auto client generation --- lib/clients/osiris/Cargo.toml | 6 + lib/clients/osiris/src/lib.rs | 8 + lib/clients/osiris/src/macros.rs | 204 ++++++++++++++++++++++ lib/clients/osiris/src/zdfz_extensions.rs | 24 +++ 4 files changed, 242 insertions(+) create mode 100644 lib/clients/osiris/src/macros.rs create mode 100644 lib/clients/osiris/src/zdfz_extensions.rs diff --git a/lib/clients/osiris/Cargo.toml b/lib/clients/osiris/Cargo.toml index be3c69f..ced6351 100644 --- a/lib/clients/osiris/Cargo.toml +++ b/lib/clients/osiris/Cargo.toml @@ -5,13 +5,19 @@ edition.workspace = true description = "Osiris client library" license = "MIT OR Apache-2.0" +[features] +default = [] +zdfz = ["dep:zdfz-models"] + [dependencies] +zdfz-models = { path = "../../../../../zdfz/sdk/models", optional = true } # Core dependencies serde.workspace = true serde_json.workspace = true anyhow.workspace = true thiserror.workspace = true chrono.workspace = true +paste = "1.0" # HTTP client reqwest = { version = "0.12", default-features = false, features = ["json"] } diff --git a/lib/clients/osiris/src/lib.rs b/lib/clients/osiris/src/lib.rs index 4051b8f..d982833 100644 --- a/lib/clients/osiris/src/lib.rs +++ b/lib/clients/osiris/src/lib.rs @@ -13,10 +13,15 @@ use thiserror::Error; pub mod kyc; pub mod payment; pub mod communication; +pub mod macros; + +#[cfg(feature = "zdfz")] +pub mod zdfz_extensions; pub use kyc::*; pub use payment::*; pub use communication::*; +pub use macros::*; #[derive(Debug, Error)] pub enum OsirisClientError { @@ -34,6 +39,9 @@ pub enum OsirisClientError { #[error("Command execution failed: {0}")] CommandFailed(String), + + #[error("Serialization failed: {0}")] + SerializationFailed(String), } /// Osiris client with CQRS support diff --git a/lib/clients/osiris/src/macros.rs b/lib/clients/osiris/src/macros.rs new file mode 100644 index 0000000..3a4588b --- /dev/null +++ b/lib/clients/osiris/src/macros.rs @@ -0,0 +1,204 @@ +//! Macros for generating CRUD methods on OsirisClient +//! +//! These macros allow you to quickly generate standard CRUD operations +//! and custom methods for your models. + +/// Generate CRUD methods for a model on OsirisClient +/// +/// This macro generates 5 standard methods: +/// - create_{collection} +/// - get_{collection} +/// - update_{collection} +/// - delete_{collection} +/// - list_{collection} +/// +/// # Example +/// +/// ```rust +/// use osiris_client::{OsirisClient, impl_osiris_crud}; +/// +/// #[derive(serde::Serialize, serde::Deserialize)] +/// struct User { +/// id: String, +/// name: String, +/// } +/// +/// impl_osiris_crud!(User, "users", "id"); +/// +/// // Now you can use: +/// // client.create_users(&user).await?; +/// // client.get_users("123").await?; +/// // client.update_users("123", &user).await?; +/// // client.delete_users("123").await?; +/// // client.list_users().await?; +/// ``` +#[macro_export] +macro_rules! impl_osiris_crud { + ($model:ty, $collection:expr, $id_field:expr) => { + paste::paste! { + impl $crate::OsirisClient { + /// Create a new instance + #[doc = "Create a new " $collection " instance"] + pub async fn [<$collection:snake _create>](&self, model: &$model) -> Result<$model, $crate::OsirisClientError> { + let json = serde_json::to_string(model) + .map_err(|e| $crate::OsirisClientError::SerializationFailed(e.to_string()))?; + + // Create Rhai script that uses Osiris context API + // Note: The actual object creation depends on the model type + // For now, we serialize the data and would need model-specific constructors + let script = format!( + r#" + let ctx = get_context(["system"]); + let data = {}; + // TODO: Model-specific object creation + // For now, this is a placeholder + data + "#, + json + ); + + let response = self.execute_script(&script).await?; + // TODO: Parse response from job result + Err($crate::OsirisClientError::CommandFailed("Not yet implemented".to_string())) + } + + /// Get an instance by ID + #[doc = "Get a " $collection " instance by ID"] + pub async fn [<$collection:snake _get>](&self, id: &str) -> Result<$model, $crate::OsirisClientError> { + let script = format!( + r#" + let ctx = get_context(["system"]); + ctx.get("{}", "{}") + "#, + $collection, id + ); + + let response = self.execute_script(&script).await?; + // TODO: Parse response from job result + Err($crate::OsirisClientError::CommandFailed("Not yet implemented".to_string())) + } + + /// Update an existing instance + #[doc = "Update an existing " $collection " instance"] + pub async fn [<$collection:snake _update>](&self, id: &str, model: &$model) -> Result<$model, $crate::OsirisClientError> { + let json = serde_json::to_string(model) + .map_err(|e| $crate::OsirisClientError::SerializationFailed(e.to_string()))?; + + let script = format!( + r#" + let ctx = get_context(["system"]); + let obj = ctx.get("{}", "{}"); + let data = {}; + // TODO: Update object fields from data + ctx.save(obj); + obj + "#, + $collection, id, json + ); + + let response = self.execute_script(&script).await?; + // TODO: Parse response from job result + Err($crate::OsirisClientError::CommandFailed("Not yet implemented".to_string())) + } + + /// Delete an instance + #[doc = "Delete a " $collection " instance"] + pub async fn [<$collection:snake _delete>](&self, id: &str) -> Result<(), $crate::OsirisClientError> { + let script = format!( + r#" + let ctx = get_context(["system"]); + ctx.delete("{}", "{}") + "#, + $collection, id + ); + + self.execute_script(&script).await?; + Ok(()) + } + + /// List all instances + #[doc = "List all " $collection " instances"] + pub async fn [<$collection:snake _list>](&self) -> Result, $crate::OsirisClientError> { + let script = format!( + r#" + let ctx = get_context(["system"]); + ctx.list("{}") + "#, + $collection + ); + + let response = self.execute_script(&script).await?; + // TODO: Parse response from job result + Err($crate::OsirisClientError::CommandFailed("Not yet implemented".to_string())) + } + } + } + }; +} + +/// Generate a custom method on a model +/// +/// This macro generates a method that calls a custom Rhai function on the model. +/// +/// # Example +/// +/// ```rust +/// use osiris_client::{OsirisClient, impl_osiris_method}; +/// +/// #[derive(serde::Serialize, serde::Deserialize)] +/// struct CalendarEvent { +/// id: String, +/// start_time: i64, +/// } +/// +/// impl_osiris_method!(CalendarEvent, "calendar_events", reschedule, new_start: i64, new_end: i64); +/// +/// // Now you can use: +/// // client.reschedule_calendar_events("123", 1234567890, 1234567900).await?; +/// ``` +#[macro_export] +macro_rules! impl_osiris_method { + ($model:ty, $collection:expr, $method_name:ident $(, $param:ident: $param_type:ty)*) => { + paste::paste! { + impl $crate::OsirisClient { + #[doc = "Call " $method_name " on a " $collection " instance"] + pub async fn [<$collection:snake _ $method_name>](&self, id: &str $(, $param: $param_type)*) -> Result<$model, $crate::OsirisClientError> { + let params = serde_json::json!({ + $(stringify!($param): $param),* + }); + + let script = format!( + r#" + let ctx = get_context(["system"]); + let obj = ctx.get("{}", "{}"); + // TODO: Call custom method on object + // obj.{}({}); + ctx.save(obj); + obj + "#, + $collection, id, stringify!($method_name), params + ); + + let response = self.execute_script(&script).await?; + // TODO: Parse response from job result + Err($crate::OsirisClientError::CommandFailed("Not yet implemented".to_string())) + } + } + } + }; +} + +#[cfg(test)] +mod tests { + use super::*; + + // Example model for testing + #[derive(serde::Serialize, serde::Deserialize)] + struct TestModel { + id: String, + name: String, + } + + // This would generate the methods (can't actually test async in doc tests easily) + // impl_osiris_crud!(TestModel, "test_models", "id"); +} diff --git a/lib/clients/osiris/src/zdfz_extensions.rs b/lib/clients/osiris/src/zdfz_extensions.rs new file mode 100644 index 0000000..72daa77 --- /dev/null +++ b/lib/clients/osiris/src/zdfz_extensions.rs @@ -0,0 +1,24 @@ +//! ZDFZ model extensions for OsirisClient +//! +//! This module generates CRUD and custom methods for ZDFZ models. +//! It must be in the osiris-client crate to satisfy Rust's orphan rules. + +use crate::{impl_osiris_crud, impl_osiris_method}; + +// Import ZDFZ models - these will be available when zdfz-models is a dependency +#[cfg(feature = "zdfz")] +use zdfz_models::*; + +// ========== Core Business Models ========== + +// Digital Residents - Individual users of the freezone +#[cfg(feature = "zdfz")] +impl_osiris_crud!(ApiDigitalResident, "digital_residents", "resident_id"); + +// Free Zone Companies - Companies registered in the freezone +#[cfg(feature = "zdfz")] +impl_osiris_crud!(FreeZoneCompany, "free_zone_companies", "fzc_id"); + +// Invoices - Financial documents for companies +#[cfg(feature = "zdfz")] +impl_osiris_crud!(FreeZoneInvoice, "invoices", "fz_invoice_id");