cognite/dto/
filter.rs

1use serde::{Deserialize, Serialize};
2use serde_with::skip_serializing_none;
3
4use crate::models::SourceReference;
5
6mod query_value;
7pub use query_value::*;
8mod aggregate;
9pub use aggregate::*;
10
11#[derive(Serialize, Deserialize, Clone, Debug)]
12#[serde(rename_all = "camelCase", rename_all_fields = "camelCase")]
13#[skip_serializing_none]
14/// Advanced filter. The `filter` module contains useful tools for
15/// building filters.
16///
17/// # Example
18///
19/// This creates a filter matching nodes where "prop" is 15 and
20/// externalId is not test, or "prop" is between 1 (inclusive) and 5 (exclusive)
21///
22/// ```rust
23/// use cognite::filter::*;
24/// equals(["space", "view/1", "prop"], 15)
25///     .and(not(equals(["node", "externalId"], "test")))
26///     .or(range(["space", "view/1", "prop"], 1..5));
27/// ```
28pub enum AdvancedFilter {
29    /// Require the value of `property` to be equal to `value`
30    Equals {
31        /// Left hand side property.
32        property: Vec<String>,
33        /// Right hand side value.
34        value: QueryValue,
35    },
36    /// Require the value to be in the list of `values`
37    In {
38        /// Left hand side property.
39        property: Vec<String>,
40        /// Right hand side list of values.
41        values: QueryValue,
42    },
43    /// Require the value to be greater than `gt`, greater than or equal to `gte`,
44    /// less than `lt` and less than or equal to `lte`
45    Range {
46        /// Left hand side property.
47        property: Vec<String>,
48        /// Greater than or equal to
49        #[serde(skip_serializing_if = "Option::is_none")]
50        gte: Option<QueryValue>,
51        /// Greater than
52        #[serde(skip_serializing_if = "Option::is_none")]
53        gt: Option<QueryValue>,
54        /// Less than or equal to
55        #[serde(skip_serializing_if = "Option::is_none")]
56        lte: Option<QueryValue>,
57        /// Less than
58        #[serde(skip_serializing_if = "Option::is_none")]
59        lt: Option<QueryValue>,
60    },
61    /// Require the value to be text and start with `value`
62    Prefix {
63        /// Left hand side property.
64        property: Vec<String>,
65        /// Prefix value
66        value: String,
67    },
68    /// Require this property to exist.
69    Exists {
70        /// Property that must exist.
71        property: Vec<String>,
72    },
73    /// Matches items where the property contains one or more of the given values.
74    /// This filter can only be applied to multivalued properties.
75    ContainsAny {
76        /// Multivalued property.
77        property: Vec<String>,
78        /// List of values.
79        values: QueryValue,
80    },
81    /// Matches items where the property contains all the given values.
82    /// This filter can only be applied to multivalued properties.
83    ContainsAll {
84        /// Multivalued property.
85        property: Vec<String>,
86        /// List of values.
87        values: QueryValue,
88    },
89    /// An open filter that matches anything.
90    MatchAll {},
91    /// Use nested to apply the properties of the direct relation as the filter.
92    /// `scope` specifies the direct relation property you want use as the filtering property.
93    Nested {
94        /// Direct relation property to query through.
95        scope: Vec<String>,
96        /// Filter to apply to nested property.
97        filter: Box<AdvancedFilter>,
98    },
99    /// Matches items where the range made up of the two properties overlap with the provided range.
100    Overlaps {
101        /// Start property reference.
102        start_property: Vec<String>,
103        /// End property reference.
104        end_property: Vec<String>,
105        /// Greater than or equal to
106        #[serde(skip_serializing_if = "Option::is_none")]
107        gte: Option<QueryValue>,
108        /// Greater than
109        #[serde(skip_serializing_if = "Option::is_none")]
110        gt: Option<QueryValue>,
111        /// Less than or equal to
112        #[serde(skip_serializing_if = "Option::is_none")]
113        lte: Option<QueryValue>,
114        /// Less than
115        #[serde(skip_serializing_if = "Option::is_none")]
116        lt: Option<QueryValue>,
117    },
118    /// Require items to have data in the referenced views, or containers.
119    HasData(Vec<SourceReference>),
120    /// Require all these filters to match.
121    And(Vec<AdvancedFilter>),
122    /// Require at least one of these filters to match.
123    Or(Vec<AdvancedFilter>),
124    /// Require this filter _not_ to match.
125    Not(Box<AdvancedFilter>),
126}
127
128impl Default for AdvancedFilter {
129    fn default() -> Self {
130        Self::MatchAll {}
131    }
132}
133
134/// Trait for values that can be converted into a property identifier.
135pub trait PropertyIdentifier {
136    /// Convert the value into an identifier.
137    fn into_identifier(self) -> Vec<String>;
138}
139
140impl PropertyIdentifier for Vec<String> {
141    fn into_identifier(self) -> Vec<String> {
142        self
143    }
144}
145
146impl PropertyIdentifier for &[String] {
147    fn into_identifier(self) -> Vec<String> {
148        self.to_owned()
149    }
150}
151
152impl PropertyIdentifier for &[&str] {
153    fn into_identifier(self) -> Vec<String> {
154        self.iter().map(|&s| s.to_owned()).collect()
155    }
156}
157
158impl<const N: usize> PropertyIdentifier for &[String; N] {
159    fn into_identifier(self) -> Vec<String> {
160        self.to_vec()
161    }
162}
163
164impl<const N: usize> PropertyIdentifier for &[&str; N] {
165    fn into_identifier(self) -> Vec<String> {
166        self.iter().map(|&s| s.to_owned()).collect()
167    }
168}
169
170impl<const N: usize> PropertyIdentifier for [String; N] {
171    fn into_identifier(self) -> Vec<String> {
172        self.to_vec()
173    }
174}
175
176impl<const N: usize> PropertyIdentifier for [&str; N] {
177    fn into_identifier(self) -> Vec<String> {
178        self.iter().map(|&s| s.to_owned()).collect()
179    }
180}
181
182/// Start or end of a range.
183pub enum RangeItem<T> {
184    /// Inclusive end point.
185    Inclusive(T),
186    /// Exclusive end point.
187    Exclusive(T),
188    /// Unbounded end point.
189    Empty,
190}
191
192impl<T> RangeItem<T> {
193    /// Map the inner value.
194    pub fn map<R>(self, map: impl FnOnce(T) -> R) -> RangeItem<R> {
195        match self {
196            RangeItem::Inclusive(r) => RangeItem::Inclusive(map(r)),
197            RangeItem::Exclusive(r) => RangeItem::Exclusive(map(r)),
198            RangeItem::Empty => RangeItem::Empty,
199        }
200    }
201}
202
203/// Trait for types that can be converted into a range for a DMS query.
204pub trait IntoQueryRange {
205    /// Create a query range.
206    fn into_query_range(self) -> (RangeItem<QueryValue>, RangeItem<QueryValue>);
207}
208
209impl<T> IntoQueryRange for std::ops::Range<T>
210where
211    T: Into<QueryValue>,
212{
213    fn into_query_range(self) -> (RangeItem<QueryValue>, RangeItem<QueryValue>) {
214        (
215            RangeItem::Inclusive(self.start.into()),
216            RangeItem::Exclusive(self.end.into()),
217        )
218    }
219}
220
221impl<T> IntoQueryRange for std::ops::RangeFrom<T>
222where
223    T: Into<QueryValue>,
224{
225    fn into_query_range(self) -> (RangeItem<QueryValue>, RangeItem<QueryValue>) {
226        (RangeItem::Inclusive(self.start.into()), RangeItem::Empty)
227    }
228}
229
230impl IntoQueryRange for std::ops::RangeFull {
231    fn into_query_range(self) -> (RangeItem<QueryValue>, RangeItem<QueryValue>) {
232        (RangeItem::Empty, RangeItem::Empty)
233    }
234}
235
236impl<T> IntoQueryRange for std::ops::RangeInclusive<T>
237where
238    T: Into<QueryValue> + Clone,
239{
240    fn into_query_range(self) -> (RangeItem<QueryValue>, RangeItem<QueryValue>) {
241        (
242            RangeItem::Inclusive(self.start().clone().into()),
243            RangeItem::Inclusive(self.end().clone().into()),
244        )
245    }
246}
247
248impl<T> IntoQueryRange for std::ops::RangeTo<T>
249where
250    T: Into<QueryValue>,
251{
252    fn into_query_range(self) -> (RangeItem<QueryValue>, RangeItem<QueryValue>) {
253        (RangeItem::Empty, RangeItem::Exclusive(self.end.into()))
254    }
255}
256
257impl<T> IntoQueryRange for std::ops::RangeToInclusive<T>
258where
259    T: Into<QueryValue>,
260{
261    fn into_query_range(self) -> (RangeItem<QueryValue>, RangeItem<QueryValue>) {
262        (RangeItem::Empty, RangeItem::Exclusive(self.end.into()))
263    }
264}
265
266impl<T: Into<QueryValue>> IntoQueryRange for (T, T) {
267    fn into_query_range(self) -> (RangeItem<QueryValue>, RangeItem<QueryValue>) {
268        (
269            RangeItem::Inclusive(self.0.into()),
270            RangeItem::Exclusive(self.1.into()),
271        )
272    }
273}
274
275impl<T> IntoQueryRange for (RangeItem<T>, RangeItem<T>)
276where
277    T: Into<QueryValue>,
278{
279    fn into_query_range(self) -> (RangeItem<QueryValue>, RangeItem<QueryValue>) {
280        (self.0.map(Into::into), self.1.map(Into::into))
281    }
282}
283
284/// Filter builder methods.
285pub(crate) mod filter_methods {
286    use super::*;
287    /// Create an `Equals` filter. `property = value`
288    ///
289    /// # Arguments
290    ///
291    /// * `property` - Left hand side property.
292    /// * `value` - Right hand side value.
293    pub fn equals(
294        property: impl PropertyIdentifier,
295        value: impl Into<QueryValue>,
296    ) -> AdvancedFilter {
297        AdvancedFilter::Equals {
298            property: property.into_identifier(),
299            value: value.into(),
300        }
301    }
302    /// Create an `In` filter. `property IN values`
303    ///
304    /// # Arguments
305    ///
306    /// * `property` - Left hand side property.
307    /// * `value` - Right hand side list of values.
308    pub fn is_in(
309        property: impl PropertyIdentifier,
310        values: impl Into<QueryValue>,
311    ) -> AdvancedFilter {
312        AdvancedFilter::In {
313            property: property.into_identifier(),
314            values: values.into(),
315        }
316    }
317
318    /// Create a `Range` filter.
319    ///
320    /// # Arguments
321    ///
322    /// * `property` - Property to filter.
323    /// * `range` - Range to check.
324    ///
325    /// `IntoQueryRange` is implemented for `(QueryValue, QueryValue)`, which interprets it as
326    /// (inclusive, exclusive), as well as ranges in `std::ops`, such as `0..5`, `..`, `..=7`, etc.
327    ///
328    /// If you need fine control, use `(RangeItem<T>, RangeItem<T>)`
329    pub fn range(property: impl PropertyIdentifier, range: impl IntoQueryRange) -> AdvancedFilter {
330        let (start, end) = range.into_query_range();
331        let mut lte = None;
332        let mut gte = None;
333        let mut lt = None;
334        let mut gt = None;
335        match start {
336            RangeItem::Inclusive(i) => gte = Some(i),
337            RangeItem::Exclusive(i) => gt = Some(i),
338            RangeItem::Empty => (),
339        }
340        match end {
341            RangeItem::Inclusive(i) => lte = Some(i),
342            RangeItem::Exclusive(i) => lt = Some(i),
343            RangeItem::Empty => (),
344        }
345
346        AdvancedFilter::Range {
347            property: property.into_identifier(),
348            gte,
349            gt,
350            lte,
351            lt,
352        }
353    }
354
355    /// Create a `Prefix` filter, `property STARTS WITH value`
356    ///
357    /// # Arguments
358    ///
359    /// * `property` - Property to filter.
360    /// * `value` - Prefix string.
361    pub fn prefix(property: impl PropertyIdentifier, value: impl Into<String>) -> AdvancedFilter {
362        AdvancedFilter::Prefix {
363            property: property.into_identifier(),
364            value: value.into(),
365        }
366    }
367
368    /// Create an `Exists` filter.
369    ///
370    /// # Arguments
371    ///
372    /// * `property` - Property that must exist.
373    pub fn exists(property: impl PropertyIdentifier) -> AdvancedFilter {
374        AdvancedFilter::Exists {
375            property: property.into_identifier(),
376        }
377    }
378
379    /// Create a `ContainsAny` filter.
380    ///
381    /// # Arguments
382    ///
383    /// * `property` - Multivalued property reference.
384    /// * `values` - List of values.
385    pub fn contains_any(
386        property: impl PropertyIdentifier,
387        values: impl Into<QueryValue>,
388    ) -> AdvancedFilter {
389        AdvancedFilter::ContainsAny {
390            property: property.into_identifier(),
391            values: values.into(),
392        }
393    }
394
395    /// Create a `ContainsAll` filter.
396    ///
397    /// # Arguments
398    ///
399    /// * `property` - Multivalued property reference.
400    /// * `values` - List of values.
401    pub fn contains_all(
402        property: impl PropertyIdentifier,
403        values: impl Into<QueryValue>,
404    ) -> AdvancedFilter {
405        AdvancedFilter::ContainsAll {
406            property: property.into_identifier(),
407            values: values.into(),
408        }
409    }
410
411    /// Create an empty `MatchAll` filter.
412    pub fn match_all() -> AdvancedFilter {
413        AdvancedFilter::MatchAll {}
414    }
415
416    /// Create a nested filter.
417    ///
418    /// # Arguments
419    ///
420    /// * `scope` - Direct relation property to query through.
421    /// * `filter` - Filter to apply to referenced node.
422    pub fn nested(scope: impl PropertyIdentifier, filter: AdvancedFilter) -> AdvancedFilter {
423        AdvancedFilter::Nested {
424            scope: scope.into_identifier(),
425            filter: Box::new(filter),
426        }
427    }
428
429    /// Construct an `Overlaps` filter.
430    ///
431    /// # Arguments
432    ///
433    /// * `start_property` - Start property
434    /// * `end_property` - End property.
435    /// * `range` - Range to check for overlap with.
436    ///
437    /// `IntoQueryRange` is implemented for `(QueryValue, QueryValue)`, which interprets it as
438    /// (inclusive, exclusive), as well as ranges in `std::ops`, such as `0..5`, `..`, `..=7`, etc.
439    ///
440    /// If you need fine control, use `(RangeItem<T>, RangeItem<T>)`
441    pub fn overlaps(
442        start_property: impl PropertyIdentifier,
443        end_property: impl PropertyIdentifier,
444        range: impl IntoQueryRange,
445    ) -> AdvancedFilter {
446        let (start, end) = range.into_query_range();
447        let mut lte = None;
448        let mut gte = None;
449        let mut lt = None;
450        let mut gt = None;
451        match start {
452            RangeItem::Inclusive(i) => gte = Some(i),
453            RangeItem::Exclusive(i) => gt = Some(i),
454            RangeItem::Empty => (),
455        }
456        match end {
457            RangeItem::Inclusive(i) => lte = Some(i),
458            RangeItem::Exclusive(i) => lt = Some(i),
459            RangeItem::Empty => (),
460        }
461        AdvancedFilter::Overlaps {
462            start_property: start_property.into_identifier(),
463            end_property: end_property.into_identifier(),
464            gte,
465            gt,
466            lte,
467            lt,
468        }
469    }
470
471    /// Construct a `HasData` filter.
472    ///
473    /// # Arguments
474    ///
475    /// * `references` - List of sources that the results must have data in.
476    pub fn has_data(references: Vec<SourceReference>) -> AdvancedFilter {
477        AdvancedFilter::HasData(references)
478    }
479
480    /// Construct a `not` filter.
481    ///
482    /// # Arguments
483    ///
484    /// * `filter` - Filter to invert.
485    pub fn not(filter: AdvancedFilter) -> AdvancedFilter {
486        filter.not()
487    }
488
489    /// Construct an `and` filter from a vector of filters.
490    ///
491    /// # Arguments
492    ///
493    /// * `filters` - Filters to `and` together.
494    pub fn and(filters: Vec<AdvancedFilter>) -> AdvancedFilter {
495        AdvancedFilter::And(filters)
496    }
497
498    /// Construct an `or` filter from a vector of filters.
499    ///
500    /// # Arguments
501    ///
502    /// * `filters` - Filters to `or` together.
503    pub fn or(filters: Vec<AdvancedFilter>) -> AdvancedFilter {
504        AdvancedFilter::Or(filters)
505    }
506}
507
508impl AdvancedFilter {
509    #[allow(clippy::should_implement_trait)]
510    /// Construct a `not` filter.
511    ///
512    /// # Arguments
513    ///
514    /// * `filter` - Filter to invert.
515    pub fn not(self) -> Self {
516        match self {
517            Self::Not(n) => *n,
518            _ => Self::Not(Box::new(self)),
519        }
520    }
521    /// Construct an `and` filter from this filter and another filter.
522    ///
523    /// # Arguments
524    ///
525    /// * `filter` - AND with this filter.
526    pub fn and(mut self, filter: AdvancedFilter) -> Self {
527        match &mut self {
528            Self::And(a) => {
529                a.push(filter);
530                self
531            }
532            _ => Self::And(vec![self, filter]),
533        }
534    }
535    /// Construct an `or` filter from this filter and another filter.
536    ///
537    /// # Arguments
538    ///
539    /// * `filter` - OR with this filter.
540    pub fn or(mut self, filter: AdvancedFilter) -> Self {
541        match &mut self {
542            Self::Or(a) => {
543                a.push(filter);
544                self
545            }
546            _ => Self::Or(vec![self, filter]),
547        }
548    }
549}