Skip to main content

interstellar/traversal/
traverser.rs

1//! Traverser and path types for the graph traversal engine.
2//!
3//! This module provides the core types for carrying values through the traversal pipeline:
4//! - `Traverser`: Carries a `Value` through the pipeline with metadata
5//! - `Path`: Tracks traversal history
6//! - `PathElement`: A single element in the path
7//! - `PathValue`: Values that can be stored in a path
8
9use std::any::Any;
10use std::collections::HashMap;
11
12use smallvec::SmallVec;
13
14use crate::value::{EdgeId, Value, VertexId};
15
16// -----------------------------------------------------------------------------
17// CloneSack trait - enables cloning of boxed sack values
18// -----------------------------------------------------------------------------
19
20/// Trait for clonable sack values.
21///
22/// Sacks are used to carry data alongside traversers through the pipeline.
23/// This trait enables cloning of boxed sack values while maintaining type safety.
24///
25/// # Implementation
26///
27/// This trait uses a sealed pattern to prevent external implementations while
28/// allowing any `Clone + Any + Send + 'static` type to be used as a sack value
29/// through the `SackValue` wrapper.
30pub trait CloneSack: Send {
31    /// Clone this sack value into a boxed trait object.
32    fn clone_box(&self) -> Box<dyn CloneSack>;
33
34    /// Get a reference to the underlying value as `Any`.
35    fn as_any(&self) -> &dyn Any;
36}
37
38/// Wrapper type for sack values that implements `CloneSack`.
39///
40/// This wrapper is used internally to store arbitrary cloneable values
41/// in the traverser's sack.
42#[derive(Clone)]
43struct SackValue<T>(T);
44
45impl<T: Clone + Any + Send + 'static> CloneSack for SackValue<T> {
46    fn clone_box(&self) -> Box<dyn CloneSack> {
47        Box::new(SackValue(self.0.clone()))
48    }
49
50    fn as_any(&self) -> &dyn Any {
51        &self.0
52    }
53}
54
55/// Create a boxed sack value from any cloneable type.
56pub(crate) fn box_sack<T: Clone + Any + Send + 'static>(value: T) -> Box<dyn CloneSack> {
57    Box::new(SackValue(value))
58}
59
60impl Clone for Box<dyn CloneSack> {
61    fn clone(&self) -> Self {
62        self.clone_box()
63    }
64}
65
66// -----------------------------------------------------------------------------
67// PathValue - values that can be stored in a path
68// -----------------------------------------------------------------------------
69
70/// Values that can be stored in a path.
71///
72/// Path values represent the elements encountered during traversal.
73/// They are categorized into vertices, edges, and other property values.
74#[derive(Clone, Debug, PartialEq, Eq)]
75pub enum PathValue {
76    /// A vertex in the path
77    Vertex(VertexId),
78    /// An edge in the path
79    Edge(EdgeId),
80    /// A property or other value in the path
81    Property(Value),
82}
83
84impl std::hash::Hash for PathValue {
85    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
86        match self {
87            PathValue::Vertex(id) => {
88                0u8.hash(state);
89                id.hash(state);
90            }
91            PathValue::Edge(id) => {
92                1u8.hash(state);
93                id.hash(state);
94            }
95            PathValue::Property(v) => {
96                2u8.hash(state);
97                v.hash(state);
98            }
99        }
100    }
101}
102
103impl From<&Value> for PathValue {
104    fn from(value: &Value) -> Self {
105        match value {
106            Value::Vertex(id) => PathValue::Vertex(*id),
107            Value::Edge(id) => PathValue::Edge(*id),
108            other => PathValue::Property(other.clone()),
109        }
110    }
111}
112
113impl From<Value> for PathValue {
114    fn from(value: Value) -> Self {
115        match value {
116            Value::Vertex(id) => PathValue::Vertex(id),
117            Value::Edge(id) => PathValue::Edge(id),
118            other => PathValue::Property(other),
119        }
120    }
121}
122
123impl From<VertexId> for PathValue {
124    fn from(id: VertexId) -> Self {
125        PathValue::Vertex(id)
126    }
127}
128
129impl From<EdgeId> for PathValue {
130    fn from(id: EdgeId) -> Self {
131        PathValue::Edge(id)
132    }
133}
134
135impl PathValue {
136    /// Check if this path value is a vertex.
137    #[inline]
138    pub fn is_vertex(&self) -> bool {
139        matches!(self, PathValue::Vertex(_))
140    }
141
142    /// Check if this path value is an edge.
143    #[inline]
144    pub fn is_edge(&self) -> bool {
145        matches!(self, PathValue::Edge(_))
146    }
147
148    /// Get the vertex ID if this is a vertex.
149    #[inline]
150    pub fn as_vertex_id(&self) -> Option<VertexId> {
151        match self {
152            PathValue::Vertex(id) => Some(*id),
153            _ => None,
154        }
155    }
156
157    /// Get the edge ID if this is an edge.
158    #[inline]
159    pub fn as_edge_id(&self) -> Option<EdgeId> {
160        match self {
161            PathValue::Edge(id) => Some(*id),
162            _ => None,
163        }
164    }
165
166    /// Convert to a `Value`.
167    pub fn to_value(&self) -> Value {
168        match self {
169            PathValue::Vertex(id) => Value::Vertex(*id),
170            PathValue::Edge(id) => Value::Edge(*id),
171            PathValue::Property(v) => v.clone(),
172        }
173    }
174}
175
176// -----------------------------------------------------------------------------
177// PathElement - a single element in the path with labels
178// -----------------------------------------------------------------------------
179
180/// A single element in the path.
181///
182/// Each path element contains a value and optional labels that were assigned
183/// to it via `as()` step during traversal.
184#[derive(Clone, Debug)]
185pub struct PathElement {
186    /// The value at this position in the path.
187    pub value: PathValue,
188    /// Labels assigned to this path position.
189    pub labels: SmallVec<[String; 2]>,
190}
191
192impl PathElement {
193    /// Create a new path element with no labels.
194    pub fn new(value: PathValue) -> Self {
195        Self {
196            value,
197            labels: SmallVec::new(),
198        }
199    }
200
201    /// Create a new path element with labels.
202    pub fn with_labels(value: PathValue, labels: impl IntoIterator<Item = String>) -> Self {
203        Self {
204            value,
205            labels: labels.into_iter().collect(),
206        }
207    }
208}
209
210// -----------------------------------------------------------------------------
211// Path - tracks traversal history
212// -----------------------------------------------------------------------------
213
214/// Path tracks traversal history.
215///
216/// The path records every element visited during traversal, along with any
217/// labels that were assigned via `as()` steps. This enables path-based
218/// queries and cycle detection.
219///
220/// # Example
221///
222/// ```ignore
223/// let mut path = Path::default();
224/// path.push(PathValue::Vertex(VertexId(1)), &["start".to_string()]);
225/// path.push(PathValue::Edge(EdgeId(1)), &[]);
226/// path.push(PathValue::Vertex(VertexId(2)), &["end".to_string()]);
227///
228/// assert_eq!(path.len(), 3);
229/// assert!(path.contains_vertex(VertexId(1)));
230/// ```
231#[derive(Clone, Default, Debug)]
232pub struct Path {
233    /// Ordered list of path elements.
234    objects: Vec<PathElement>,
235    /// Label to indices mapping for quick lookups.
236    labels: HashMap<String, Vec<usize>>,
237}
238
239impl Path {
240    /// Create a new empty path.
241    pub fn new() -> Self {
242        Self::default()
243    }
244
245    /// Push a new element onto the path.
246    ///
247    /// # Arguments
248    ///
249    /// * `value` - The path value to add
250    /// * `labels` - Labels to assign to this path position
251    pub fn push(&mut self, value: PathValue, labels: &[String]) {
252        let idx = self.objects.len();
253
254        // Update label index
255        for label in labels {
256            self.labels.entry(label.clone()).or_default().push(idx);
257        }
258
259        self.objects.push(PathElement {
260            value,
261            labels: labels.iter().cloned().collect(),
262        });
263    }
264
265    /// Push a new element with a single label.
266    pub fn push_labeled(&mut self, value: PathValue, label: &str) {
267        self.push(value, &[label.to_string()]);
268    }
269
270    /// Push a new element without labels.
271    pub fn push_unlabeled(&mut self, value: PathValue) {
272        self.push(value, &[]);
273    }
274
275    /// Get elements by label.
276    ///
277    /// Returns `None` if the label doesn't exist in the path.
278    pub fn get(&self, label: &str) -> Option<Vec<&PathValue>> {
279        self.labels
280            .get(label)
281            .map(|indices| indices.iter().map(|&i| &self.objects[i].value).collect())
282    }
283
284    /// Get all objects in order.
285    pub fn objects(&self) -> impl Iterator<Item = &PathValue> {
286        self.objects.iter().map(|e| &e.value)
287    }
288
289    /// Get all path elements in order.
290    pub fn elements(&self) -> impl Iterator<Item = &PathElement> {
291        self.objects.iter()
292    }
293
294    /// Check if path contains a vertex (for cycle detection).
295    pub fn contains_vertex(&self, id: VertexId) -> bool {
296        self.objects
297            .iter()
298            .any(|e| matches!(&e.value, PathValue::Vertex(v) if *v == id))
299    }
300
301    /// Check if path contains an edge.
302    pub fn contains_edge(&self, id: EdgeId) -> bool {
303        self.objects
304            .iter()
305            .any(|e| matches!(&e.value, PathValue::Edge(e) if *e == id))
306    }
307
308    /// Check if a label exists in the path.
309    pub fn has_label(&self, label: &str) -> bool {
310        self.labels.contains_key(label)
311    }
312
313    /// Get all labels used in this path.
314    pub fn all_labels(&self) -> impl Iterator<Item = &String> {
315        self.labels.keys()
316    }
317
318    /// Length of the path.
319    #[inline]
320    pub fn len(&self) -> usize {
321        self.objects.len()
322    }
323
324    /// Check if path is empty.
325    #[inline]
326    pub fn is_empty(&self) -> bool {
327        self.objects.is_empty()
328    }
329
330    /// Get the last element in the path.
331    pub fn last(&self) -> Option<&PathValue> {
332        self.objects.last().map(|e| &e.value)
333    }
334
335    /// Add a label to the last path element if it matches the given value,
336    /// or push a new element with the label if not.
337    ///
338    /// This is used by `as_()` to label the current position without
339    /// adding a duplicate entry when path tracking has already added it.
340    ///
341    /// # Arguments
342    ///
343    /// * `label` - The label to assign
344    /// * `current_value` - The current traverser value to check against/add
345    pub fn label_or_push(&mut self, label: &str, current_value: PathValue) {
346        // Check if the last element matches the current value
347        if let Some(last_idx) = self.objects.len().checked_sub(1) {
348            if self.objects[last_idx].value == current_value {
349                // Last element matches, just add the label
350                self.objects[last_idx].labels.push(label.to_string());
351                self.labels
352                    .entry(label.to_string())
353                    .or_default()
354                    .push(last_idx);
355                return;
356            }
357        }
358        // Either path is empty or last element doesn't match - push new
359        self.push_labeled(current_value, label);
360    }
361
362    /// Get the first element in the path.
363    pub fn first(&self) -> Option<&PathValue> {
364        self.objects.first().map(|e| &e.value)
365    }
366
367    /// Convert path to a list of values.
368    pub fn to_list(&self) -> Vec<Value> {
369        self.objects.iter().map(|e| e.value.to_value()).collect()
370    }
371}
372
373// -----------------------------------------------------------------------------
374// Traverser - carries a Value through the pipeline with metadata
375// -----------------------------------------------------------------------------
376
377/// Traverser carries a `Value` through the pipeline with metadata.
378///
379/// Unlike a monomorphic design, we use a single concrete type with `Value`
380/// to enable type erasure in steps. This allows heterogeneous steps to be
381/// stored in a `Vec<Box<dyn DynStep>>`.
382///
383/// # Metadata
384///
385/// - `path`: History of elements visited
386/// - `loops`: Counter for `repeat()` step
387/// - `sack`: Optional data carried alongside the traverser
388/// - `bulk`: Optimization for identical traversers
389///
390/// # Example
391///
392/// ```ignore
393/// let t = Traverser::from_vertex(VertexId(1));
394/// assert_eq!(t.as_vertex_id(), Some(VertexId(1)));
395///
396/// // Split preserves metadata
397/// let t2 = t.split(Value::Vertex(VertexId(2)));
398/// assert_eq!(t2.path.len(), t.path.len());
399/// ```
400#[derive(Clone)]
401pub struct Traverser {
402    /// The current element (always a Value).
403    pub value: Value,
404    /// Path history.
405    pub path: Path,
406    /// Loop counter for `repeat()`.
407    pub loops: usize,
408    /// Optional sack value (for future use).
409    pub sack: Option<Box<dyn CloneSack>>,
410    /// Bulk count (optimization for identical traversers).
411    pub bulk: u64,
412}
413
414impl Traverser {
415    /// Create a new traverser with default metadata.
416    ///
417    /// # Arguments
418    ///
419    /// * `value` - The initial value for the traverser
420    pub fn new(value: impl Into<Value>) -> Self {
421        Self {
422            value: value.into(),
423            path: Path::default(),
424            loops: 0,
425            sack: None,
426            bulk: 1,
427        }
428    }
429
430    /// Create traverser for a vertex.
431    ///
432    /// # Arguments
433    ///
434    /// * `id` - The vertex ID
435    pub fn from_vertex(id: VertexId) -> Self {
436        Self::new(Value::Vertex(id))
437    }
438
439    /// Create traverser for an edge.
440    ///
441    /// # Arguments
442    ///
443    /// * `id` - The edge ID
444    pub fn from_edge(id: EdgeId) -> Self {
445        Self::new(Value::Edge(id))
446    }
447
448    /// Split traverser for branching (preserves path and metadata).
449    ///
450    /// Creates a new traverser with a different value but the same
451    /// path, loops, sack, and bulk. Used when a single traverser
452    /// branches into multiple paths.
453    ///
454    /// # Arguments
455    ///
456    /// * `new_value` - The value for the new traverser
457    pub fn split(&self, new_value: impl Into<Value>) -> Traverser {
458        Traverser {
459            value: new_value.into(),
460            path: self.path.clone(),
461            loops: self.loops,
462            sack: self.sack.clone(),
463            bulk: self.bulk,
464        }
465    }
466
467    /// Replace the value while preserving metadata.
468    ///
469    /// Consumes self and returns a new traverser with the updated value.
470    /// More efficient than `split()` when you don't need to keep the original.
471    ///
472    /// # Arguments
473    ///
474    /// * `new_value` - The new value for the traverser
475    pub fn with_value(self, new_value: impl Into<Value>) -> Traverser {
476        Traverser {
477            value: new_value.into(),
478            path: self.path,
479            loops: self.loops,
480            sack: self.sack,
481            bulk: self.bulk,
482        }
483    }
484
485    /// Increment loop counter.
486    ///
487    /// Called by the `repeat()` step each time the traverser loops.
488    pub fn inc_loops(&mut self) {
489        self.loops += 1;
490    }
491
492    /// Get the current loop count.
493    ///
494    /// Returns the number of times this traverser has been through a `repeat()` loop.
495    #[inline]
496    pub fn loops(&self) -> usize {
497        self.loops
498    }
499
500    /// Extend path with current value.
501    ///
502    /// Adds the current value to the path with the given labels.
503    ///
504    /// # Arguments
505    ///
506    /// * `labels` - Labels to assign to this path position
507    pub fn extend_path(&mut self, labels: &[String]) {
508        let path_value = PathValue::from(&self.value);
509        self.path.push(path_value, labels);
510    }
511
512    /// Extend path with current value using a single label.
513    pub fn extend_path_labeled(&mut self, label: &str) {
514        self.extend_path(&[label.to_string()]);
515    }
516
517    /// Extend path with current value without labels.
518    pub fn extend_path_unlabeled(&mut self) {
519        self.extend_path(&[]);
520    }
521
522    /// Label the current path position or add a new labeled entry.
523    ///
524    /// Used by `as_()` step. If the last path element matches the current
525    /// value (e.g., when path tracking already added it), adds the label
526    /// to that element. Otherwise, pushes a new entry with the label.
527    pub fn label_path_position(&mut self, label: &str) {
528        let current = PathValue::from(&self.value);
529        self.path.label_or_push(label, current);
530    }
531
532    /// Get the value as a vertex ID (if it is one).
533    #[inline]
534    pub fn as_vertex_id(&self) -> Option<VertexId> {
535        self.value.as_vertex_id()
536    }
537
538    /// Get the value as an edge ID (if it is one).
539    #[inline]
540    pub fn as_edge_id(&self) -> Option<EdgeId> {
541        self.value.as_edge_id()
542    }
543
544    /// Check if the current value is a vertex.
545    #[inline]
546    pub fn is_vertex(&self) -> bool {
547        self.value.is_vertex()
548    }
549
550    /// Check if the current value is an edge.
551    #[inline]
552    pub fn is_edge(&self) -> bool {
553        self.value.is_edge()
554    }
555
556    /// Get a reference to the sack value, downcasted to type T.
557    pub fn get_sack<T: Clone + Any + Send + 'static>(&self) -> Option<&T> {
558        self.sack.as_ref().and_then(|s| s.as_any().downcast_ref())
559    }
560
561    /// Set the sack value.
562    pub fn set_sack<T: Clone + Any + Send + 'static>(&mut self, value: T) {
563        self.sack = Some(box_sack(value));
564    }
565
566    /// Clear the sack value.
567    pub fn clear_sack(&mut self) {
568        self.sack = None;
569    }
570
571    /// Get the bulk count.
572    #[inline]
573    pub fn bulk(&self) -> u64 {
574        self.bulk
575    }
576
577    /// Set the bulk count.
578    #[inline]
579    pub fn set_bulk(&mut self, bulk: u64) {
580        self.bulk = bulk;
581    }
582}
583
584impl std::fmt::Debug for Traverser {
585    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
586        f.debug_struct("Traverser")
587            .field("value", &self.value)
588            .field("path", &self.path)
589            .field("loops", &self.loops)
590            .field("sack", &self.sack.is_some())
591            .field("bulk", &self.bulk)
592            .finish()
593    }
594}
595
596// -----------------------------------------------------------------------------
597// TraversalSource - source information for bound traversals
598// -----------------------------------------------------------------------------
599
600/// Source information for bound traversals.
601///
602/// This enum describes where a traversal starts - from all vertices,
603/// specific vertices, all edges, specific edges, or injected values.
604#[derive(Clone, Debug)]
605pub enum TraversalSource {
606    /// Start from all vertices
607    AllVertices,
608    /// Start from specific vertex IDs
609    Vertices(Vec<VertexId>),
610    /// Start from all edges
611    AllEdges,
612    /// Start from specific edge IDs
613    Edges(Vec<EdgeId>),
614    /// Inject arbitrary values
615    Inject(Vec<Value>),
616    /// Start from vertices produced by a full-text search, each carrying its
617    /// BM25 relevance score in the traverser's sack as `f32`.
618    ///
619    /// Used by `Graph::search_text` and `Graph::search_text_query` to seed a
620    /// traversal with scored results without leaking the index handle into
621    /// the executor. Downstream steps can read the score via
622    /// `Traverser::get_sack::<f32>()` (or via `__::text_score()` once the
623    /// anonymous-traversal helper lands).
624    #[cfg(feature = "full-text")]
625    VerticesWithTextScore(Vec<(VertexId, f32)>),
626    /// Start from edges produced by a full-text search, each carrying its
627    /// BM25 relevance score in the traverser's sack as `f32`. Mirrors
628    /// [`Self::VerticesWithTextScore`] but for edge text indexes.
629    ///
630    /// Used by `Graph::search_text_e` and `Graph::search_text_query_e`.
631    #[cfg(feature = "full-text")]
632    EdgesWithTextScore(Vec<(EdgeId, f32)>),
633}
634
635// -----------------------------------------------------------------------------
636// Tests
637// -----------------------------------------------------------------------------
638
639#[cfg(test)]
640mod tests {
641    use super::*;
642
643    mod path_value_tests {
644        use super::*;
645
646        #[test]
647        fn from_value_vertex() {
648            let v = Value::Vertex(VertexId(42));
649            let pv = PathValue::from(&v);
650            assert_eq!(pv, PathValue::Vertex(VertexId(42)));
651        }
652
653        #[test]
654        fn from_value_edge() {
655            let v = Value::Edge(EdgeId(99));
656            let pv = PathValue::from(&v);
657            assert_eq!(pv, PathValue::Edge(EdgeId(99)));
658        }
659
660        #[test]
661        fn from_value_property() {
662            let v = Value::Int(42);
663            let pv = PathValue::from(&v);
664            assert_eq!(pv, PathValue::Property(Value::Int(42)));
665
666            let v2 = Value::String("hello".to_string());
667            let pv2 = PathValue::from(&v2);
668            assert_eq!(pv2, PathValue::Property(Value::String("hello".to_string())));
669        }
670
671        #[test]
672        fn is_vertex() {
673            assert!(PathValue::Vertex(VertexId(1)).is_vertex());
674            assert!(!PathValue::Edge(EdgeId(1)).is_vertex());
675            assert!(!PathValue::Property(Value::Int(1)).is_vertex());
676        }
677
678        #[test]
679        fn is_edge() {
680            assert!(PathValue::Edge(EdgeId(1)).is_edge());
681            assert!(!PathValue::Vertex(VertexId(1)).is_edge());
682            assert!(!PathValue::Property(Value::Int(1)).is_edge());
683        }
684
685        #[test]
686        fn as_vertex_id() {
687            assert_eq!(
688                PathValue::Vertex(VertexId(42)).as_vertex_id(),
689                Some(VertexId(42))
690            );
691            assert_eq!(PathValue::Edge(EdgeId(42)).as_vertex_id(), None);
692            assert_eq!(PathValue::Property(Value::Int(42)).as_vertex_id(), None);
693        }
694
695        #[test]
696        fn as_edge_id() {
697            assert_eq!(PathValue::Edge(EdgeId(99)).as_edge_id(), Some(EdgeId(99)));
698            assert_eq!(PathValue::Vertex(VertexId(99)).as_edge_id(), None);
699            assert_eq!(PathValue::Property(Value::Int(99)).as_edge_id(), None);
700        }
701
702        #[test]
703        fn to_value() {
704            assert_eq!(
705                PathValue::Vertex(VertexId(1)).to_value(),
706                Value::Vertex(VertexId(1))
707            );
708            assert_eq!(
709                PathValue::Edge(EdgeId(2)).to_value(),
710                Value::Edge(EdgeId(2))
711            );
712            assert_eq!(
713                PathValue::Property(Value::String("test".to_string())).to_value(),
714                Value::String("test".to_string())
715            );
716        }
717    }
718
719    mod path_tests {
720        use super::*;
721
722        #[test]
723        fn new_path_is_empty() {
724            let path = Path::new();
725            assert!(path.is_empty());
726            assert_eq!(path.len(), 0);
727        }
728
729        #[test]
730        fn push_adds_elements() {
731            let mut path = Path::new();
732            path.push(PathValue::Vertex(VertexId(1)), &[]);
733            assert_eq!(path.len(), 1);
734            assert!(!path.is_empty());
735
736            path.push(PathValue::Edge(EdgeId(1)), &[]);
737            assert_eq!(path.len(), 2);
738
739            path.push(PathValue::Vertex(VertexId(2)), &[]);
740            assert_eq!(path.len(), 3);
741        }
742
743        #[test]
744        fn push_with_labels() {
745            let mut path = Path::new();
746            path.push(
747                PathValue::Vertex(VertexId(1)),
748                &["start".to_string(), "source".to_string()],
749            );
750            path.push(PathValue::Vertex(VertexId(2)), &["end".to_string()]);
751
752            assert!(path.has_label("start"));
753            assert!(path.has_label("source"));
754            assert!(path.has_label("end"));
755            assert!(!path.has_label("middle"));
756        }
757
758        #[test]
759        fn get_by_label() {
760            let mut path = Path::new();
761            path.push(PathValue::Vertex(VertexId(1)), &["a".to_string()]);
762            path.push(PathValue::Vertex(VertexId(2)), &["b".to_string()]);
763            path.push(PathValue::Vertex(VertexId(3)), &["a".to_string()]); // Duplicate label
764
765            let a_values = path.get("a").unwrap();
766            assert_eq!(a_values.len(), 2);
767            assert_eq!(a_values[0].as_vertex_id(), Some(VertexId(1)));
768            assert_eq!(a_values[1].as_vertex_id(), Some(VertexId(3)));
769
770            let b_values = path.get("b").unwrap();
771            assert_eq!(b_values.len(), 1);
772            assert_eq!(b_values[0].as_vertex_id(), Some(VertexId(2)));
773
774            assert!(path.get("nonexistent").is_none());
775        }
776
777        #[test]
778        fn objects_iterator() {
779            let mut path = Path::new();
780            path.push(PathValue::Vertex(VertexId(1)), &[]);
781            path.push(PathValue::Edge(EdgeId(2)), &[]);
782            path.push(PathValue::Vertex(VertexId(3)), &[]);
783
784            let objects: Vec<_> = path.objects().collect();
785            assert_eq!(objects.len(), 3);
786            assert_eq!(objects[0], &PathValue::Vertex(VertexId(1)));
787            assert_eq!(objects[1], &PathValue::Edge(EdgeId(2)));
788            assert_eq!(objects[2], &PathValue::Vertex(VertexId(3)));
789        }
790
791        #[test]
792        fn contains_vertex() {
793            let mut path = Path::new();
794            path.push(PathValue::Vertex(VertexId(1)), &[]);
795            path.push(PathValue::Edge(EdgeId(2)), &[]);
796            path.push(PathValue::Vertex(VertexId(3)), &[]);
797
798            assert!(path.contains_vertex(VertexId(1)));
799            assert!(path.contains_vertex(VertexId(3)));
800            assert!(!path.contains_vertex(VertexId(2)));
801            assert!(!path.contains_vertex(VertexId(99)));
802        }
803
804        #[test]
805        fn contains_edge() {
806            let mut path = Path::new();
807            path.push(PathValue::Vertex(VertexId(1)), &[]);
808            path.push(PathValue::Edge(EdgeId(2)), &[]);
809            path.push(PathValue::Vertex(VertexId(3)), &[]);
810
811            assert!(path.contains_edge(EdgeId(2)));
812            assert!(!path.contains_edge(EdgeId(1)));
813            assert!(!path.contains_edge(EdgeId(99)));
814        }
815
816        #[test]
817        fn first_and_last() {
818            let mut path = Path::new();
819            assert!(path.first().is_none());
820            assert!(path.last().is_none());
821
822            path.push(PathValue::Vertex(VertexId(1)), &[]);
823            assert_eq!(path.first(), Some(&PathValue::Vertex(VertexId(1))));
824            assert_eq!(path.last(), Some(&PathValue::Vertex(VertexId(1))));
825
826            path.push(PathValue::Vertex(VertexId(2)), &[]);
827            path.push(PathValue::Vertex(VertexId(3)), &[]);
828            assert_eq!(path.first(), Some(&PathValue::Vertex(VertexId(1))));
829            assert_eq!(path.last(), Some(&PathValue::Vertex(VertexId(3))));
830        }
831
832        #[test]
833        fn to_list() {
834            let mut path = Path::new();
835            path.push(PathValue::Vertex(VertexId(1)), &[]);
836            path.push(PathValue::Edge(EdgeId(2)), &[]);
837            path.push(PathValue::Property(Value::Int(42)), &[]);
838
839            let list = path.to_list();
840            assert_eq!(list.len(), 3);
841            assert_eq!(list[0], Value::Vertex(VertexId(1)));
842            assert_eq!(list[1], Value::Edge(EdgeId(2)));
843            assert_eq!(list[2], Value::Int(42));
844        }
845
846        #[test]
847        fn clone_preserves_data() {
848            let mut path = Path::new();
849            path.push(PathValue::Vertex(VertexId(1)), &["start".to_string()]);
850            path.push(PathValue::Vertex(VertexId(2)), &["end".to_string()]);
851
852            let cloned = path.clone();
853            assert_eq!(cloned.len(), 2);
854            assert!(cloned.has_label("start"));
855            assert!(cloned.has_label("end"));
856            assert!(cloned.contains_vertex(VertexId(1)));
857            assert!(cloned.contains_vertex(VertexId(2)));
858        }
859    }
860
861    mod traverser_tests {
862        use super::*;
863
864        #[test]
865        fn new_creates_traverser_with_value() {
866            let t = Traverser::new(Value::Int(42));
867            assert_eq!(t.value, Value::Int(42));
868            assert!(t.path.is_empty());
869            assert_eq!(t.loops, 0);
870            assert!(t.sack.is_none());
871            assert_eq!(t.bulk, 1);
872        }
873
874        #[test]
875        fn new_with_into_value() {
876            // Test with types that implement Into<Value>
877            let t1 = Traverser::new(42i64);
878            assert_eq!(t1.value, Value::Int(42));
879
880            let t2 = Traverser::new("hello");
881            assert_eq!(t2.value, Value::String("hello".to_string()));
882
883            let t3 = Traverser::new(true);
884            assert_eq!(t3.value, Value::Bool(true));
885        }
886
887        #[test]
888        fn from_vertex_creates_vertex_traverser() {
889            let t = Traverser::from_vertex(VertexId(123));
890            assert_eq!(t.value, Value::Vertex(VertexId(123)));
891            assert_eq!(t.as_vertex_id(), Some(VertexId(123)));
892            assert!(t.is_vertex());
893            assert!(!t.is_edge());
894        }
895
896        #[test]
897        fn from_edge_creates_edge_traverser() {
898            let t = Traverser::from_edge(EdgeId(456));
899            assert_eq!(t.value, Value::Edge(EdgeId(456)));
900            assert_eq!(t.as_edge_id(), Some(EdgeId(456)));
901            assert!(t.is_edge());
902            assert!(!t.is_vertex());
903        }
904
905        #[test]
906        fn split_preserves_path_and_metadata() {
907            let mut t = Traverser::from_vertex(VertexId(1));
908            t.extend_path_labeled("start");
909            t.loops = 5;
910            t.bulk = 10;
911
912            let t2 = t.split(Value::Vertex(VertexId(2)));
913
914            // New value
915            assert_eq!(t2.value, Value::Vertex(VertexId(2)));
916
917            // Preserved metadata
918            assert_eq!(t2.path.len(), t.path.len());
919            assert!(t2.path.has_label("start"));
920            assert_eq!(t2.loops, 5);
921            assert_eq!(t2.bulk, 10);
922        }
923
924        #[test]
925        fn with_value_replaces_value() {
926            let t = Traverser::from_vertex(VertexId(1));
927            let t2 = t.with_value(Value::Int(42));
928
929            assert_eq!(t2.value, Value::Int(42));
930        }
931
932        #[test]
933        fn inc_loops_increments() {
934            let mut t = Traverser::new(Value::Null);
935            assert_eq!(t.loops, 0);
936
937            t.inc_loops();
938            assert_eq!(t.loops, 1);
939
940            t.inc_loops();
941            assert_eq!(t.loops, 2);
942        }
943
944        #[test]
945        fn loops_returns_loop_count() {
946            let mut t = Traverser::new(Value::Null);
947            assert_eq!(t.loops(), 0);
948
949            t.inc_loops();
950            assert_eq!(t.loops(), 1);
951
952            t.inc_loops();
953            t.inc_loops();
954            assert_eq!(t.loops(), 3);
955        }
956
957        #[test]
958        fn split_preserves_loop_count() {
959            let mut t = Traverser::from_vertex(VertexId(1));
960            t.inc_loops();
961            t.inc_loops();
962            t.inc_loops();
963            assert_eq!(t.loops(), 3);
964
965            let t2 = t.split(Value::Vertex(VertexId(2)));
966            assert_eq!(t2.loops(), 3);
967        }
968
969        #[test]
970        fn extend_path_adds_to_path() {
971            let mut t = Traverser::from_vertex(VertexId(1));
972            assert!(t.path.is_empty());
973
974            t.extend_path_labeled("a");
975            assert_eq!(t.path.len(), 1);
976            assert!(t.path.has_label("a"));
977
978            t.value = Value::Vertex(VertexId(2));
979            t.extend_path(&["b".to_string(), "c".to_string()]);
980            assert_eq!(t.path.len(), 2);
981            assert!(t.path.has_label("b"));
982            assert!(t.path.has_label("c"));
983        }
984
985        #[test]
986        fn as_vertex_id_returns_vertex_id() {
987            let t = Traverser::from_vertex(VertexId(42));
988            assert_eq!(t.as_vertex_id(), Some(VertexId(42)));
989
990            let t2 = Traverser::from_edge(EdgeId(42));
991            assert_eq!(t2.as_vertex_id(), None);
992
993            let t3 = Traverser::new(Value::Int(42));
994            assert_eq!(t3.as_vertex_id(), None);
995        }
996
997        #[test]
998        fn as_edge_id_returns_edge_id() {
999            let t = Traverser::from_edge(EdgeId(99));
1000            assert_eq!(t.as_edge_id(), Some(EdgeId(99)));
1001
1002            let t2 = Traverser::from_vertex(VertexId(99));
1003            assert_eq!(t2.as_edge_id(), None);
1004
1005            let t3 = Traverser::new(Value::Int(99));
1006            assert_eq!(t3.as_edge_id(), None);
1007        }
1008
1009        #[test]
1010        fn clone_works_correctly() {
1011            let mut t = Traverser::from_vertex(VertexId(1));
1012            t.extend_path_labeled("start");
1013            t.loops = 3;
1014            t.bulk = 5;
1015
1016            let cloned = t.clone();
1017
1018            assert_eq!(cloned.value, t.value);
1019            assert_eq!(cloned.path.len(), t.path.len());
1020            assert_eq!(cloned.loops, t.loops);
1021            assert_eq!(cloned.bulk, t.bulk);
1022        }
1023
1024        #[test]
1025        fn sack_operations() {
1026            let mut t = Traverser::new(Value::Null);
1027
1028            // Initially no sack
1029            assert!(t.get_sack::<i32>().is_none());
1030
1031            // Set sack
1032            t.set_sack(42i32);
1033            assert_eq!(t.get_sack::<i32>(), Some(&42));
1034
1035            // Wrong type returns None
1036            assert!(t.get_sack::<String>().is_none());
1037
1038            // Clear sack
1039            t.clear_sack();
1040            assert!(t.get_sack::<i32>().is_none());
1041        }
1042
1043        #[test]
1044        fn bulk_operations() {
1045            let mut t = Traverser::new(Value::Null);
1046            assert_eq!(t.bulk(), 1);
1047
1048            t.set_bulk(100);
1049            assert_eq!(t.bulk(), 100);
1050        }
1051
1052        #[test]
1053        fn debug_output() {
1054            let t = Traverser::from_vertex(VertexId(1));
1055            let debug_str = format!("{:?}", t);
1056            assert!(debug_str.contains("Traverser"));
1057            assert!(debug_str.contains("value"));
1058            assert!(debug_str.contains("path"));
1059        }
1060    }
1061
1062    mod clone_sack_tests {
1063        use super::*;
1064
1065        #[test]
1066        fn clone_box_works() {
1067            let boxed: Box<dyn CloneSack> = box_sack(42i32);
1068            let cloned = boxed.clone_box();
1069            assert_eq!(cloned.as_any().downcast_ref::<i32>(), Some(&42));
1070        }
1071
1072        #[test]
1073        fn clone_trait_impl_works() {
1074            let boxed: Box<dyn CloneSack> = box_sack("hello".to_string());
1075            let cloned = boxed.clone();
1076            assert_eq!(
1077                cloned.as_any().downcast_ref::<String>(),
1078                Some(&"hello".to_string())
1079            );
1080        }
1081
1082        #[test]
1083        fn sack_preserves_on_split() {
1084            let mut t = Traverser::new(Value::Int(1));
1085            t.set_sack(vec![1, 2, 3]);
1086
1087            let t2 = t.split(Value::Int(2));
1088
1089            // Sack should be cloned
1090            assert_eq!(t2.get_sack::<Vec<i32>>(), Some(&vec![1, 2, 3]));
1091        }
1092    }
1093
1094    mod traversal_source_tests {
1095        use super::*;
1096
1097        #[test]
1098        fn all_vertices_source() {
1099            let source = TraversalSource::AllVertices;
1100            assert!(matches!(source, TraversalSource::AllVertices));
1101        }
1102
1103        #[test]
1104        fn specific_vertices_source() {
1105            let source = TraversalSource::Vertices(vec![VertexId(1), VertexId(2)]);
1106            match source {
1107                TraversalSource::Vertices(ids) => {
1108                    assert_eq!(ids.len(), 2);
1109                    assert_eq!(ids[0], VertexId(1));
1110                    assert_eq!(ids[1], VertexId(2));
1111                }
1112                _ => panic!("Expected Vertices variant"),
1113            }
1114        }
1115
1116        #[test]
1117        fn all_edges_source() {
1118            let source = TraversalSource::AllEdges;
1119            assert!(matches!(source, TraversalSource::AllEdges));
1120        }
1121
1122        #[test]
1123        fn specific_edges_source() {
1124            let source = TraversalSource::Edges(vec![EdgeId(10), EdgeId(20)]);
1125            match source {
1126                TraversalSource::Edges(ids) => {
1127                    assert_eq!(ids.len(), 2);
1128                    assert_eq!(ids[0], EdgeId(10));
1129                    assert_eq!(ids[1], EdgeId(20));
1130                }
1131                _ => panic!("Expected Edges variant"),
1132            }
1133        }
1134
1135        #[test]
1136        fn inject_source() {
1137            let source =
1138                TraversalSource::Inject(vec![Value::Int(1), Value::String("test".to_string())]);
1139            match source {
1140                TraversalSource::Inject(values) => {
1141                    assert_eq!(values.len(), 2);
1142                    assert_eq!(values[0], Value::Int(1));
1143                    assert_eq!(values[1], Value::String("test".to_string()));
1144                }
1145                _ => panic!("Expected Inject variant"),
1146            }
1147        }
1148
1149        #[test]
1150        fn source_is_clonable() {
1151            let source1 = TraversalSource::AllVertices;
1152            let source2 = TraversalSource::Vertices(vec![VertexId(1)]);
1153            let source3 = TraversalSource::Inject(vec![Value::Int(42)]);
1154
1155            let _ = source1.clone();
1156            let _ = source2.clone();
1157            let _ = source3.clone();
1158        }
1159    }
1160}