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    pub fn at(path: impl Into<String>) -> Self {
43        Self {
44            path: path.into(),
45            _marker: PhantomData,
46        }
47    }
48
49    /// Parents with **at least one** element matching `query`.
50    pub fn any(&self, query: impl AsQuery<C>) -> Query<E> {
51        let inner = query
52            .into_query()
53            .map_or_else(match_all_value, |q| q.to_value());
54        Query::leaf(nested_value(&self.path, inner))
55    }
56
57    /// Parents where **every** element matches `query` ("no element fails it").
58    pub fn all(&self, query: impl AsQuery<C>) -> Query<E> {
59        let inner = query
60            .into_query()
61            .map_or_else(match_all_value, |q| q.to_value());
62        let fails = bool_value("must_not", vec![inner]);
63        let nested = nested_value(&self.path, fails);
64        Query::leaf(bool_value("must_not", vec![nested]))
65    }
66
67    /// The nested array has at least one element.
68    pub fn exists(&self) -> Query<E> {
69        exists_q(&self.path)
70    }
71
72    /// Shape the **returned** array: keep elements matching `query` (with the
73    /// builder's sort/size). Pass to [`crate::Search::filter_nested`].
74    pub fn matching(&self, query: impl AsQuery<C>) -> NestedProjection {
75        NestedProjection {
76            path: self.path.clone(),
77            query: query.into_query().map(|q| q.to_value()),
78            sort: Vec::new(),
79            size: None,
80            from: None,
81        }
82    }
83
84    /// Like [`matching`](Self::matching) with no predicate — every element.
85    pub fn project(&self) -> NestedProjection {
86        NestedProjection {
87            path: self.path.clone(),
88            query: None,
89            sort: Vec::new(),
90            size: None,
91            from: None,
92        }
93    }
94}
95
96/// A request to shape one nested array in the results (via `inner_hits`).
97#[derive(Debug, Clone)]
98pub struct NestedProjection {
99    path: String,
100    query: Option<Value>,
101    sort: Vec<Sort>,
102    size: Option<u64>,
103    from: Option<u64>,
104}
105
106impl NestedProjection {
107    /// Order the returned elements.
108    #[must_use]
109    pub fn sort(mut self, sort: Sort) -> Self {
110        self.sort.push(sort);
111        self
112    }
113
114    /// Cap how many elements are returned per parent.
115    #[must_use]
116    pub fn size(mut self, size: u64) -> Self {
117        self.size = Some(size);
118        self
119    }
120
121    /// Offset within each parent's matching elements.
122    #[must_use]
123    pub fn from(mut self, from: u64) -> Self {
124        self.from = Some(from);
125        self
126    }
127
128    pub(crate) fn path(&self) -> &str {
129        &self.path
130    }
131
132    /// The `{ "nested": { path, query, inner_hits } }` clause (inner_hits named
133    /// after the path, for retrieval).
134    pub(crate) fn to_value(&self) -> Value {
135        let query = self.query.clone().unwrap_or_else(match_all_value);
136        let mut inner_hits = Map::new();
137        inner_hits.insert("name".to_string(), Value::String(self.path.clone()));
138        if let Some(size) = self.size {
139            inner_hits.insert("size".to_string(), Value::from(size));
140        }
141        if let Some(from) = self.from {
142            inner_hits.insert("from".to_string(), Value::from(from));
143        }
144        if !self.sort.is_empty() {
145            let keys = self.sort.iter().map(Sort::to_value).collect();
146            inner_hits.insert("sort".to_string(), Value::Array(keys));
147        }
148        let mut nested = Map::new();
149        nested.insert("path".to_string(), Value::String(self.path.clone()));
150        nested.insert("query".to_string(), query);
151        nested.insert("inner_hits".to_string(), Value::Object(inner_hits));
152        let mut outer = Map::new();
153        outer.insert("nested".to_string(), Value::Object(nested));
154        Value::Object(outer)
155    }
156}