Skip to main content

zen_types/decision/
mod.rs

1use ahash::HashMap;
2use serde::{Deserialize, Deserializer, Serialize};
3use serde_json::Value;
4use std::sync::Arc;
5
6/// JDM Decision model
7#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
8#[serde(rename_all = "camelCase")]
9pub struct BaseDecisionContent {
10    pub nodes: Vec<Arc<DecisionNode>>,
11    pub edges: Vec<Arc<DecisionEdge>>,
12}
13
14#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
15#[serde(rename_all = "camelCase")]
16pub struct DecisionEdge {
17    pub id: Arc<str>,
18    pub source_id: Arc<str>,
19    pub target_id: Arc<str>,
20    pub source_handle: Option<Arc<str>>,
21}
22
23#[derive(Clone, Debug, Deserialize, Serialize)]
24#[serde(rename_all = "camelCase")]
25pub struct DecisionNode {
26    pub id: Arc<str>,
27    pub name: Arc<str>,
28    #[serde(rename = "type")]
29    #[serde(flatten)]
30    pub kind: DecisionNodeKind,
31}
32
33impl PartialEq for DecisionNode {
34    fn eq(&self, other: &Self) -> bool {
35        self.id == other.id
36    }
37}
38
39#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
40#[serde(tag = "type")]
41#[serde(rename_all = "camelCase")]
42pub enum DecisionNodeKind {
43    InputNode {
44        #[serde(default)]
45        content: InputNodeContent,
46    },
47    OutputNode {
48        #[serde(default)]
49        content: OutputNodeContent,
50    },
51    FunctionNode {
52        content: FunctionNodeContent,
53    },
54    DecisionNode {
55        content: DecisionNodeContent,
56    },
57    DecisionTableNode {
58        content: DecisionTableContent,
59    },
60    ExpressionNode {
61        content: ExpressionNodeContent,
62    },
63    SwitchNode {
64        content: SwitchNodeContent,
65    },
66    CustomNode {
67        content: CustomNodeContent,
68    },
69}
70
71#[derive(Clone, Debug, PartialEq, Deserialize, Serialize, Default)]
72#[serde(rename_all = "camelCase")]
73pub struct InputNodeContent {
74    #[serde(default, deserialize_with = "empty_value_string_is_none_safe")]
75    pub schema: Option<Arc<Value>>,
76}
77
78#[derive(Clone, Debug, PartialEq, Deserialize, Serialize, Default)]
79#[serde(rename_all = "camelCase")]
80pub struct OutputNodeContent {
81    #[serde(default, deserialize_with = "empty_value_string_is_none_safe")]
82    pub schema: Option<Arc<Value>>,
83}
84
85#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
86#[serde(rename_all = "camelCase")]
87#[serde(untagged)]
88pub enum FunctionNodeContent {
89    Version2(FunctionContent),
90    Version1(Arc<str>),
91}
92
93#[derive(Clone, Debug, PartialEq, Deserialize, Serialize, Default)]
94#[serde(rename_all = "camelCase")]
95pub struct FunctionContent {
96    pub source: Arc<str>,
97}
98
99#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
100#[serde(rename_all = "camelCase")]
101pub struct DecisionNodeContent {
102    pub key: Arc<str>,
103    #[serde(flatten)]
104    pub transform_attributes: TransformAttributes,
105}
106
107#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
108#[serde(rename_all = "camelCase")]
109pub struct DecisionTableContent {
110    #[serde(deserialize_with = "deserialize_trim_rules")]
111    pub rules: Arc<Vec<HashMap<Arc<str>, Arc<str>>>>,
112    pub inputs: Arc<Vec<DecisionTableInputField>>,
113    pub outputs: Arc<Vec<DecisionTableOutputField>>,
114    pub hit_policy: DecisionTableHitPolicy,
115    #[serde(flatten)]
116    pub transform_attributes: TransformAttributes,
117}
118
119#[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize)]
120#[serde(rename_all = "camelCase")]
121pub enum DecisionTableHitPolicy {
122    #[default]
123    First,
124    Collect,
125}
126
127#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
128#[serde(rename_all = "camelCase")]
129pub struct DecisionTableInputField {
130    pub id: Arc<str>,
131    #[serde(default = "empty_arc_str")]
132    pub name: Arc<str>,
133    #[serde(default, deserialize_with = "empty_string_is_none")]
134    pub field: Option<Arc<str>>,
135}
136
137#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
138#[serde(rename_all = "camelCase")]
139pub struct DecisionTableOutputField {
140    pub id: Arc<str>,
141    #[serde(default = "empty_arc_str")]
142    pub name: Arc<str>,
143    pub field: Arc<str>,
144    #[serde(
145        rename = "type",
146        default,
147        skip_serializing_if = "Option::is_none",
148        deserialize_with = "empty_string_is_none"
149    )]
150    pub column_type: Option<Arc<str>>,
151}
152
153impl DecisionTableOutputField {
154    pub fn write_path(&self) -> (&str, bool) {
155        let field = self.field.trim();
156        match field.strip_suffix("[]") {
157            Some(base) => (base.trim_end(), true),
158            None => (field, false),
159        }
160    }
161}
162
163fn empty_arc_str() -> Arc<str> {
164    Arc::from("")
165}
166
167#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
168#[serde(rename_all = "camelCase")]
169pub struct ExpressionNodeContent {
170    pub expressions: Arc<Vec<Expression>>,
171    #[serde(flatten)]
172    pub transform_attributes: TransformAttributes,
173}
174
175#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
176#[serde(rename_all = "camelCase")]
177pub struct Expression {
178    pub id: Arc<str>,
179    pub key: Arc<str>,
180    pub value: Arc<str>,
181}
182
183#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
184#[serde(rename_all = "camelCase")]
185pub struct SwitchNodeContent {
186    #[serde(default)]
187    pub hit_policy: SwitchStatementHitPolicy,
188    pub statements: Arc<Vec<SwitchStatement>>,
189}
190
191#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
192#[serde(rename_all = "camelCase")]
193pub struct SwitchStatement {
194    pub id: Arc<str>,
195    pub condition: Arc<str>,
196}
197
198#[derive(Clone, Debug, PartialEq, Deserialize, Serialize, Default)]
199#[serde(rename_all = "camelCase")]
200pub enum SwitchStatementHitPolicy {
201    #[default]
202    First,
203    Collect,
204}
205
206#[derive(Clone, Debug, PartialEq, Deserialize, Serialize, Default)]
207#[serde(rename_all = "camelCase")]
208pub struct TransformAttributes {
209    #[serde(default, deserialize_with = "empty_string_is_none")]
210    pub input_field: Option<Arc<str>>,
211    #[serde(default, deserialize_with = "empty_string_is_none")]
212    pub output_path: Option<Arc<str>>,
213    #[serde(default)]
214    pub execution_mode: TransformExecutionMode,
215    #[serde(default)]
216    pub pass_through: bool,
217}
218
219#[derive(Clone, Debug, PartialEq, Deserialize, Serialize, Default)]
220#[serde(rename_all = "camelCase")]
221pub enum TransformExecutionMode {
222    #[default]
223    Single,
224    Loop,
225}
226
227#[derive(Clone, Debug, PartialEq, Deserialize, Serialize, Default)]
228#[serde(rename_all = "camelCase")]
229pub struct CustomNodeContent {
230    pub kind: Arc<str>,
231    pub config: Arc<Value>,
232}
233
234fn empty_string_is_none<'de, D>(deserializer: D) -> Result<Option<Arc<str>>, D::Error>
235where
236    D: Deserializer<'de>,
237{
238    #[derive(Deserialize)]
239    #[serde(untagged)]
240    enum StringOrNull {
241        String(Arc<str>),
242        Null,
243    }
244
245    match StringOrNull::deserialize(deserializer)? {
246        StringOrNull::String(s) if s.trim().is_empty() => Ok(None),
247        StringOrNull::String(s) => Ok(Some(s)),
248        StringOrNull::Null => Ok(None),
249    }
250}
251
252fn empty_value_string_is_none_safe<'de, D>(deserializer: D) -> Result<Option<Arc<Value>>, D::Error>
253where
254    D: Deserializer<'de>,
255{
256    let s = empty_string_is_none(deserializer)?;
257    let Some(data) = s else {
258        return Ok(None);
259    };
260
261    Ok(serde_json::from_str(data.as_ref()).ok())
262}
263
264fn deserialize_trim_rules<'de, D>(
265    deserializer: D,
266) -> Result<Arc<Vec<HashMap<Arc<str>, Arc<str>>>>, D::Error>
267where
268    D: Deserializer<'de>,
269{
270    let rules: Vec<HashMap<Arc<str>, Arc<str>>> = Vec::deserialize(deserializer)?;
271
272    let filtered_rules: Vec<HashMap<Arc<str>, Arc<str>>> = rules
273        .into_iter()
274        .map(|rule| {
275            rule.into_iter()
276                .map(|(k, v)| (k, Arc::from(v.trim())))
277                .collect()
278        })
279        .collect();
280
281    Ok(Arc::new(filtered_rules))
282}