Skip to main content

flusso_query/handles/
nested.rs

1//! Handles for `nested` arrays of objects: [`Nested`] (lifting element queries
2//! into the enclosing scope) and [`NestedProjection`] (shaping the returned
3//! array via `inner_hits`).
4
5use std::marker::PhantomData;
6
7use serde_json::{Map, Value};
8
9use super::{Sort, exists_q, match_all_value};
10use crate::query::{AsQuery, Query, Root};
11
12/// `{ "nested": { "path": "<path>", "query": <query> } }`.
13fn nested_value(path: &str, query: Value) -> Value {
14    let mut body = Map::new();
15    body.insert("path".to_string(), Value::String(path.to_string()));
16    body.insert("query".to_string(), query);
17    let mut outer = Map::new();
18    outer.insert("nested".to_string(), Value::Object(body));
19    Value::Object(outer)
20}
21
22/// `{ "bool": { "<clause>": [ … ] } }`.
23fn bool_value(clause: &str, items: Vec<Value>) -> Value {
24    let mut body = Map::new();
25    body.insert(clause.to_string(), Value::Array(items));
26    let mut outer = Map::new();
27    outer.insert("bool".to_string(), Value::Object(body));
28    Value::Object(outer)
29}
30
31/// A `nested` array of objects. `E` is the **enclosing** scope (where queries
32/// over this array land — `Root` at the top level, the parent element type when
33/// deeper); `C` is the **child** scope (the element type). Lifting a child query
34/// (`Query<C>`) through `any`/`all` produces a `Query<E>`.
35#[derive(Debug, Clone)]
36pub struct Nested<E = Root, C = serde_json::Value> {
37    path: String,
38    _marker: PhantomData<fn() -> (E, C)>,
39}
40
41impl<E, C> Nested<E, C> {
42    /// Build a handle for the nested array at `path`.
43    pub fn at(path: impl Into<String>) -> Self {
44        Self {
45            path: path.into(),
46            _marker: PhantomData,
47        }
48    }
49
50    /// Parents with **at least one** element matching `query`.
51    pub fn any(&self, query: impl AsQuery<C>) -> Query<E> {
52        let inner = query
53            .into_query()
54            .map_or_else(match_all_value, |q| q.to_value());
55        Query::leaf(nested_value(&self.path, inner))
56    }
57
58    /// Parents where **every** element matches `query` ("no element fails it").
59    pub fn all(&self, query: impl AsQuery<C>) -> Query<E> {
60        let inner = query
61            .into_query()
62            .map_or_else(match_all_value, |q| q.to_value());
63        let fails = bool_value("must_not", vec![inner]);
64        let nested = nested_value(&self.path, fails);
65        Query::leaf(bool_value("must_not", vec![nested]))
66    }
67
68    /// The nested array has at least one element.
69    pub fn exists(&self) -> Query<E> {
70        exists_q(&self.path)
71    }
72
73    /// Shape the **returned** array: keep elements matching `query` (with the
74    /// builder's sort/size). Pass to [`crate::Search::filter_nested`].
75    pub fn matching(&self, query: impl AsQuery<C>) -> NestedProjection {
76        NestedProjection {
77            path: self.path.clone(),
78            query: query.into_query().map(|q| q.to_value()),
79            sort: Vec::new(),
80            size: None,
81            from: None,
82        }
83    }
84
85    /// Like [`matching`](Self::matching) with no predicate — every element.
86    pub fn project(&self) -> NestedProjection {
87        NestedProjection {
88            path: self.path.clone(),
89            query: None,
90            sort: Vec::new(),
91            size: None,
92            from: None,
93        }
94    }
95}
96
97/// A request to shape one nested array in the results (via `inner_hits`).
98#[derive(Debug, Clone)]
99pub struct NestedProjection {
100    path: String,
101    query: Option<Value>,
102    sort: Vec<Sort>,
103    size: Option<u64>,
104    from: Option<u64>,
105}
106
107impl NestedProjection {
108    /// Order the returned elements.
109    #[must_use]
110    pub fn sort(mut self, sort: Sort) -> Self {
111        self.sort.push(sort);
112        self
113    }
114
115    /// Cap how many elements are returned per parent.
116    #[must_use]
117    pub fn size(mut self, size: u64) -> Self {
118        self.size = Some(size);
119        self
120    }
121
122    /// Offset within each parent's matching elements.
123    #[must_use]
124    pub fn from(mut self, from: u64) -> Self {
125        self.from = Some(from);
126        self
127    }
128
129    pub(crate) fn path(&self) -> &str {
130        &self.path
131    }
132
133    /// The `{ "nested": { path, query, inner_hits } }` clause (inner_hits named
134    /// after the path, for retrieval).
135    pub(crate) fn to_value(&self) -> Value {
136        let query = self.query.clone().unwrap_or_else(match_all_value);
137        let mut inner_hits = Map::new();
138        inner_hits.insert("name".to_string(), Value::String(self.path.clone()));
139        if let Some(size) = self.size {
140            inner_hits.insert("size".to_string(), Value::from(size));
141        }
142        if let Some(from) = self.from {
143            inner_hits.insert("from".to_string(), Value::from(from));
144        }
145        if !self.sort.is_empty() {
146            let keys = self.sort.iter().map(Sort::to_value).collect();
147            inner_hits.insert("sort".to_string(), Value::Array(keys));
148        }
149        let mut nested = Map::new();
150        nested.insert("path".to_string(), Value::String(self.path.clone()));
151        nested.insert("query".to_string(), query);
152        nested.insert("inner_hits".to_string(), Value::Object(inner_hits));
153        let mut outer = Map::new();
154        outer.insert("nested".to_string(), Value::Object(nested));
155        Value::Object(outer)
156    }
157}