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}