jellyflow_core/core/
imports.rs1use std::collections::{BTreeMap, BTreeSet};
2
3use super::{Graph, GraphId};
4
5#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
11pub struct GraphImport {
12 #[serde(default, skip_serializing_if = "Option::is_none")]
14 pub alias: Option<String>,
15}
16
17#[derive(Debug, Clone, thiserror::Error, PartialEq, Eq)]
18pub enum GraphImportError {
19 #[error("import references missing graph: from={from} to={to}")]
20 MissingGraph { from: GraphId, to: GraphId },
21
22 #[error("import cycle detected")]
23 Cycle { cycle: Vec<GraphId> },
24}
25
26#[derive(Debug, Clone)]
27pub struct GraphImportClosure {
28 pub reachable: BTreeSet<GraphId>,
30 pub order: Vec<GraphId>,
32}
33
34impl GraphImportClosure {
35 pub fn contains(&self, graph: GraphId) -> bool {
36 self.reachable.contains(&graph)
37 }
38}
39
40pub fn resolve_import_closure<'a>(
46 root_graph: &'a Graph,
47 resolver: impl FnMut(GraphId) -> Option<&'a Graph>,
48) -> Result<GraphImportClosure, GraphImportError> {
49 ImportClosureResolver::new(root_graph, resolver).finish()
50}
51
52#[derive(Debug, Clone, Copy, PartialEq, Eq)]
53enum ImportVisitMark {
54 Temporary,
55 Permanent,
56}
57
58struct ImportClosureResolver<'a, R>
59where
60 R: FnMut(GraphId) -> Option<&'a Graph>,
61{
62 root: GraphId,
63 root_graph: &'a Graph,
64 resolver: R,
65 marks: BTreeMap<GraphId, ImportVisitMark>,
66 stack: Vec<GraphId>,
67 closure: GraphImportClosure,
68}
69
70impl<'a, R> ImportClosureResolver<'a, R>
71where
72 R: FnMut(GraphId) -> Option<&'a Graph>,
73{
74 fn new(root_graph: &'a Graph, resolver: R) -> Self {
75 Self {
76 root: root_graph.graph_id,
77 root_graph,
78 resolver,
79 marks: BTreeMap::new(),
80 stack: Vec::new(),
81 closure: GraphImportClosure {
82 reachable: BTreeSet::new(),
83 order: Vec::new(),
84 },
85 }
86 }
87
88 fn finish(mut self) -> Result<GraphImportClosure, GraphImportError> {
89 self.visit(self.root, self.root)?;
90 Ok(self.closure)
91 }
92
93 fn visit(&mut self, node: GraphId, from: GraphId) -> Result<(), GraphImportError> {
94 match self.marks.get(&node).copied() {
95 Some(ImportVisitMark::Permanent) => return Ok(()),
96 Some(ImportVisitMark::Temporary) => return Err(self.cycle_error(node)),
97 None => {}
98 }
99
100 self.marks.insert(node, ImportVisitMark::Temporary);
101 self.stack.push(node);
102
103 for dep in self.dependencies(node, from)? {
104 self.closure.reachable.insert(dep);
105 self.visit(dep, node)?;
106 }
107
108 self.stack.pop();
109 self.marks.insert(node, ImportVisitMark::Permanent);
110
111 if node != self.root {
112 self.closure.order.push(node);
113 }
114
115 Ok(())
116 }
117
118 fn dependencies(
119 &mut self,
120 node: GraphId,
121 from: GraphId,
122 ) -> Result<Vec<GraphId>, GraphImportError> {
123 let imports = if node == self.root {
124 &self.root_graph.imports
125 } else {
126 let Some(graph) = (self.resolver)(node) else {
127 return Err(GraphImportError::MissingGraph { from, to: node });
128 };
129 &graph.imports
130 };
131
132 Ok(imports.keys().copied().collect())
133 }
134
135 fn cycle_error(&self, node: GraphId) -> GraphImportError {
136 if let Some(pos) = self.stack.iter().position(|id| *id == node) {
137 let mut cycle = self.stack[pos..].to_vec();
138 cycle.push(node);
139 return GraphImportError::Cycle { cycle };
140 }
141
142 GraphImportError::Cycle {
143 cycle: vec![node, node],
144 }
145 }
146}