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 + Send + Sync + '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 + Send
141 + Sync
142 + 'static,
143 ) -> GraphResult<DerivedNode<T>>
144 where
145 T: Clone + PartialEq + Send + Sync + 'static,
146 {
147 self.validate_dependencies(id, &dependencies)?;
148 self.reject_collection_dependencies(&dependencies)?;
149 let meta = NodeMeta::new(
150 id,
151 NodeKind::Derived,
152 debug_name,
153 dependencies,
154 self.revision,
155 Some(value_type::<T>()),
156 );
157 self.nodes.insert(id, meta);
158 self.derived_specs
159 .insert(id, DerivedSpec::<C, O>::new(derive));
160 Ok(DerivedNode::new(id))
161 }
162
163 pub(crate) fn attach_node_to_scope_direct(
164 &mut self,
165 node_id: NodeId,
166 scope: ScopeId,
167 ) -> GraphResult<()> {
168 let scope_meta = self.require_scope(scope)?;
169 if scope_meta.is_closed() {
170 return Err(GraphError::ScopeAlreadyClosed(scope));
171 }
172
173 let node_meta = self
174 .nodes
175 .get_mut(&node_id)
176 .ok_or(GraphError::UnknownNode(node_id))?;
177
178 if node_meta.owning_scope().is_some() {
179 return Err(GraphError::NodeAlreadyAttached(node_id));
180 }
181
182 node_meta.attach_scope(scope);
183 Ok(())
184 }
185
186 pub fn node_meta<H: NodeHandle>(&self, node: H) -> Option<&NodeMeta> {
188 self.nodes.get(&node.id())
189 }
190
191 pub fn node_meta_by_id(&self, id: NodeId) -> Option<&NodeMeta> {
193 self.nodes.get(&id)
194 }
195
196 pub fn scope_meta(&self, id: ScopeId) -> Option<&ScopeMeta> {
198 self.scopes.get(&id)
199 }
200
201 pub fn output_meta(&self, key: OutputKey) -> Option<&OutputMeta> {
203 self.outputs.get(&key)
204 }
205
206 pub fn dependencies<H: NodeHandle>(&self, node: H) -> Option<&DependencyList> {
208 self.node_meta(node).map(NodeMeta::dependencies)
209 }
210
211 pub fn nodes(&self) -> impl Iterator<Item = &NodeMeta> {
213 self.nodes.values()
214 }
215
216 pub fn scopes(&self) -> impl Iterator<Item = &ScopeMeta> {
218 self.scopes.values()
219 }
220
221 pub(crate) fn allocate_node_id(&mut self) -> NodeId {
222 let id = NodeId::from_index(self.next_node_id);
223 self.next_node_id += 1;
224 id
225 }
226
227 pub(crate) fn allocate_scope_id(&mut self) -> ScopeId {
228 let id = ScopeId::from_index(self.next_scope_id);
229 self.next_scope_id += 1;
230 id
231 }
232
233 pub(crate) fn allocate_output_key(&mut self) -> OutputKey {
234 let key = OutputKey::from_index(self.next_output_key);
235 self.next_output_key += 1;
236 key
237 }
238
239 fn allocate_transaction_id(&mut self) -> TransactionId {
240 self.next_transaction_id = self.next_transaction_id.next();
241 self.next_transaction_id
242 }
243
244 pub(crate) fn require_scope(&self, id: ScopeId) -> GraphResult<&ScopeMeta> {
245 self.scopes.get(&id).ok_or(GraphError::UnknownScope(id))
246 }
247
248 pub(crate) fn validate_dependencies(
249 &self,
250 node_id: NodeId,
251 dependencies: &DependencyList,
252 ) -> GraphResult<()> {
253 for dependency in dependencies.as_slice() {
254 if *dependency == node_id {
255 return Err(GraphError::SelfDependency(node_id));
256 }
257 if !self.nodes.contains_key(dependency) {
258 return Err(GraphError::UnknownNode(*dependency));
259 }
260 if self.depends_on(*dependency, node_id) {
261 return Err(GraphError::CycleDetected(node_id));
262 }
263 }
264 Ok(())
265 }
266
267 pub(crate) fn validate_output_dependencies(
268 &self,
269 dependencies: &DependencyList,
270 ) -> GraphResult<()> {
271 for dependency in dependencies.as_slice() {
272 if !self.nodes.contains_key(dependency) {
273 return Err(GraphError::UnknownNode(*dependency));
274 }
275 }
276 Ok(())
277 }
278
279 fn reject_collection_dependencies(&self, dependencies: &DependencyList) -> GraphResult<()> {
280 for dependency in dependencies.as_slice() {
281 if self
282 .nodes
283 .get(dependency)
284 .is_some_and(|meta| meta.kind() == NodeKind::Collection)
285 {
286 return Err(GraphError::CollectionDependencyNotAllowed(*dependency));
287 }
288 }
289 Ok(())
290 }
291
292 fn depends_on(&self, start: NodeId, target: NodeId) -> bool {
293 let Some(meta) = self.nodes.get(&start) else {
294 return false;
295 };
296 meta.dependencies()
297 .as_slice()
298 .iter()
299 .any(|dependency| *dependency == target || self.depends_on(*dependency, target))
300 }
301}