Skip to main content

flusso_query/
path.rs

1//! Document path metadata: where a scope sits relative to the index root.
2//!
3//! Every scope type (the [`Root`](crate::Root) marker and each `nested` element
4//! struct) implements [`FlussoDocument`](crate::FlussoDocument), which carries a
5//! `const PATH: &[Segment]` — the chain of container levels from the root down to
6//! that scope. A nesting-aware sort reads it to render the right `nested` clause.
7//!
8//! Only the **kinds** a path level can be are modeled: an [`Object`](SegmentKind::Object)
9//! (a group / to-one join — flattened, no query boundary) or a
10//! [`Nested`](SegmentKind::Nested) array (a real `nested` boundary). The derive
11//! translates the resolved mapping into these at codegen, so this crate needs no
12//! dependency on the schema layer.
13//!
14//! ```
15//! use flusso_query::{Segment, SegmentKind, nested_boundaries};
16//!
17//! // `orders.shipping.packages`: a nested array, an object hop, a nested array.
18//! let path = &[
19//!     Segment { name: "orders", kind: SegmentKind::Nested },
20//!     Segment { name: "shipping", kind: SegmentKind::Object },
21//!     Segment { name: "packages", kind: SegmentKind::Nested },
22//! ];
23//! assert_eq!(nested_boundaries(path), ["orders", "orders.shipping.packages"]);
24//! ```
25
26/// How one path level is stored — the only shapes a level can take.
27///
28/// Named `SegmentKind` to stay clear of the value-kind markers in
29/// [`kind`](crate::kind). Non-exhaustive: more container kinds may be added.
30#[non_exhaustive]
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub enum SegmentKind {
33    /// A group / to-one-join object: extends the dotted path but is *not* a
34    /// `nested` query boundary (it flattens into the enclosing scope).
35    Object,
36    /// A `nested` array: a real query/sort boundary that must be wrapped.
37    Nested,
38}
39
40/// One level of a document path — a field name plus how it's stored.
41#[derive(Debug, Clone, Copy)]
42pub struct Segment {
43    /// The field name at this level (a single path segment, not dotted).
44    pub name: &'static str,
45    /// Whether this level is a flattened object or a `nested` boundary.
46    pub kind: SegmentKind,
47}
48
49/// The `nested` boundaries along `path`: the cumulative dotted path of each
50/// [`Nested`](SegmentKind::Nested) level, outermost first.
51///
52/// Object levels extend the running path but contribute no boundary. A pure
53/// function of the path — identical for every document — so it lives here rather
54/// than on [`FlussoDocument`](crate::FlussoDocument). An empty result (a root or
55/// flattened-object field) means a plain, non-nested sort.
56#[must_use]
57pub fn nested_boundaries(path: &[Segment]) -> Vec<String> {
58    let mut running = String::new();
59    let mut boundaries = Vec::new();
60    for segment in path {
61        if !running.is_empty() {
62            running.push('.');
63        }
64        running.push_str(segment.name);
65        if segment.kind == SegmentKind::Nested {
66            boundaries.push(running.clone());
67        }
68    }
69    boundaries
70}
71
72#[cfg(test)]
73mod tests {
74    use super::{Segment, SegmentKind, nested_boundaries};
75
76    const OBJECT: SegmentKind = SegmentKind::Object;
77    const NESTED: SegmentKind = SegmentKind::Nested;
78
79    #[test]
80    fn root_has_no_boundaries() {
81        assert!(nested_boundaries(&[]).is_empty());
82    }
83
84    #[test]
85    fn a_flattened_object_adds_no_boundary() {
86        let path = &[Segment {
87            name: "account",
88            kind: OBJECT,
89        }];
90        assert!(nested_boundaries(path).is_empty());
91    }
92
93    #[test]
94    fn one_nested_level_yields_its_path() {
95        let path = &[Segment {
96            name: "orders",
97            kind: NESTED,
98        }];
99        assert_eq!(nested_boundaries(path), ["orders"]);
100    }
101
102    #[test]
103    fn an_object_hop_extends_the_path_without_a_boundary() {
104        // orders (nested) → shipping (object) → packages (nested)
105        let path = &[
106            Segment {
107                name: "orders",
108                kind: NESTED,
109            },
110            Segment {
111                name: "shipping",
112                kind: OBJECT,
113            },
114            Segment {
115                name: "packages",
116                kind: NESTED,
117            },
118        ];
119        assert_eq!(
120            nested_boundaries(path),
121            ["orders", "orders.shipping.packages"]
122        );
123    }
124}