graph_api_lib/walker/steps/
probe.rs

1use crate::ElementId;
2use crate::graph::Graph;
3use crate::walker::builder::{EdgeWalkerBuilder, VertexWalkerBuilder};
4use crate::walker::{EdgeWalker, VertexWalker, Walker};
5use include_doc::function_body;
6use std::marker::PhantomData;
7
8// ================ PROBE IMPLEMENTATION ================
9
10pub struct VertexProbe<'graph, Parent, Callback> {
11    _phantom_data: PhantomData<&'graph ()>,
12    parent: Parent,
13    callback: Callback,
14}
15
16impl<Parent, Callback> VertexProbe<'_, Parent, Callback> {
17    pub(crate) fn new(parent: Parent, callback: Callback) -> Self {
18        VertexProbe {
19            _phantom_data: Default::default(),
20            parent,
21            callback,
22        }
23    }
24}
25
26impl<'graph, Parent, Callback> Walker<'graph> for VertexProbe<'graph, Parent, Callback>
27where
28    Parent: VertexWalker<'graph>,
29    Callback: FnMut(&<Parent::Graph as Graph>::VertexReference<'_>, &Parent::Context),
30{
31    type Graph = Parent::Graph;
32    type Context = Parent::Context;
33
34    fn next_element(&mut self, graph: &'graph Self::Graph) -> Option<ElementId<Self::Graph>> {
35        self.next(graph).map(ElementId::Vertex)
36    }
37
38    fn ctx(&self) -> &Self::Context {
39        self.parent.ctx()
40    }
41    fn ctx_mut(&mut self) -> &mut Self::Context {
42        self.parent.ctx_mut()
43    }
44}
45
46impl<'graph, Parent, Callback> VertexWalker<'graph> for VertexProbe<'graph, Parent, Callback>
47where
48    Parent: VertexWalker<'graph>,
49    Callback: FnMut(&<Parent::Graph as Graph>::VertexReference<'_>, &Parent::Context),
50{
51    fn next(&mut self, graph: &'graph Self::Graph) -> Option<<Self::Graph as Graph>::VertexId> {
52        let next = self.parent.next(graph);
53        if let Some(id) = next {
54            if let Some(vertex) = graph.vertex(id) {
55                (self.callback)(&vertex, self.parent.ctx());
56            }
57        }
58        next
59    }
60}
61
62pub struct EdgeProbe<'graph, Parent, Callback> {
63    _phantom_data: PhantomData<&'graph ()>,
64    parent: Parent,
65    callback: Callback,
66}
67
68impl<Parent, Callback> EdgeProbe<'_, Parent, Callback> {
69    pub(crate) fn new(parent: Parent, callback: Callback) -> Self {
70        EdgeProbe {
71            _phantom_data: Default::default(),
72            parent,
73            callback,
74        }
75    }
76}
77
78impl<'graph, Parent, Callback> Walker<'graph> for EdgeProbe<'graph, Parent, Callback>
79where
80    Parent: EdgeWalker<'graph>,
81    Callback: FnMut(&<Parent::Graph as Graph>::EdgeReference<'_>, &Parent::Context),
82{
83    type Graph = Parent::Graph;
84    type Context = Parent::Context;
85
86    fn next_element(&mut self, graph: &'graph Self::Graph) -> Option<ElementId<Self::Graph>> {
87        self.next(graph).map(ElementId::Edge)
88    }
89
90    fn ctx(&self) -> &Self::Context {
91        self.parent.ctx()
92    }
93    fn ctx_mut(&mut self) -> &mut Self::Context {
94        self.parent.ctx_mut()
95    }
96}
97
98impl<'graph, Parent, Callback> EdgeWalker<'graph> for EdgeProbe<'graph, Parent, Callback>
99where
100    Parent: EdgeWalker<'graph>,
101    Callback: FnMut(&<Parent::Graph as Graph>::EdgeReference<'_>, &Parent::Context),
102{
103    fn next(&mut self, graph: &'graph Self::Graph) -> Option<<Self::Graph as Graph>::EdgeId> {
104        let next = self.parent.next(graph);
105        if let Some(next) = next {
106            let edge = graph.edge(next).expect("edge must exist");
107            (self.callback)(&edge, self.parent.ctx());
108        }
109        next
110    }
111}
112
113impl<'graph, Mutability, Graph, Walker> VertexWalkerBuilder<'graph, Mutability, Graph, Walker>
114where
115    Graph: crate::graph::Graph,
116    Walker: VertexWalker<'graph, Graph = Graph>,
117{
118    /// # Probe Step
119    ///
120    /// The `probe` step allows you to execute a callback function for each vertex in the traversal
121    /// without altering the traversal itself. This is useful for debugging, logging, or collecting
122    /// information during a traversal.
123    ///
124    /// ## Visual Diagram
125    ///
126    /// Before probe step:
127    /// ```text
128    ///   [A]* --- edge1 ---> [B]* --- edge2 ---> [C]*
129    ///    ^
130    ///    |
131    ///   edge3
132    ///    |
133    ///   [D]*
134    /// ```
135    ///
136    /// After probe step (unchanged, but callback executed for each vertex *):
137    /// ```text
138    ///   [A]* --- edge1 ---> [B]* --- edge2 ---> [C]*
139    ///    ^
140    ///    |
141    ///   edge3
142    ///    |
143    ///   [D]*
144    /// ```
145    ///
146    /// ## Parameters
147    ///
148    /// - `callback`: A function that takes a reference to the current vertex being traversed,
149    ///   and optionally the current context.
150    ///   The function signature can be either:
151    ///   - `FnMut(&Graph::VertexReference<'_>, &Context)` - Probe with access to current context
152    ///
153    /// ## Return Value
154    ///
155    /// A walker of the same type as the input with the probe operation added to the pipeline,
156    /// allowing for further chaining of operations.
157    ///
158    /// ## Example
159    ///
160    /// ```rust
161    #[doc = function_body!("examples/probe.rs", vertex_example, [])]
162    /// ```
163    ///
164    /// ## Notes
165    ///
166    /// - The `probe` step does not modify the traversal path or elements
167    /// - The callback function is executed for each vertex as it's traversed
168    /// - When using the context variant, you can access traversal context data during probing
169    /// - Useful for debugging, logging, or gathering statistics about your graph
170    /// - Side effects in the callback function (like printing or counting) do not affect the traversal
171    /// - Can be used at multiple points in a traversal to monitor the flow at different stages
172    /// - Consider using pattern matching in the callback to work with specific vertex types
173    /// - Context access is especially useful when combined with `push_context` steps earlier in the traversal
174    pub fn probe<Callback>(
175        self,
176        callback: Callback,
177    ) -> VertexWalkerBuilder<'graph, Mutability, Graph, VertexProbe<'graph, Walker, Callback>>
178    where
179        Callback: FnMut(&Graph::VertexReference<'_>, &Walker::Context),
180    {
181        self.with_vertex_walker(|walker| VertexProbe::new(walker, callback))
182    }
183}
184
185impl<'graph, Mutability, Graph, Walker> EdgeWalkerBuilder<'graph, Mutability, Graph, Walker>
186where
187    Graph: crate::graph::Graph,
188    Walker: EdgeWalker<'graph, Graph = Graph>,
189{
190    /// # Probe Step
191    ///
192    /// The `probe` step allows you to execute a callback function for each edge in the traversal
193    /// without altering the traversal itself. This is useful for debugging, analyzing connections,
194    /// or collecting edge statistics during a traversal.
195    ///
196    /// ## Visual Diagram
197    ///
198    /// Before probe step:
199    /// ```text
200    ///   [Person A] --- knows* ---> [Person B] --- created* ---> [Project]
201    ///    ^
202    ///    |
203    ///   owns*
204    ///    |
205    ///   [Company]
206    /// ```
207    ///
208    /// After probe step (unchanged, but callback executed for each edge *):
209    /// ```text
210    ///   [Person A] --- knows* ---> [Person B] --- created* ---> [Project]
211    ///    ^
212    ///    |
213    ///   owns*
214    ///    |
215    ///   [Company]
216    /// ```
217    ///
218    /// ## Parameters
219    ///
220    /// - `callback`: A function that takes a reference to the current edge being traversed,
221    ///   and optionally the current context.
222    ///   The function signature can be either:
223    ///   - `FnMut(&Graph::EdgeReference<'_>, &Context)` - Probe with access to current context
224    ///
225    /// ## Return Value
226    ///
227    /// A walker of the same type as the input with the probe operation added to the pipeline,
228    /// allowing for further chaining of operations.
229    ///
230    /// ## Example
231    ///
232    /// ```rust
233    #[doc = function_body!("examples/probe.rs", edge_example, [])]
234    /// ```
235    ///
236    /// ## Notes
237    ///
238    /// - The `probe` step does not modify the traversal path or edges
239    /// - The callback function is executed for each edge as it's traversed
240    /// - When using the context variant, you can access traversal context data during probing
241    /// - Useful for analyzing connection patterns without modifying the traversal
242    /// - Consider using pattern matching in the callback to handle different edge types
243    /// - You can use endpoint accessors like `tail()` and `head()` to inspect connected vertices
244    /// - Context access is especially useful when combined with `push_context` steps earlier in the traversal
245    pub fn probe<Callback>(
246        self,
247        callback: Callback,
248    ) -> EdgeWalkerBuilder<'graph, Mutability, Graph, EdgeProbe<'graph, Walker, Callback>>
249    where
250        Callback: FnMut(&Graph::EdgeReference<'_>, &Walker::Context),
251    {
252        self.with_edge_walker(|walker| EdgeProbe::new(walker, callback))
253    }
254}