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, W = ()> {
42    /// Nodes of the graph.
43    nodes: Vec<T>,
44    /// Edges of the graph.
45    edges: Vec<Edge<W>>,
46}
47
48/// Graph edge.
49#[derive(Clone, Debug, PartialEq, Eq)]
50pub struct Edge<W = ()> {
51    /// Source node index.
52    pub source: usize,
53    /// Target node index.
54    pub target: usize,
55    /// Weight.
56    pub weight: W,
57}
58
59// ----------------------------------------------------------------------------
60// Implementations
61// ----------------------------------------------------------------------------
62
63impl<T> Graph<T> {
64    /// Creates a graph builder.
65    ///
66    /// # Examples
67    ///
68    /// ```
69    /// # use std::error::Error;
70    /// # fn main() -> Result<(), Box<dyn Error>> {
71    /// use zrx_graph::Graph;
72    ///
73    /// // Create graph builder
74    /// let mut builder = Graph::builder();
75    /// let a = builder.add_node("a");
76    /// let b = builder.add_node("b");
77    ///
78    /// // Create edges between nodes
79    /// builder.add_edge(a, b, 0)?;
80    /// # Ok(())
81    /// # }
82    /// ```
83    #[inline]
84    #[must_use]
85    pub fn builder<W>() -> Builder<T, W>
86    where
87        W: Clone,
88    {
89        Builder {
90            nodes: Vec::new(),
91            edges: Vec::new(),
92        }
93    }
94}
95
96// ----------------------------------------------------------------------------
97
98impl<T, W> Builder<T, W> {
99    /// Adds a node to the graph.
100    ///
101    /// # Examples
102    ///
103    /// ```
104    /// # use std::error::Error;
105    /// # fn main() -> Result<(), Box<dyn Error>> {
106    /// use zrx_graph::Graph;
107    ///
108    /// // Create graph builder and add nodes
109    /// let mut builder = Graph::builder();
110    /// let a = builder.add_node("a");
111    /// let b = builder.add_node("b");
112    /// #
113    /// # // Create edges between nodes
114    /// # builder.add_edge(a, b, 0)?;
115    /// # Ok(())
116    /// # }
117    /// ```
118    pub fn add_node(&mut self, node: T) -> usize {
119        self.nodes.push(node);
120        self.nodes.len() - 1
121    }
122
123    /// Adds an edge to the graph.
124    ///
125    /// # Errors
126    ///
127    /// In case the source or target node doesn't exist, [`Error::NotFound`] is
128    /// returned, to make sure the graph does not contain stale node references.
129    /// By returning an error instead of panicking, we can provide recoverable
130    /// and proper error handling to the caller.
131    ///
132    /// This is mentionable, as some other graph libraries will just panic and
133    /// crash the program, like the popular [`petgraph`][] crate. Additionally,
134    /// note that this method does not check whether an edge already exists, as
135    /// the existence of multiple edges is a valid use case in some scenarios.
136    ///
137    /// [`petgraph`]: https://docs.rs/petgraph/
138    ///
139    /// # Examples
140    ///
141    /// ```
142    /// # use std::error::Error;
143    /// # fn main() -> Result<(), Box<dyn Error>> {
144    /// use zrx_graph::Graph;
145    ///
146    /// // Create graph builder and add nodes
147    /// let mut builder = Graph::builder();
148    /// let a = builder.add_node("a");
149    /// let b = builder.add_node("b");
150    /// let c = builder.add_node("c");
151    ///
152    /// // Create edges between nodes
153    /// builder.add_edge(a, b, 0)?;
154    /// builder.add_edge(b, c, 0)?;
155    /// # Ok(())
156    /// # }
157    /// ```
158    pub fn add_edge(
159        &mut self, source: usize, target: usize, weight: W,
160    ) -> Result {
161        if source >= self.nodes.len() {
162            return Err(Error::NotFound(source));
163        }
164        if target >= self.nodes.len() {
165            return Err(Error::NotFound(target));
166        }
167
168        // Add edge, as both nodes were found
169        self.edges.push(Edge { source, target, weight });
170        Ok(())
171    }
172
173    /// Creates the edge graph of the graph.
174    ///
175    /// This method derives a new graph from the given graph in which each edge
176    /// represents a transition from one edge to another based on their source
177    /// and target nodes in the original graph, which means that the nodes of
178    /// the edge graph are the edges of the original graph.
179    ///
180    /// Edge graphs are necessary for representing relationships between edges,
181    /// which is exactly what we need for action graphs, where edges represent
182    /// actions and their dependencies. During execution, we don't need to know
183    /// the actual nodes, but rather the dependencies between the edges.
184    ///
185    /// # Examples
186    ///
187    /// ```
188    /// # use std::error::Error;
189    /// # fn main() -> Result<(), Box<dyn Error>> {
190    /// use zrx_graph::Graph;
191    ///
192    /// // Create graph builder and add nodes
193    /// let mut builder = Graph::builder();
194    /// let a = builder.add_node("a");
195    /// let b = builder.add_node("b");
196    /// let c = builder.add_node("c");
197    ///
198    /// // Create edges between nodes
199    /// builder.add_edge(a, b, 0)?;
200    /// builder.add_edge(b, c, 0)?;
201    ///
202    /// // Create edge graph
203    /// let edges = builder.to_edge_graph();
204    /// assert_eq!(edges.nodes(), builder.edges());
205    /// # Ok(())
206    /// # }
207    /// ```
208    #[must_use]
209    pub fn to_edge_graph(&self) -> Builder<Edge<W>>
210    where
211        W: Clone,
212    {
213        // We expect that the edges are ordered by target an weight, since the
214        // former represents the corresponding action, and the latter the index
215        // of the argument in the action. This is also why we index sources by
216        // targets and not the other way around, i.e., to keep the ordering.
217        let mut targets: BTreeMap<usize, Vec<usize>> = BTreeMap::new();
218        for (source, edge) in self.edges.iter().enumerate() {
219            targets.entry(edge.target).or_default().push(source);
220        }
221
222        // Enumerate all sources for each target and create the edges between
223        // them in order to create the edge graph. The new edges don't receive
224        // a weight, since the original edges are now the nodes, and there's no
225        // other information that can't be obtained from the original graph.
226        let mut edges = Vec::with_capacity(targets.len());
227        for (target, edge) in self.edges.iter().enumerate() {
228            if let Some(sources) = targets.get(&edge.source) {
229                for &source in sources {
230                    edges.push(Edge { source, target, weight: () });
231                }
232            }
233        }
234
235        // Return edge graph builder
236        Builder {
237            nodes: self.edges.clone(),
238            edges,
239        }
240    }
241
242    /// Builds the graph.
243    ///
244    /// This method creates the actual graph from the builder, which brings the
245    /// graph into an executable form to allow for very efficient traversal.
246    ///
247    /// # Examples
248    ///
249    /// ```
250    /// # use std::error::Error;
251    /// # fn main() -> Result<(), Box<dyn Error>> {
252    /// use zrx_graph::Graph;
253    ///
254    /// // Create graph builder and add nodes
255    /// let mut builder = Graph::builder();
256    /// let a = builder.add_node("a");
257    /// let b = builder.add_node("b");
258    /// let c = builder.add_node("c");
259    ///
260    /// // Create edges between nodes
261    /// builder.add_edge(a, b, 0)?;
262    /// builder.add_edge(b, c, 0)?;
263    ///
264    /// // Create graph from builder
265    /// let graph = builder.build();
266    /// # Ok(())
267    /// # }
268    /// ```
269    #[must_use]
270    pub fn build(self) -> Graph<T>
271    where
272        W: Clone,
273    {
274        Graph {
275            topology: Topology::new(&self),
276            data: self.nodes,
277        }
278    }
279}
280
281#[allow(clippy::must_use_candidate)]
282impl<T, W> Builder<T, W> {
283    /// Returns a reference to the nodes.
284    #[inline]
285    pub fn nodes(&self) -> &[T] {
286        &self.nodes
287    }
288
289    /// Returns a reference to the edges.
290    #[inline]
291    pub fn edges(&self) -> &[Edge<W>] {
292        &self.edges
293    }
294
295    /// Returns the number of nodes.
296    #[inline]
297    pub fn len(&self) -> usize {
298        self.nodes.len()
299    }
300
301    /// Returns whether there are any nodes.
302    #[inline]
303    pub fn is_empty(&self) -> bool {
304        self.nodes.is_empty()
305    }
306}
307
308// ----------------------------------------------------------------------------
309// Trait implementations
310// ----------------------------------------------------------------------------
311
312impl<T, W> Index<usize> for Builder<T, W> {
313    type Output = T;
314
315    /// Returns a reference to the node at the index.
316    ///
317    /// # Panics
318    ///
319    /// Panics if the index is out of bounds.
320    ///
321    /// # Examples
322    ///
323    /// ```
324    /// # use std::error::Error;
325    /// # fn main() -> Result<(), Box<dyn Error>> {
326    /// use zrx_graph::topology::Adjacency;
327    /// use zrx_graph::Graph;
328    ///
329    /// // Create graph builder and add nodes
330    /// let mut builder = Graph::builder();
331    /// let a = builder.add_node("a");
332    /// let b = builder.add_node("b");
333    /// let c = builder.add_node("c");
334    ///
335    /// // Create edges between nodes
336    /// builder.add_edge(a, b, 0)?;
337    /// builder.add_edge(b, c, 0)?;
338    ///
339    /// // Obtain references to nodes
340    /// assert_eq!(&builder[a], &"a");
341    /// assert_eq!(&builder[b], &"b");
342    /// assert_eq!(&builder[c], &"c");
343    /// # Ok(())
344    /// # }
345    /// ```
346    #[inline]
347    fn index(&self, index: usize) -> &Self::Output {
348        &self.nodes[index]
349    }
350}
351
352// ----------------------------------------------------------------------------
353
354impl<T, W> Default for Builder<T, W>
355where
356    W: Clone,
357{
358    /// Creates a graph builder.
359    ///
360    /// # Examples
361    ///
362    /// ```
363    /// # use std::error::Error;
364    /// # fn main() -> Result<(), Box<dyn Error>> {
365    /// use zrx_graph::Builder;
366    ///
367    /// // Create graph builder
368    /// let mut builder = Builder::default();
369    /// # let a = builder.add_node("a");
370    /// # let b = builder.add_node("b");
371    /// #
372    /// # // Create edges between nodes
373    /// # builder.add_edge(a, b, 0)?;
374    /// # Ok(())
375    /// # }
376    /// ```
377    #[inline]
378    fn default() -> Self {
379        Graph::builder()
380    }
381}