umari 0.3.0

SDK for building event-sourced WASM components for the Umari runtime
Documentation
#![allow(clippy::too_many_arguments)]

use std::marker::PhantomData;

use schemars::JsonSchema;
use serde::de::DeserializeOwned;

pub use self::umari::command::types::*;

wit_bindgen::generate!({
    world: "command",
    path: "wit/command",
    additional_derives: [PartialEq, Clone, serde::Serialize, serde::Deserialize],
    generate_unused_types: true,
    pub_export_macro: true,
    with: {
        "umari:common/types@0.1.0": crate::runtime::common,
    },
});

pub struct CommandExport<T>(PhantomData<T>);

pub trait ExportedCommand {
    type Input: DeserializeOwned + JsonSchema;

    fn execute(
        input: Self::Input,
        context: crate::command::CommandContext,
    ) -> anyhow::Result<ExecuteOutput>;
}

impl<T> Guest for CommandExport<T>
where
    T: ExportedCommand,
{
    fn schema() -> Option<Json> {
        let schema = schemars::schema_for!(T::Input);
        Some(serde_json::to_string(&schema).unwrap_or_else(|_| panic!("invalid json schema")))
    }

    fn execute(input: Json, context: CommandContext) -> Result<ExecuteOutput, Error> {
        let input: T::Input =
            serde_json::from_str(&input).map_err(|err| Error::InvalidInput(err.to_string()))?;

        let context = crate::command::CommandContext {
            correlation_id: uuid::Uuid::parse_str(&context.correlation_id).unwrap(),
            causation_id: uuid::Uuid::parse_str(&context.causation_id).unwrap(),
            triggering_event_id: context
                .triggering_event_id
                .as_deref()
                .map(|id| uuid::Uuid::parse_str(id).unwrap()),
            idempotency_key: context
                .idempotency_key
                .as_deref()
                .map(|id| uuid::Uuid::parse_str(id).unwrap()),
        };

        T::execute(input, context).map_err(|err| Error::Rejected(err.to_string()))
    }
}

impl From<crate::command::ExecuteOutput> for ExecuteOutput {
    fn from(output: crate::command::ExecuteOutput) -> Self {
        ExecuteOutput {
            position: output.position,
            events: output
                .events
                .into_iter()
                .map(|event| EmittedEvent {
                    id: event.id.to_string(),
                    event_type: event.event_type,
                    domain_ids: event
                        .domain_ids
                        .into_iter()
                        .map(|(name, id)| DomainId { name, id })
                        .collect(),
                })
                .collect(),
        }
    }
}

impl From<crate::command::CommandContext> for CommandContext {
    fn from(ctx: crate::command::CommandContext) -> Self {
        CommandContext {
            correlation_id: ctx.correlation_id.to_string(),
            causation_id: ctx.causation_id.to_string(),
            triggering_event_id: ctx.triggering_event_id.as_ref().map(ToString::to_string),
            idempotency_key: ctx.idempotency_key.as_ref().map(ToString::to_string),
        }
    }
}