Skip to main content

a3s_flow/model/
hook.rs

1use serde::{Deserialize, Serialize};
2use std::collections::BTreeMap;
3
4use crate::error::{FlowError, Result};
5
6use super::JsonValue;
7
8/// HTTP route metadata for external hook callbacks.
9#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
10pub struct HookCallbackRoute {
11    pub method: String,
12    pub path: String,
13}
14
15impl HookCallbackRoute {
16    pub fn new(method: impl Into<String>, path: impl Into<String>) -> Self {
17        Self {
18            method: method.into().to_ascii_uppercase(),
19            path: path.into(),
20        }
21    }
22
23    pub fn post(path: impl Into<String>) -> Self {
24        Self::new("POST", path)
25    }
26}
27
28/// Typed helper for common hook metadata fields.
29///
30/// Hook metadata is still persisted as JSON in `flow.hook.created` events. This
31/// type only gives Rust workflow authors a stable shape for audit and callback
32/// routing fields.
33#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
34pub struct HookMetadata {
35    pub kind: String,
36    #[serde(skip_serializing_if = "Option::is_none")]
37    pub subject: Option<String>,
38    #[serde(skip_serializing_if = "Option::is_none")]
39    pub callback: Option<HookCallbackRoute>,
40    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
41    pub labels: BTreeMap<String, String>,
42    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
43    pub data: BTreeMap<String, JsonValue>,
44}
45
46impl HookMetadata {
47    pub fn new(kind: impl Into<String>) -> Self {
48        Self {
49            kind: kind.into(),
50            subject: None,
51            callback: None,
52            labels: BTreeMap::new(),
53            data: BTreeMap::new(),
54        }
55    }
56
57    pub fn human_approval(subject: impl Into<String>) -> Self {
58        Self::new("human_approval").with_subject(subject)
59    }
60
61    pub fn with_subject(mut self, subject: impl Into<String>) -> Self {
62        self.subject = Some(subject.into());
63        self
64    }
65
66    pub fn with_callback_route(mut self, callback: HookCallbackRoute) -> Self {
67        self.callback = Some(callback);
68        self
69    }
70
71    pub fn with_label(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
72        self.labels.insert(key.into(), value.into());
73        self
74    }
75
76    pub fn with_data(mut self, key: impl Into<String>, value: impl Into<JsonValue>) -> Self {
77        self.data.insert(key.into(), value.into());
78        self
79    }
80
81    pub fn into_json(self) -> Result<JsonValue> {
82        serde_json::to_value(self).map_err(FlowError::from)
83    }
84}