a3s-flow 0.4.1

Durable workflow engine and Rust SDK for A3S
Documentation
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;

use crate::error::{FlowError, Result};

use super::JsonValue;

/// HTTP route metadata for external hook callbacks.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct HookCallbackRoute {
    pub method: String,
    pub path: String,
}

impl HookCallbackRoute {
    pub fn new(method: impl Into<String>, path: impl Into<String>) -> Self {
        Self {
            method: method.into().to_ascii_uppercase(),
            path: path.into(),
        }
    }

    pub fn post(path: impl Into<String>) -> Self {
        Self::new("POST", path)
    }
}

/// Typed helper for common hook metadata fields.
///
/// Hook metadata is still persisted as JSON in `flow.hook.created` events. This
/// type only gives Rust workflow authors a stable shape for audit and callback
/// routing fields.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct HookMetadata {
    pub kind: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub subject: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub callback: Option<HookCallbackRoute>,
    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
    pub labels: BTreeMap<String, String>,
    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
    pub data: BTreeMap<String, JsonValue>,
}

impl HookMetadata {
    pub fn new(kind: impl Into<String>) -> Self {
        Self {
            kind: kind.into(),
            subject: None,
            callback: None,
            labels: BTreeMap::new(),
            data: BTreeMap::new(),
        }
    }

    pub fn human_approval(subject: impl Into<String>) -> Self {
        Self::new("human_approval").with_subject(subject)
    }

    pub fn with_subject(mut self, subject: impl Into<String>) -> Self {
        self.subject = Some(subject.into());
        self
    }

    pub fn with_callback_route(mut self, callback: HookCallbackRoute) -> Self {
        self.callback = Some(callback);
        self
    }

    pub fn with_label(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
        self.labels.insert(key.into(), value.into());
        self
    }

    pub fn with_data(mut self, key: impl Into<String>, value: impl Into<JsonValue>) -> Self {
        self.data.insert(key.into(), value.into());
        self
    }

    pub fn into_json(self) -> Result<JsonValue> {
        serde_json::to_value(self).map_err(FlowError::from)
    }
}