mocra_core/common/model/
module_dag.rs1use std::collections::HashMap;
9use std::sync::Arc;
10
11use indexmap::IndexMap;
12use mocra_dag::{DagNodeExecutionPolicy, NodePlacement};
13
14use crate::common::interface::ModuleNodeTrait;
15
16#[derive(Clone)]
17pub struct ModuleDagNodeDef {
18 pub node_id: String,
19 pub node: Arc<dyn ModuleNodeTrait>,
20 pub placement_override: Option<NodePlacement>,
21 pub policy_override: Option<DagNodeExecutionPolicy>,
22 pub tags: Vec<String>,
23}
24impl ModuleDagNodeDef {
25 pub fn new(node: Arc<dyn ModuleNodeTrait>) -> Self {
26 let key = node.stable_node_key();
27 let node_id = if key.is_empty() {
28 uuid::Uuid::now_v7().to_string()
29 } else {
30 key.to_string()
31 };
32 Self {
33 node_id,
34 node,
35 placement_override: None,
36 policy_override: None,
37 tags: Vec::new(),
38 }
39 }
40
41 pub fn with_id(mut self, id: impl Into<String>) -> Self {
47 self.node_id = id.into();
48 self
49 }
50}
51
52#[derive(Debug, Clone)]
53pub struct ModuleDagEdgeDef {
54 pub from: String,
55 pub to: String,
56}
57impl ModuleDagEdgeDef {
58 pub fn new(from: &ModuleDagNodeDef, to: &ModuleDagNodeDef) -> Self {
59 Self {
60 from: from.node_id.clone(),
61 to: to.node_id.clone(),
62 }
63 }
64}
65
66#[derive(Clone, Default)]
67pub struct ModuleDagDefinition {
68 pub nodes: Vec<ModuleDagNodeDef>,
69 pub edges: Vec<ModuleDagEdgeDef>,
70 pub entry_nodes: Vec<String>,
71 pub default_policy: Option<DagNodeExecutionPolicy>,
72 pub metadata: HashMap<String, String>,
73}
74
75impl ModuleDagDefinition {
76 pub fn builder() -> ModuleDagBuilder {
93 ModuleDagBuilder::new()
94 }
95
96 pub fn from_linear_steps(steps: Vec<Arc<dyn ModuleNodeTrait>>) -> Self {
97 let mut nodes = Vec::with_capacity(steps.len());
98 let mut edges = Vec::with_capacity(steps.len().saturating_sub(1));
99
100 for step in steps {
101 nodes.push(ModuleDagNodeDef::new(step));
102 }
103 for idx in 1..nodes.len() {
104 edges.push(ModuleDagEdgeDef::new(&nodes[idx - 1], &nodes[idx]));
105 }
106
107 let entry_nodes = nodes
108 .first()
109 .map(|n| vec![n.node_id.clone()])
110 .unwrap_or_default();
111
112 Self {
113 nodes,
114 edges,
115 entry_nodes,
116 default_policy: None,
117 metadata: HashMap::new(),
118 }
119 }
120}
121
122pub struct ModuleDagBuilder {
130 nodes: IndexMap<String, ModuleDagNodeDef>,
132 edges: Vec<ModuleDagEdgeDef>,
133 default_policy: Option<DagNodeExecutionPolicy>,
134}
135
136impl ModuleDagBuilder {
137 pub fn new() -> Self {
138 Self {
139 nodes: IndexMap::new(),
140 edges: Vec::new(),
141 default_policy: None,
142 }
143 }
144
145 pub fn edge(mut self, from: &ModuleDagNodeDef, to: &ModuleDagNodeDef) -> Self {
151 self.nodes
152 .entry(from.node_id.clone())
153 .or_insert_with(|| from.clone());
154 self.nodes
155 .entry(to.node_id.clone())
156 .or_insert_with(|| to.clone());
157 self.edges.push(ModuleDagEdgeDef {
158 from: from.node_id.clone(),
159 to: to.node_id.clone(),
160 });
161 self
162 }
163
164 pub fn node(mut self, node: &ModuleDagNodeDef) -> Self {
166 self.nodes
167 .entry(node.node_id.clone())
168 .or_insert_with(|| node.clone());
169 self
170 }
171
172 pub fn default_policy(mut self, policy: DagNodeExecutionPolicy) -> Self {
174 self.default_policy = Some(policy);
175 self
176 }
177
178 pub fn build(self) -> ModuleDagDefinition {
182 let to_set: std::collections::HashSet<&str> =
183 self.edges.iter().map(|e| e.to.as_str()).collect();
184
185 let entry_nodes: Vec<String> = self
186 .nodes
187 .keys()
188 .filter(|id| !to_set.contains(id.as_str()))
189 .cloned()
190 .collect();
191
192 ModuleDagDefinition {
193 nodes: self.nodes.into_values().collect(),
194 edges: self.edges,
195 entry_nodes,
196 default_policy: self.default_policy,
197 metadata: HashMap::new(),
198 }
199 }
200}
201
202impl Default for ModuleDagBuilder {
203 fn default() -> Self {
204 Self::new()
205 }
206}
207
208#[cfg(test)]
209mod tests {
210 use std::sync::Arc;
211
212 use async_trait::async_trait;
213 use serde_json::Map;
214
215 use super::ModuleDagDefinition;
216 use crate::common::interface::{ModuleNodeTrait, SyncBoxStream, ToSyncBoxStream};
217 use crate::common::model::login_info::LoginInfo;
218 use crate::common::model::message::TaskOutputEvent;
219 use crate::common::model::{ModuleConfig, Request, Response};
220 use crate::errors::Result;
221
222 struct DummyNode;
223
224 #[async_trait]
225 impl ModuleNodeTrait for DummyNode {
226 async fn generate(
227 &self,
228 _config: Arc<ModuleConfig>,
229 _params: Map<String, serde_json::Value>,
230 _login_info: Option<LoginInfo>,
231 ) -> Result<SyncBoxStream<'static, Request>> {
232 Ok(Vec::<Request>::new().to_stream())
233 }
234
235 async fn parser(
236 &self,
237 _response: Response,
238 _config: Option<Arc<ModuleConfig>>,
239 ) -> Result<TaskOutputEvent> {
240 Ok(TaskOutputEvent::default())
241 }
242 }
243
244 fn dummy_node() -> Arc<dyn ModuleNodeTrait> {
245 Arc::new(DummyNode)
246 }
247
248 #[test]
249 fn from_linear_steps_builds_expected_shape() {
250 let steps = vec![dummy_node(), dummy_node(), dummy_node()];
251 let definition = ModuleDagDefinition::from_linear_steps(steps);
252
253 assert_eq!(definition.nodes.len(), 3);
254 assert_eq!(definition.edges.len(), 2);
255 assert_eq!(
256 definition.entry_nodes,
257 vec![definition.nodes[0].node_id.clone()]
258 );
259 assert_eq!(definition.edges[0].from, definition.nodes[0].node_id);
260 assert_eq!(definition.edges[0].to, definition.nodes[1].node_id);
261 assert_eq!(definition.edges[1].from, definition.nodes[1].node_id);
262 assert_eq!(definition.edges[1].to, definition.nodes[2].node_id);
263 }
264}