1use crate::{
2 AuditState, DependencyList, DerivedNode, GraphError, GraphResult, InputNode, NodeHandle,
3 NodeId, NodeKind, NodeMeta, OutputKey, OutputMeta, Revision, ScopeId, ScopeMeta, Transaction,
4 TransactionId, TransactionOptions,
5 collection::{CollectionSpec, StoredCollection, StoredDiff},
6 derive::DerivedSpec,
7 input::{StoredInput, value_type},
8 output::OutputSpec,
9 resource::{ResourceKey, ResourcePlanner},
10};
11use std::collections::{BTreeMap, BTreeSet};
12
13pub struct Graph<C = (), O = ()> {
15 pub(crate) next_node_id: u64,
16 pub(crate) next_scope_id: u64,
17 pub(crate) next_output_key: u64,
18 pub(crate) next_transaction_id: TransactionId,
19 pub(crate) revision: Revision,
20 pub(crate) nodes: BTreeMap<NodeId, NodeMeta>,
21 pub(crate) scopes: BTreeMap<ScopeId, ScopeMeta>,
22 pub(crate) input_values: BTreeMap<NodeId, Box<dyn StoredInput>>,
23 pub(crate) derived_specs: BTreeMap<NodeId, DerivedSpec<C, O>>,
24 pub(crate) derived_values: BTreeMap<NodeId, Box<dyn StoredInput>>,
25 pub(crate) collection_specs: BTreeMap<NodeId, CollectionSpec<C, O>>,
26 pub(crate) collection_values: BTreeMap<NodeId, Box<dyn StoredCollection>>,
27 pub(crate) previous_collection_values: BTreeMap<NodeId, Box<dyn StoredCollection>>,
28 pub(crate) collection_diffs: BTreeMap<NodeId, Box<dyn StoredDiff>>,
29 pub(crate) resource_planners: Vec<ResourcePlanner<C, O>>,
30 pub(crate) resource_owners: BTreeMap<ResourceKey, BTreeSet<ScopeId>>,
31 pub(crate) output_specs: BTreeMap<OutputKey, OutputSpec<C, O>>,
32 pub(crate) output_values: BTreeMap<OutputKey, O>,
33 pub(crate) outputs: BTreeMap<OutputKey, OutputMeta>,
34 pub(crate) audit: AuditState,
35 pub(crate) transaction_open: bool,
36}
37
38impl<C, O> Graph<C, O> {
39 pub fn new_with_command_type() -> Self {
41 Self {
42 next_node_id: 1,
43 next_scope_id: 1,
44 next_output_key: 1,
45 next_transaction_id: TransactionId::default(),
46 revision: Revision::default(),
47 nodes: BTreeMap::new(),
48 scopes: BTreeMap::new(),
49 input_values: BTreeMap::new(),
50 derived_specs: BTreeMap::new(),
51 derived_values: BTreeMap::new(),
52 collection_specs: BTreeMap::new(),
53 collection_values: BTreeMap::new(),
54 previous_collection_values: BTreeMap::new(),
55 collection_diffs: BTreeMap::new(),
56 resource_planners: Vec::new(),
57 resource_owners: BTreeMap::new(),
58 output_specs: BTreeMap::new(),
59 output_values: BTreeMap::new(),
60 outputs: BTreeMap::new(),
61 audit: AuditState::default(),
62 transaction_open: false,
63 }
64 }
65
66 pub fn revision(&self) -> Revision {
68 self.revision
69 }
70
71 pub fn begin_transaction(&mut self) -> GraphResult<Transaction<'_, C, O>>
73 where
74 O: Clone + PartialEq,
75 {
76 self.begin_transaction_with_options(TransactionOptions::default())
77 }
78
79 pub fn begin_transaction_with_options(
81 &mut self,
82 options: TransactionOptions,
83 ) -> GraphResult<Transaction<'_, C, O>>
84 where
85 O: Clone + PartialEq,
86 {
87 if self.transaction_open {
88 return Err(GraphError::NestedTransaction);
89 }
90
91 self.transaction_open = true;
92 let id = self.allocate_transaction_id();
93 Ok(Transaction::new(self, id, options))
94 }
95
96 pub(crate) fn create_scope_with_parent_direct(
97 &mut self,
98 id: ScopeId,
99 debug_name: impl Into<String>,
100 parent: Option<ScopeId>,
101 ) -> GraphResult<ScopeId> {
102 if let Some(parent) = parent {
103 let parent_meta = self.require_scope(parent)?;
104 if parent_meta.is_closed() {
105 return Err(GraphError::ScopeAlreadyClosed(parent));
106 }
107 }
108
109 self.scopes
110 .insert(id, ScopeMeta::new(id, debug_name, parent));
111 Ok(id)
112 }
113
114 pub(crate) fn input_direct<T>(
115 &mut self,
116 id: NodeId,
117 debug_name: impl Into<String>,
118 ) -> GraphResult<InputNode<T>>
119 where
120 T: Clone + PartialEq + 'static,
121 {
122 let meta = NodeMeta::new(
123 id,
124 NodeKind::Input,
125 debug_name,
126 DependencyList::empty(),
127 self.revision,
128 Some(value_type::<T>()),
129 );
130 self.nodes.insert(id, meta);
131 Ok(InputNode::new(id))
132 }
133
134 pub(crate) fn derived_direct<T>(
135 &mut self,
136 id: NodeId,
137 debug_name: impl Into<String>,
138 dependencies: DependencyList,
139 derive: impl for<'ctx> Fn(&crate::DeriveContext<'ctx, C, O>) -> Result<T, crate::DeriveError>
140 + 'static,
141 ) -> GraphResult<DerivedNode<T>>
142 where
143 T: Clone + PartialEq + 'static,
144 {
145 self.validate_dependencies(id, &dependencies)?;
146 self.reject_collection_dependencies(&dependencies)?;
147 let meta = NodeMeta::new(
148 id,
149 NodeKind::Derived,
150 debug_name,
151 dependencies,
152 self.revision,
153 Some(value_type::<T>()),
154 );
155 self.nodes.insert(id, meta);
156 self.derived_specs
157 .insert(id, DerivedSpec::<C, O>::new(derive));
158 Ok(DerivedNode::new(id))
159 }
160
161 pub(crate) fn attach_node_to_scope_direct(
162 &mut self,
163 node_id: NodeId,
164 scope: ScopeId,
165 ) -> GraphResult<()> {
166 let scope_meta = self.require_scope(scope)?;
167 if scope_meta.is_closed() {
168 return Err(GraphError::ScopeAlreadyClosed(scope));
169 }
170
171 let node_meta = self
172 .nodes
173 .get_mut(&node_id)
174 .ok_or(GraphError::UnknownNode(node_id))?;
175
176 if node_meta.owning_scope().is_some() {
177 return Err(GraphError::NodeAlreadyAttached(node_id));
178 }
179
180 node_meta.attach_scope(scope);
181 Ok(())
182 }
183
184 pub fn node_meta<H: NodeHandle>(&self, node: H) -> Option<&NodeMeta> {
186 self.nodes.get(&node.id())
187 }
188
189 pub fn node_meta_by_id(&self, id: NodeId) -> Option<&NodeMeta> {
191 self.nodes.get(&id)
192 }
193
194 pub fn scope_meta(&self, id: ScopeId) -> Option<&ScopeMeta> {
196 self.scopes.get(&id)
197 }
198
199 pub fn output_meta(&self, key: OutputKey) -> Option<&OutputMeta> {
201 self.outputs.get(&key)
202 }
203
204 pub fn dependencies<H: NodeHandle>(&self, node: H) -> Option<&DependencyList> {
206 self.node_meta(node).map(NodeMeta::dependencies)
207 }
208
209 pub fn nodes(&self) -> impl Iterator<Item = &NodeMeta> {
211 self.nodes.values()
212 }
213
214 pub fn scopes(&self) -> impl Iterator<Item = &ScopeMeta> {
216 self.scopes.values()
217 }
218
219 pub(crate) fn allocate_node_id(&mut self) -> NodeId {
220 let id = NodeId::from_index(self.next_node_id);
221 self.next_node_id += 1;
222 id
223 }
224
225 pub(crate) fn allocate_scope_id(&mut self) -> ScopeId {
226 let id = ScopeId::from_index(self.next_scope_id);
227 self.next_scope_id += 1;
228 id
229 }
230
231 pub(crate) fn allocate_output_key(&mut self) -> OutputKey {
232 let key = OutputKey::from_index(self.next_output_key);
233 self.next_output_key += 1;
234 key
235 }
236
237 fn allocate_transaction_id(&mut self) -> TransactionId {
238 self.next_transaction_id = self.next_transaction_id.next();
239 self.next_transaction_id
240 }
241
242 pub(crate) fn require_scope(&self, id: ScopeId) -> GraphResult<&ScopeMeta> {
243 self.scopes.get(&id).ok_or(GraphError::UnknownScope(id))
244 }
245
246 pub(crate) fn validate_dependencies(
247 &self,
248 node_id: NodeId,
249 dependencies: &DependencyList,
250 ) -> GraphResult<()> {
251 for dependency in dependencies.as_slice() {
252 if *dependency == node_id {
253 return Err(GraphError::SelfDependency(node_id));
254 }
255 if !self.nodes.contains_key(dependency) {
256 return Err(GraphError::UnknownNode(*dependency));
257 }
258 if self.depends_on(*dependency, node_id) {
259 return Err(GraphError::CycleDetected(node_id));
260 }
261 }
262 Ok(())
263 }
264
265 pub(crate) fn validate_output_dependencies(
266 &self,
267 dependencies: &DependencyList,
268 ) -> GraphResult<()> {
269 for dependency in dependencies.as_slice() {
270 if !self.nodes.contains_key(dependency) {
271 return Err(GraphError::UnknownNode(*dependency));
272 }
273 }
274 Ok(())
275 }
276
277 fn reject_collection_dependencies(&self, dependencies: &DependencyList) -> GraphResult<()> {
278 for dependency in dependencies.as_slice() {
279 if self
280 .nodes
281 .get(dependency)
282 .is_some_and(|meta| meta.kind() == NodeKind::Collection)
283 {
284 return Err(GraphError::CollectionDependencyNotAllowed(*dependency));
285 }
286 }
287 Ok(())
288 }
289
290 fn depends_on(&self, start: NodeId, target: NodeId) -> bool {
291 let Some(meta) = self.nodes.get(&start) else {
292 return false;
293 };
294 meta.dependencies()
295 .as_slice()
296 .iter()
297 .any(|dependency| *dependency == target || self.depends_on(*dependency, target))
298 }
299}