Skip to main content

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}