mf_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(
35        &self,
36        other: &Self,
37    ) -> bool {
38        self.id == other.id
39    }
40}
41
42#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
43#[serde(tag = "type")]
44#[serde(rename_all = "camelCase")]
45pub enum DecisionNodeKind {
46    InputNode {
47        #[serde(default)]
48        content: InputNodeContent,
49    },
50    OutputNode {
51        #[serde(default)]
52        content: OutputNodeContent,
53    },
54    FunctionNode {
55        content: FunctionNodeContent,
56    },
57    DecisionNode {
58        content: DecisionNodeContent,
59    },
60    DecisionTableNode {
61        content: DecisionTableContent,
62    },
63    ExpressionNode {
64        content: ExpressionNodeContent,
65    },
66    SwitchNode {
67        content: SwitchNodeContent,
68    },
69    CustomNode {
70        content: CustomNodeContent,
71    },
72}
73
74#[derive(Clone, Debug, PartialEq, Deserialize, Serialize, Default)]
75#[serde(rename_all = "camelCase")]
76pub struct InputNodeContent {
77    #[serde(default, deserialize_with = "empty_string_is_none")]
78    pub schema: Option<String>,
79}
80
81#[derive(Clone, Debug, PartialEq, Deserialize, Serialize, Default)]
82#[serde(rename_all = "camelCase")]
83pub struct OutputNodeContent {
84    #[serde(default, deserialize_with = "empty_string_is_none")]
85    pub schema: Option<String>,
86}
87
88#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
89#[serde(rename_all = "camelCase")]
90#[serde(untagged)]
91pub enum FunctionNodeContent {
92    Version2(FunctionContent),
93    Version1(String),
94}
95
96#[derive(Clone, Debug, PartialEq, Deserialize, Serialize, Default)]
97#[serde(rename_all = "camelCase")]
98pub struct FunctionContent {
99    pub source: String,
100    #[serde(default)]
101    pub omit_nodes: bool,
102}
103
104#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
105#[serde(rename_all = "camelCase")]
106pub struct DecisionNodeContent {
107    pub key: String,
108    #[serde(flatten)]
109    pub transform_attributes: TransformAttributes,
110}
111
112#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
113#[serde(rename_all = "camelCase")]
114pub struct DecisionTableContent {
115    pub rules: Vec<HashMap<String, String>>,
116    pub inputs: Vec<DecisionTableInputField>,
117    pub outputs: Vec<DecisionTableOutputField>,
118    pub hit_policy: DecisionTableHitPolicy,
119    #[serde(flatten)]
120    pub transform_attributes: TransformAttributes,
121}
122
123#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
124#[serde(rename_all = "camelCase")]
125pub enum DecisionTableHitPolicy {
126    First,
127    Collect,
128}
129
130#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
131#[serde(rename_all = "camelCase")]
132pub struct DecisionTableInputField {
133    pub id: String,
134    pub name: String,
135    #[serde(default, deserialize_with = "empty_string_is_none")]
136    pub field: Option<String>,
137}
138
139#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
140#[serde(rename_all = "camelCase")]
141pub struct DecisionTableOutputField {
142    pub id: String,
143    pub name: String,
144    pub field: String,
145}
146
147#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
148#[serde(rename_all = "camelCase")]
149pub struct ExpressionNodeContent {
150    pub expressions: Vec<Expression>,
151    #[serde(flatten)]
152    pub transform_attributes: TransformAttributes,
153}
154
155#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
156#[serde(rename_all = "camelCase")]
157pub struct Expression {
158    pub id: String,
159    pub key: String,
160    pub value: String,
161}
162
163#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
164#[serde(rename_all = "camelCase")]
165pub struct SwitchNodeContent {
166    #[serde(default)]
167    pub hit_policy: SwitchStatementHitPolicy,
168    pub statements: Vec<SwitchStatement>,
169}
170
171#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
172#[serde(rename_all = "camelCase")]
173pub struct SwitchStatement {
174    pub id: String,
175    pub condition: String,
176}
177
178#[derive(Clone, Debug, PartialEq, Deserialize, Serialize, Default)]
179#[serde(rename_all = "camelCase")]
180pub enum SwitchStatementHitPolicy {
181    #[default]
182    First,
183    Collect,
184}
185
186#[derive(Clone, Debug, PartialEq, Deserialize, Serialize, Default)]
187#[serde(rename_all = "camelCase")]
188pub struct TransformAttributes {
189    #[serde(default, deserialize_with = "empty_string_is_none")]
190    pub input_field: Option<String>,
191    #[serde(default, deserialize_with = "empty_string_is_none")]
192    pub output_path: Option<String>,
193    #[serde(default)]
194    pub execution_mode: TransformExecutionMode,
195    #[serde(default)]
196    pub pass_through: bool,
197}
198
199#[derive(Clone, Debug, PartialEq, Deserialize, Serialize, Default)]
200#[serde(rename_all = "camelCase")]
201pub enum TransformExecutionMode {
202    #[default]
203    Single,
204    Loop,
205}
206
207#[derive(Clone, Debug, PartialEq, Deserialize, Serialize, Default)]
208#[serde(rename_all = "camelCase")]
209pub struct CustomNodeContent {
210    pub kind: String,
211    pub config: Arc<Value>,
212}
213
214fn empty_string_is_none<'de, D>(
215    deserializer: D
216) -> Result<Option<String>, D::Error>
217where
218    D: Deserializer<'de>,
219{
220    #[derive(Deserialize)]
221    #[serde(untagged)]
222    enum StringOrNull {
223        String(String),
224        Null,
225    }
226
227    match StringOrNull::deserialize(deserializer)? {
228        StringOrNull::String(s) if s.trim().is_empty() => Ok(None),
229        StringOrNull::String(s) => Ok(Some(s)),
230        StringOrNull::Null => Ok(None),
231    }
232}