zen_engine/model/
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 DecisionContent {
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: String,
18    pub source_id: String,
19    pub target_id: String,
20    pub source_handle: Option<String>,
21}
22
23#[derive(Clone, Debug, Deserialize, Serialize)]
24#[serde(rename_all = "camelCase")]
25pub struct DecisionNode {
26    pub id: String,
27    pub name: String,
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_string_is_none")]
75    pub schema: Option<String>,
76}
77
78#[derive(Clone, Debug, PartialEq, Deserialize, Serialize, Default)]
79#[serde(rename_all = "camelCase")]
80pub struct OutputNodeContent {
81    #[serde(default, deserialize_with = "empty_string_is_none")]
82    pub schema: Option<String>,
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(String),
91}
92
93#[derive(Clone, Debug, PartialEq, Deserialize, Serialize, Default)]
94#[serde(rename_all = "camelCase")]
95pub struct FunctionContent {
96    pub source: String,
97}
98
99#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
100#[serde(rename_all = "camelCase")]
101pub struct DecisionNodeContent {
102    pub key: String,
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    pub rules: Vec<HashMap<String, String>>,
111    pub inputs: Vec<DecisionTableInputField>,
112    pub outputs: Vec<DecisionTableOutputField>,
113    pub hit_policy: DecisionTableHitPolicy,
114    #[serde(flatten)]
115    pub transform_attributes: TransformAttributes,
116}
117
118#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
119#[serde(rename_all = "camelCase")]
120pub enum DecisionTableHitPolicy {
121    First,
122    Collect,
123}
124
125#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
126#[serde(rename_all = "camelCase")]
127pub struct DecisionTableInputField {
128    pub id: String,
129    pub name: String,
130    #[serde(default, deserialize_with = "empty_string_is_none")]
131    pub field: Option<String>,
132}
133
134#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
135#[serde(rename_all = "camelCase")]
136pub struct DecisionTableOutputField {
137    pub id: String,
138    pub name: String,
139    pub field: String,
140}
141
142#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
143#[serde(rename_all = "camelCase")]
144pub struct ExpressionNodeContent {
145    pub expressions: Vec<Expression>,
146    #[serde(flatten)]
147    pub transform_attributes: TransformAttributes,
148}
149
150#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
151#[serde(rename_all = "camelCase")]
152pub struct Expression {
153    pub id: String,
154    pub key: String,
155    pub value: String,
156}
157
158#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
159#[serde(rename_all = "camelCase")]
160pub struct SwitchNodeContent {
161    #[serde(default)]
162    pub hit_policy: SwitchStatementHitPolicy,
163    pub statements: Vec<SwitchStatement>,
164}
165
166#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
167#[serde(rename_all = "camelCase")]
168pub struct SwitchStatement {
169    pub id: String,
170    pub condition: String,
171}
172
173#[derive(Clone, Debug, PartialEq, Deserialize, Serialize, Default)]
174#[serde(rename_all = "camelCase")]
175pub enum SwitchStatementHitPolicy {
176    #[default]
177    First,
178    Collect,
179}
180
181#[derive(Clone, Debug, PartialEq, Deserialize, Serialize, Default)]
182#[serde(rename_all = "camelCase")]
183pub struct TransformAttributes {
184    #[serde(default, deserialize_with = "empty_string_is_none")]
185    pub input_field: Option<String>,
186    #[serde(default, deserialize_with = "empty_string_is_none")]
187    pub output_path: Option<String>,
188    #[serde(default)]
189    pub execution_mode: TransformExecutionMode,
190    #[serde(default)]
191    pub pass_through: bool,
192}
193
194#[derive(Clone, Debug, PartialEq, Deserialize, Serialize, Default)]
195#[serde(rename_all = "camelCase")]
196pub enum TransformExecutionMode {
197    #[default]
198    Single,
199    Loop,
200}
201
202#[derive(Clone, Debug, PartialEq, Deserialize, Serialize, Default)]
203#[serde(rename_all = "camelCase")]
204pub struct CustomNodeContent {
205    pub kind: String,
206    pub config: Arc<Value>,
207}
208
209fn empty_string_is_none<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
210where
211    D: Deserializer<'de>,
212{
213    #[derive(Deserialize)]
214    #[serde(untagged)]
215    enum StringOrNull {
216        String(String),
217        Null,
218    }
219
220    match StringOrNull::deserialize(deserializer)? {
221        StringOrNull::String(s) if s.trim().is_empty() => Ok(None),
222        StringOrNull::String(s) => Ok(Some(s)),
223        StringOrNull::Null => Ok(None),
224    }
225}