zrx_graph/graph/builder.rs
1// Copyright (c) 2025-2026 Zensical and contributors
2
3// SPDX-License-Identifier: MIT
4// All contributions are certified under the DCO
5
6// Permission is hereby granted, free of charge, to any person obtaining a copy
7// of this software and associated documentation files (the "Software"), to
8// deal in the Software without restriction, including without limitation the
9// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10// sell copies of the Software, and to permit persons to whom the Software is
11// furnished to do so, subject to the following conditions:
12
13// The above copyright notice and this permission notice shall be included in
14// all copies or substantial portions of the Software.
15
16// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
19// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
22// IN THE SOFTWARE.
23
24// ----------------------------------------------------------------------------
25
26//! Graph builder.
27
28use std::collections::BTreeMap;
29use std::ops::Index;
30
31use super::error::{Error, Result};
32use super::topology::Topology;
33use super::Graph;
34
35// ----------------------------------------------------------------------------
36// Structs
37// ----------------------------------------------------------------------------
38
39/// Graph builder.
40#[derive(Clone, Debug)]
41pub struct Builder<T> {
42 /// Nodes of the graph.
43 nodes: Vec<T>,
44 /// Edges of the graph.
45 edges: Vec<Edge>,
46}
47
48/// Graph edge.
49#[derive(Clone, Debug, PartialEq, Eq)]
50pub struct Edge {
51 /// Source node index.
52 pub source: usize,
53 /// Target node index.
54 pub target: usize,
55}
56
57// ----------------------------------------------------------------------------
58// Implementations
59// ----------------------------------------------------------------------------
60
61impl<T> Graph<T> {
62 /// Creates a graph builder.
63 ///
64 /// # Examples
65 ///
66 /// ```
67 /// # use std::error::Error;
68 /// # fn main() -> Result<(), Box<dyn Error>> {
69 /// use zrx_graph::Graph;
70 ///
71 /// // Create graph builder
72 /// let mut builder = Graph::builder();
73 /// let a = builder.add_node("a");
74 /// let b = builder.add_node("b");
75 ///
76 /// // Create edges between nodes
77 /// builder.add_edge(a, b)?;
78 /// # Ok(())
79 /// # }
80 /// ```
81 #[inline]
82 #[must_use]
83 pub fn builder() -> Builder<T> {
84 Builder {
85 nodes: Vec::new(),
86 edges: Vec::new(),
87 }
88 }
89}
90
91// ----------------------------------------------------------------------------
92
93impl<T> Builder<T> {
94 /// Adds a node to the graph.
95 ///
96 /// # Examples
97 ///
98 /// ```
99 /// # use std::error::Error;
100 /// # fn main() -> Result<(), Box<dyn Error>> {
101 /// use zrx_graph::Graph;
102 ///
103 /// // Create graph builder and add nodes
104 /// let mut builder = Graph::builder();
105 /// let a = builder.add_node("a");
106 /// let b = builder.add_node("b");
107 /// #
108 /// # // Create edges between nodes
109 /// # builder.add_edge(a, b)?;
110 /// # Ok(())
111 /// # }
112 /// ```
113 pub fn add_node(&mut self, data: T) -> usize {
114 self.nodes.push(data);
115 self.nodes.len() - 1
116 }
117
118 /// Adds an edge to the graph.
119 ///
120 /// # Errors
121 ///
122 /// In case the source or target node doesn't exist, [`Error::NotFound`] is
123 /// returned, to make sure the graph does not contain stale node references.
124 /// By returning an error instead of panicking, we can provide recoverable
125 /// and proper error handling to the caller.
126 ///
127 /// This is mentionable, as some other graph libraries will just panic and
128 /// crash the program, like the popular [`petgraph`][] crate. Additionally,
129 /// note that this method does not check whether an edge already exists, as
130 /// the existence of multiple edges is a valid use case in some scenarios.
131 ///
132 /// [`petgraph`]: https://docs.rs/petgraph/
133 ///
134 /// # Examples
135 ///
136 /// ```
137 /// # use std::error::Error;
138 /// # fn main() -> Result<(), Box<dyn Error>> {
139 /// use zrx_graph::Graph;
140 ///
141 /// // Create graph builder and add nodes
142 /// let mut builder = Graph::builder();
143 /// let a = builder.add_node("a");
144 /// let b = builder.add_node("b");
145 /// let c = builder.add_node("c");
146 ///
147 /// // Create edges between nodes
148 /// builder.add_edge(a, b)?;
149 /// builder.add_edge(b, c)?;
150 /// # Ok(())
151 /// # }
152 /// ```
153 pub fn add_edge(&mut self, source: usize, target: usize) -> Result {
154 if source >= self.nodes.len() {
155 return Err(Error::NotFound(source));
156 }
157 if target >= self.nodes.len() {
158 return Err(Error::NotFound(target));
159 }
160
161 // Add edge, as both nodes were found
162 self.edges.push(Edge { source, target });
163 Ok(())
164 }
165
166 /// Creates the edge graph of the graph.
167 ///
168 /// This method derives a new graph from the given graph in which each edge
169 /// represents a transition from one edge to another based on their source
170 /// and target nodes in the original graph, which means that the nodes of
171 /// the edge graph are the edges of the original graph.
172 ///
173 /// Edge graphs are necessary for representing relationships between edges,
174 /// which is exactly what we need for action graphs, where edges represent
175 /// actions and their dependencies. During execution, we don't need to know
176 /// the actual nodes, but rather the dependencies between the edges.
177 ///
178 /// # Examples
179 ///
180 /// ```
181 /// # use std::error::Error;
182 /// # fn main() -> Result<(), Box<dyn Error>> {
183 /// use zrx_graph::Graph;
184 ///
185 /// // Create graph builder and add nodes
186 /// let mut builder = Graph::builder();
187 /// let a = builder.add_node("a");
188 /// let b = builder.add_node("b");
189 /// let c = builder.add_node("c");
190 ///
191 /// // Create edges between nodes
192 /// builder.add_edge(a, b)?;
193 /// builder.add_edge(b, c)?;
194 ///
195 /// // Create edge graph
196 /// let edges = builder.to_edge_graph();
197 /// assert_eq!(edges.nodes(), builder.edges());
198 /// # Ok(())
199 /// # }
200 /// ```
201 #[deprecated(
202 since = "0.0.6",
203 note = "Edge graphs are no longer needed for action graphs"
204 )]
205 #[must_use]
206 pub fn to_edge_graph(&self) -> Builder<Edge> {
207 // We expect that the edges are ordered by target and weight, since the
208 // former represents the corresponding action, and the latter the index
209 // of the argument in the action. This is also why we index sources by
210 // targets and not the other way around, i.e., to keep the ordering.
211 let mut targets: BTreeMap<usize, Vec<usize>> = BTreeMap::new();
212 for (source, edge) in self.edges.iter().enumerate() {
213 targets.entry(edge.target).or_default().push(source);
214 }
215
216 // Enumerate all sources for each target and create the edges between
217 // them in order to create the edge graph. The new edges don't receive
218 // a weight, since the original edges are now the nodes, and there's no
219 // other information that can't be obtained from the original graph.
220 let mut edges = Vec::with_capacity(targets.len());
221 for (target, edge) in self.edges.iter().enumerate() {
222 if let Some(sources) = targets.get(&edge.source) {
223 for &source in sources {
224 edges.push(Edge { source, target });
225 }
226 }
227 }
228
229 // Return edge graph builder
230 Builder {
231 nodes: self.edges.clone(),
232 edges,
233 }
234 }
235
236 /// Builds the graph.
237 ///
238 /// This method creates the actual graph from the builder, which brings the
239 /// graph into an executable form to allow for very efficient traversal.
240 ///
241 /// # Examples
242 ///
243 /// ```
244 /// # use std::error::Error;
245 /// # fn main() -> Result<(), Box<dyn Error>> {
246 /// use zrx_graph::Graph;
247 ///
248 /// // Create graph builder and add nodes
249 /// let mut builder = Graph::builder();
250 /// let a = builder.add_node("a");
251 /// let b = builder.add_node("b");
252 /// let c = builder.add_node("c");
253 ///
254 /// // Create edges between nodes
255 /// builder.add_edge(a, b)?;
256 /// builder.add_edge(b, c)?;
257 ///
258 /// // Create graph from builder
259 /// let graph = builder.build();
260 /// # Ok(())
261 /// # }
262 /// ```
263 #[must_use]
264 pub fn build(self) -> Graph<T> {
265 let topology = Topology::new(self.nodes.len(), &self.edges);
266 Graph { data: self.nodes, topology }
267 }
268}
269
270#[allow(clippy::must_use_candidate)]
271impl<T> Builder<T> {
272 /// Returns a reference to the nodes.
273 #[inline]
274 pub fn nodes(&self) -> &[T] {
275 &self.nodes
276 }
277
278 /// Returns a reference to the edges.
279 #[inline]
280 pub fn edges(&self) -> &[Edge] {
281 &self.edges
282 }
283
284 /// Returns the number of nodes.
285 #[inline]
286 pub fn len(&self) -> usize {
287 self.nodes.len()
288 }
289
290 /// Returns whether there are any nodes.
291 #[inline]
292 pub fn is_empty(&self) -> bool {
293 self.nodes.is_empty()
294 }
295}
296
297// ----------------------------------------------------------------------------
298// Trait implementations
299// ----------------------------------------------------------------------------
300
301impl<T> Index<usize> for Builder<T> {
302 type Output = T;
303
304 /// Returns a reference to the node at the index.
305 ///
306 /// # Panics
307 ///
308 /// Panics if the index is out of bounds.
309 ///
310 /// # Examples
311 ///
312 /// ```
313 /// # use std::error::Error;
314 /// # fn main() -> Result<(), Box<dyn Error>> {
315 /// use zrx_graph::topology::Adjacency;
316 /// use zrx_graph::Graph;
317 ///
318 /// // Create graph builder and add nodes
319 /// let mut builder = Graph::builder();
320 /// let a = builder.add_node("a");
321 /// let b = builder.add_node("b");
322 /// let c = builder.add_node("c");
323 ///
324 /// // Create edges between nodes
325 /// builder.add_edge(a, b)?;
326 /// builder.add_edge(b, c)?;
327 ///
328 /// // Obtain references to nodes
329 /// assert_eq!(&builder[a], &"a");
330 /// assert_eq!(&builder[b], &"b");
331 /// assert_eq!(&builder[c], &"c");
332 /// # Ok(())
333 /// # }
334 /// ```
335 #[inline]
336 fn index(&self, index: usize) -> &Self::Output {
337 &self.nodes[index]
338 }
339}
340
341// ----------------------------------------------------------------------------
342
343impl<T> Default for Builder<T> {
344 /// Creates a graph builder.
345 ///
346 /// # Examples
347 ///
348 /// ```
349 /// # use std::error::Error;
350 /// # fn main() -> Result<(), Box<dyn Error>> {
351 /// use zrx_graph::Builder;
352 ///
353 /// // Create graph builder
354 /// let mut builder = Builder::default();
355 /// # let a = builder.add_node("a");
356 /// # let b = builder.add_node("b");
357 /// #
358 /// # // Create edges between nodes
359 /// # builder.add_edge(a, b)?;
360 /// # Ok(())
361 /// # }
362 /// ```
363 #[inline]
364 fn default() -> Self {
365 Graph::builder()
366 }
367}