moduforge_rules_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}
101
102#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
103#[serde(rename_all = "camelCase")]
104pub struct DecisionNodeContent {
105    pub key: String,
106    #[serde(flatten)]
107    pub transform_attributes: TransformAttributes,
108}
109
110#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
111#[serde(rename_all = "camelCase")]
112pub struct DecisionTableContent {
113    pub rules: Vec<HashMap<String, String>>,
114    pub inputs: Vec<DecisionTableInputField>,
115    pub outputs: Vec<DecisionTableOutputField>,
116    pub hit_policy: DecisionTableHitPolicy,
117    #[serde(flatten)]
118    pub transform_attributes: TransformAttributes,
119}
120
121#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
122#[serde(rename_all = "camelCase")]
123pub enum DecisionTableHitPolicy {
124    First,
125    Collect,
126}
127
128#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
129#[serde(rename_all = "camelCase")]
130pub struct DecisionTableInputField {
131    pub id: String,
132    pub name: String,
133    #[serde(default, deserialize_with = "empty_string_is_none")]
134    pub field: Option<String>,
135}
136
137#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
138#[serde(rename_all = "camelCase")]
139pub struct DecisionTableOutputField {
140    pub id: String,
141    pub name: String,
142    pub field: String,
143}
144
145#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
146#[serde(rename_all = "camelCase")]
147pub struct ExpressionNodeContent {
148    pub expressions: Vec<Expression>,
149    #[serde(flatten)]
150    pub transform_attributes: TransformAttributes,
151}
152
153#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
154#[serde(rename_all = "camelCase")]
155pub struct Expression {
156    pub id: String,
157    pub key: String,
158    pub value: String,
159}
160
161#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
162#[serde(rename_all = "camelCase")]
163pub struct SwitchNodeContent {
164    #[serde(default)]
165    pub hit_policy: SwitchStatementHitPolicy,
166    pub statements: Vec<SwitchStatement>,
167}
168
169#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
170#[serde(rename_all = "camelCase")]
171pub struct SwitchStatement {
172    pub id: String,
173    pub condition: String,
174}
175
176#[derive(Clone, Debug, PartialEq, Deserialize, Serialize, Default)]
177#[serde(rename_all = "camelCase")]
178pub enum SwitchStatementHitPolicy {
179    #[default]
180    First,
181    Collect,
182}
183
184#[derive(Clone, Debug, PartialEq, Deserialize, Serialize, Default)]
185#[serde(rename_all = "camelCase")]
186pub struct TransformAttributes {
187    #[serde(default, deserialize_with = "empty_string_is_none")]
188    pub input_field: Option<String>,
189    #[serde(default, deserialize_with = "empty_string_is_none")]
190    pub output_path: Option<String>,
191    #[serde(default)]
192    pub execution_mode: TransformExecutionMode,
193    #[serde(default)]
194    pub pass_through: bool,
195}
196
197#[derive(Clone, Debug, PartialEq, Deserialize, Serialize, Default)]
198#[serde(rename_all = "camelCase")]
199pub enum TransformExecutionMode {
200    #[default]
201    Single,
202    Loop,
203}
204
205#[derive(Clone, Debug, PartialEq, Deserialize, Serialize, Default)]
206#[serde(rename_all = "camelCase")]
207pub struct CustomNodeContent {
208    pub kind: String,
209    pub config: Arc<Value>,
210}
211
212fn empty_string_is_none<'de, D>(
213    deserializer: D
214) -> Result<Option<String>, D::Error>
215where
216    D: Deserializer<'de>,
217{
218    #[derive(Deserialize)]
219    #[serde(untagged)]
220    enum StringOrNull {
221        String(String),
222        Null,
223    }
224
225    match StringOrNull::deserialize(deserializer)? {
226        StringOrNull::String(s) if s.trim().is_empty() => Ok(None),
227        StringOrNull::String(s) => Ok(Some(s)),
228        StringOrNull::Null => Ok(None),
229    }
230}