vrl/value/kind/
comparison.rs

1use super::Kind;
2use crate::path::OwnedValuePath;
3
4impl Kind {
5    /// Returns `true` if all type states are valid.
6    ///
7    /// That is, this method only returns `true` if the object matches _all_ of the known types.
8    #[must_use]
9    pub const fn is_any(&self) -> bool {
10        self.contains_bytes()
11            && self.contains_integer()
12            && self.contains_float()
13            && self.contains_boolean()
14            && self.contains_timestamp()
15            && self.contains_regex()
16            && self.contains_null()
17            && self.contains_undefined()
18            && self.contains_array()
19            && self.contains_object()
20    }
21
22    /// Returns `true` if the JSON type states are valid.
23    #[must_use]
24    pub const fn is_json(&self) -> bool {
25        self.contains_bytes()
26            && self.contains_integer()
27            && self.contains_float()
28            && self.contains_boolean()
29            && !self.contains_timestamp()
30            && !self.contains_regex()
31            && self.contains_null()
32            && self.contains_undefined()
33            && self.contains_array()
34            && self.contains_object()
35    }
36
37    /// Returns `true` if only collection type states are valid.
38    #[must_use]
39    pub const fn is_collection(&self) -> bool {
40        if !self.contains_object() && !self.contains_array() {
41            return false;
42        }
43
44        !self.contains_bytes()
45            && !self.contains_integer()
46            && !self.contains_float()
47            && !self.contains_boolean()
48            && !self.contains_timestamp()
49            && !self.contains_regex()
50            && !self.contains_null()
51            && !self.contains_undefined()
52    }
53
54    /// Returns `true` if the type is `bytes`.
55    #[must_use]
56    pub const fn is_bytes(&self) -> bool {
57        self.integer.is_none()
58            && self.float.is_none()
59            && self.boolean.is_none()
60            && self.timestamp.is_none()
61            && self.regex.is_none()
62            && self.null.is_none()
63            && self.undefined.is_none()
64            && self.array.is_none()
65            && self.object.is_none()
66    }
67
68    /// Returns `true` if the type is `integer`.
69    #[must_use]
70    pub const fn is_integer(&self) -> bool {
71        self.bytes.is_none()
72            && self.float.is_none()
73            && self.boolean.is_none()
74            && self.timestamp.is_none()
75            && self.regex.is_none()
76            && self.null.is_none()
77            && self.undefined.is_none()
78            && self.array.is_none()
79            && self.object.is_none()
80    }
81
82    /// Returns `true` if the type is `never`.
83    #[must_use]
84    pub const fn is_never(&self) -> bool {
85        self.bytes.is_none()
86            && self.integer.is_none()
87            && self.float.is_none()
88            && self.boolean.is_none()
89            && self.timestamp.is_none()
90            && self.regex.is_none()
91            && self.null.is_none()
92            && self.undefined.is_none()
93            && self.array.is_none()
94            && self.object.is_none()
95    }
96
97    /// Returns `true` if the type is `float`.
98    #[must_use]
99    pub const fn is_float(&self) -> bool {
100        self.bytes.is_none()
101            && self.integer.is_none()
102            && self.boolean.is_none()
103            && self.timestamp.is_none()
104            && self.regex.is_none()
105            && self.null.is_none()
106            && self.undefined.is_none()
107            && self.array.is_none()
108            && self.object.is_none()
109    }
110
111    /// Returns `true` if the type is `boolean`.
112    #[must_use]
113    pub const fn is_boolean(&self) -> bool {
114        self.bytes.is_none()
115            && self.integer.is_none()
116            && self.float.is_none()
117            && self.timestamp.is_none()
118            && self.regex.is_none()
119            && self.null.is_none()
120            && self.undefined.is_none()
121            && self.array.is_none()
122            && self.object.is_none()
123    }
124
125    /// Returns `true` if the type is `timestamp`.
126    #[must_use]
127    pub const fn is_timestamp(&self) -> bool {
128        self.bytes.is_none()
129            && self.integer.is_none()
130            && self.float.is_none()
131            && self.boolean.is_none()
132            && self.regex.is_none()
133            && self.null.is_none()
134            && self.undefined.is_none()
135            && self.array.is_none()
136            && self.object.is_none()
137    }
138
139    /// Returns `true` if the type is `regex`.
140    #[must_use]
141    pub const fn is_regex(&self) -> bool {
142        self.bytes.is_none()
143            && self.integer.is_none()
144            && self.float.is_none()
145            && self.boolean.is_none()
146            && self.timestamp.is_none()
147            && self.null.is_none()
148            && self.undefined.is_none()
149            && self.array.is_none()
150            && self.object.is_none()
151    }
152
153    /// Returns `true` if the type is `null`.
154    #[must_use]
155    pub const fn is_null(&self) -> bool {
156        self.bytes.is_none()
157            && self.integer.is_none()
158            && self.float.is_none()
159            && self.boolean.is_none()
160            && self.timestamp.is_none()
161            && self.regex.is_none()
162            && self.undefined.is_none()
163            && self.array.is_none()
164            && self.object.is_none()
165    }
166
167    /// Returns `true` if the type is `undefined`.
168    #[must_use]
169    pub const fn is_undefined(&self) -> bool {
170        self.bytes.is_none()
171            && self.integer.is_none()
172            && self.float.is_none()
173            && self.boolean.is_none()
174            && self.timestamp.is_none()
175            && self.regex.is_none()
176            && self.null.is_none()
177            && self.array.is_none()
178            && self.object.is_none()
179    }
180
181    /// Returns `true` if the type is `array`.
182    #[must_use]
183    pub const fn is_array(&self) -> bool {
184        self.bytes.is_none()
185            && self.integer.is_none()
186            && self.float.is_none()
187            && self.boolean.is_none()
188            && self.timestamp.is_none()
189            && self.regex.is_none()
190            && self.null.is_none()
191            && self.undefined.is_none()
192            && self.object.is_none()
193    }
194
195    /// Returns `true` if the type is `object`.
196    #[must_use]
197    pub const fn is_object(&self) -> bool {
198        self.bytes.is_none()
199            && self.integer.is_none()
200            && self.float.is_none()
201            && self.boolean.is_none()
202            && self.timestamp.is_none()
203            && self.regex.is_none()
204            && self.null.is_none()
205            && self.undefined.is_none()
206            && self.array.is_none()
207    }
208
209    /// Returns `true` if at most one type is set.
210    #[must_use]
211    pub const fn is_exact(&self) -> bool {
212        self.is_bytes()
213            || self.is_integer()
214            || self.is_float()
215            || self.is_boolean()
216            || self.is_timestamp()
217            || self.is_regex()
218            || self.is_null()
219            || self.is_undefined()
220            || self.is_array()
221            || self.is_object()
222            || self.is_never()
223    }
224
225    /// Check if `self` is a superset of `other`.
226    ///
227    /// Meaning, if `other` has a type defined as valid, then `self` needs to have it defined as
228    /// valid as well.
229    ///
230    /// Collection types are recursively checked (meaning, known fields in `self` also need to be
231    /// a superset of `other`.
232    ///
233    /// # Errors
234    /// If the type is not a superset, a path to one field that doesn't match is returned.
235    /// This is mostly useful for debugging.
236    pub fn is_superset(&self, other: &Self) -> Result<(), OwnedValuePath> {
237        if let (None, Some(())) = (self.bytes, other.bytes) {
238            return Err(OwnedValuePath::root());
239        }
240
241        if let (None, Some(())) = (self.integer, other.integer) {
242            return Err(OwnedValuePath::root());
243        }
244
245        if let (None, Some(())) = (self.float, other.float) {
246            return Err(OwnedValuePath::root());
247        }
248
249        if let (None, Some(())) = (self.boolean, other.boolean) {
250            return Err(OwnedValuePath::root());
251        }
252
253        if let (None, Some(())) = (self.timestamp, other.timestamp) {
254            return Err(OwnedValuePath::root());
255        }
256
257        if let (None, Some(())) = (self.regex, other.regex) {
258            return Err(OwnedValuePath::root());
259        }
260
261        if let (None, Some(())) = (self.null, other.null) {
262            return Err(OwnedValuePath::root());
263        }
264
265        if let (None, Some(())) = (self.undefined, other.undefined) {
266            return Err(OwnedValuePath::root());
267        }
268
269        match (self.array.as_ref(), other.array.as_ref()) {
270            (None, Some(_)) => return Err(OwnedValuePath::root()),
271            (Some(lhs), Some(rhs)) => {
272                lhs.is_superset(rhs)?;
273            }
274            _ => {}
275        }
276
277        match (self.object.as_ref(), other.object.as_ref()) {
278            (None, Some(_)) => return Err(OwnedValuePath::root()),
279            (Some(lhs), Some(rhs)) => lhs.is_superset(rhs)?,
280            _ => {}
281        }
282
283        Ok(())
284    }
285
286    /// Check if `self` intersects `other`.
287    ///
288    /// Returns `true` if there are type states common to both `self` and `other`.
289    #[must_use]
290    pub const fn intersects(&self, other: &Self) -> bool {
291        // a "never" type can be treated as any type
292        if self.is_never() || other.is_never() {
293            return true;
294        }
295
296        if self.contains_bytes() && other.contains_bytes() {
297            return true;
298        }
299
300        if self.contains_integer() && other.contains_integer() {
301            return true;
302        }
303
304        if self.contains_float() && other.contains_float() {
305            return true;
306        }
307
308        if self.contains_boolean() && other.contains_boolean() {
309            return true;
310        }
311
312        if self.contains_timestamp() && other.contains_timestamp() {
313            return true;
314        }
315
316        if self.contains_regex() && other.contains_regex() {
317            return true;
318        }
319
320        if self.contains_null() && other.contains_null() {
321            return true;
322        }
323
324        if self.contains_undefined() && other.contains_undefined() {
325            return true;
326        }
327
328        if self.contains_array() && other.contains_array() {
329            return true;
330        }
331
332        if self.contains_object() && other.contains_object() {
333            return true;
334        }
335
336        false
337    }
338}
339
340// contains_*
341impl Kind {
342    /// Returns `true` if the type is _at least_ `bytes`.
343    #[must_use]
344    pub const fn contains_bytes(&self) -> bool {
345        self.bytes.is_some() || self.is_never()
346    }
347
348    /// Returns `true` if the type is _at least_ `integer`.
349    #[must_use]
350    pub const fn contains_integer(&self) -> bool {
351        self.integer.is_some() || self.is_never()
352    }
353
354    /// Returns `true` if the type is _at least_ `float`.
355    #[must_use]
356    pub const fn contains_float(&self) -> bool {
357        self.float.is_some() || self.is_never()
358    }
359
360    /// Returns `true` if the type is _at least_ `boolean`.
361    #[must_use]
362    pub const fn contains_boolean(&self) -> bool {
363        self.boolean.is_some() || self.is_never()
364    }
365
366    /// Returns `true` if the type is _at least_ `timestamp`.
367    #[must_use]
368    pub const fn contains_timestamp(&self) -> bool {
369        self.timestamp.is_some() || self.is_never()
370    }
371
372    /// Returns `true` if the type is _at least_ `regex`.
373    #[must_use]
374    pub const fn contains_regex(&self) -> bool {
375        self.regex.is_some() || self.is_never()
376    }
377
378    /// Returns `true` if the type is _at least_ `null`.
379    #[must_use]
380    pub const fn contains_null(&self) -> bool {
381        self.null.is_some() || self.is_never()
382    }
383
384    /// Returns `true` if the type is _at least_ `undefined`.
385    #[must_use]
386    pub const fn contains_undefined(&self) -> bool {
387        self.undefined.is_some() || self.is_never()
388    }
389
390    /// Returns `true` if the type can be _any_ type other than `undefined`
391    #[must_use]
392    pub const fn contains_any_defined(&self) -> bool {
393        !self.is_undefined()
394    }
395
396    /// Returns `true` if the type is _at least_ `array`.
397    #[must_use]
398    pub const fn contains_array(&self) -> bool {
399        self.array.is_some() || self.is_never()
400    }
401
402    /// Returns `true` if the type is _at least_ `object`.
403    #[must_use]
404    pub const fn contains_object(&self) -> bool {
405        self.object.is_some() || self.is_never()
406    }
407
408    /// Returns `true` if the type contains _at least_ one non-collection type.
409    #[must_use]
410    pub const fn contains_primitive(&self) -> bool {
411        self.bytes.is_some()
412            || self.null.is_some()
413            || self.boolean.is_some()
414            || self.float.is_some()
415            || self.integer.is_some()
416            || self.regex.is_some()
417            || self.timestamp.is_some()
418            || self.undefined.is_some()
419    }
420}
421
422#[cfg(test)]
423mod tests {
424    use std::collections::{BTreeMap, HashMap};
425
426    use super::*;
427    use crate::value::kind::Collection;
428
429    #[test]
430    #[allow(clippy::too_many_lines)]
431    fn test_is_superset() {
432        struct TestCase {
433            this: Kind,
434            other: Kind,
435            want: bool,
436        }
437
438        for (title, TestCase { this, other, want }) in HashMap::from([
439            (
440                "any comparison",
441                TestCase {
442                    this: Kind::any(),
443                    other: Kind::any(),
444                    want: true,
445                },
446            ),
447            (
448                "exact/any mismatch",
449                TestCase {
450                    this: Kind::json(),
451                    other: Kind::any(),
452                    want: false,
453                },
454            ),
455            (
456                "any-like",
457                TestCase {
458                    this: Kind::any().or_object(Collection::from_parts(
459                        BTreeMap::from([("foo".into(), Kind::any())]),
460                        Kind::any(),
461                    )),
462                    other: Kind::any(),
463                    want: true,
464                },
465            ),
466            (
467                "no unknown vs unknown fields",
468                TestCase {
469                    // The object we create here has no "unknown" fields, e.g. it's a "closed"
470                    // object. The `other` object _does_ have unknown field types, and thus `this`
471                    // cannot be a superset of `other`.
472                    this: Kind::any().or_object(BTreeMap::from([("foo".into(), Kind::any())])),
473                    other: Kind::any(),
474                    want: false,
475                },
476            ),
477            (
478                "nested object match",
479                TestCase {
480                    this: Kind::object(BTreeMap::from([(
481                        "foo".into(),
482                        Kind::object(BTreeMap::from([("bar".into(), Kind::any())])),
483                    )])),
484                    other: Kind::object(BTreeMap::from([(
485                        "foo".into(),
486                        Kind::object(BTreeMap::from([("bar".into(), Kind::bytes())])),
487                    )])),
488                    want: true,
489                },
490            ),
491            (
492                "nested object mismatch",
493                TestCase {
494                    this: Kind::object(BTreeMap::from([(
495                        "foo".into(),
496                        Kind::object(BTreeMap::from([("bar".into(), Kind::bytes())])),
497                    )])),
498                    other: Kind::object(BTreeMap::from([(
499                        "foo".into(),
500                        Kind::object(BTreeMap::from([("bar".into(), Kind::integer())])),
501                    )])),
502                    want: false,
503                },
504            ),
505            (
506                "nested array match",
507                TestCase {
508                    this: Kind::array(BTreeMap::from([(
509                        0.into(),
510                        Kind::array(BTreeMap::from([(1.into(), Kind::any())])),
511                    )])),
512                    other: Kind::array(BTreeMap::from([(
513                        0.into(),
514                        Kind::array(BTreeMap::from([(1.into(), Kind::bytes())])),
515                    )])),
516                    want: true,
517                },
518            ),
519            (
520                "nested array mismatch",
521                TestCase {
522                    this: Kind::array(BTreeMap::from([(
523                        0.into(),
524                        Kind::array(BTreeMap::from([(1.into(), Kind::bytes())])),
525                    )])),
526                    other: Kind::array(BTreeMap::from([(
527                        0.into(),
528                        Kind::array(BTreeMap::from([(1.into(), Kind::integer())])),
529                    )])),
530                    want: false,
531                },
532            ),
533        ]) {
534            assert_eq!(this.is_superset(&other).is_ok(), want, "{title}");
535        }
536    }
537
538    #[test]
539    fn test_is_exact() {
540        struct TestCase {
541            kind: Kind,
542            want: bool,
543        }
544
545        for (title, TestCase { kind, want }) in HashMap::from([
546            (
547                "bytes",
548                TestCase {
549                    kind: Kind::bytes(),
550                    want: true,
551                },
552            ),
553            (
554                "integer",
555                TestCase {
556                    kind: Kind::integer(),
557                    want: true,
558                },
559            ),
560            (
561                "float",
562                TestCase {
563                    kind: Kind::float(),
564                    want: true,
565                },
566            ),
567            (
568                "boolean",
569                TestCase {
570                    kind: Kind::boolean(),
571                    want: true,
572                },
573            ),
574            (
575                "timestamp",
576                TestCase {
577                    kind: Kind::timestamp(),
578                    want: true,
579                },
580            ),
581            (
582                "regex",
583                TestCase {
584                    kind: Kind::regex(),
585                    want: true,
586                },
587            ),
588            (
589                "null",
590                TestCase {
591                    kind: Kind::null(),
592                    want: true,
593                },
594            ),
595            (
596                "object",
597                TestCase {
598                    kind: Kind::object(BTreeMap::default()),
599                    want: true,
600                },
601            ),
602            (
603                "array",
604                TestCase {
605                    kind: Kind::array(BTreeMap::default()),
606                    want: true,
607                },
608            ),
609            (
610                "bytes & integer",
611                TestCase {
612                    kind: Kind::bytes().or_integer(),
613                    want: false,
614                },
615            ),
616            (
617                "null & object",
618                TestCase {
619                    kind: Kind::null().or_object(BTreeMap::default()),
620                    want: false,
621                },
622            ),
623        ]) {
624            assert_eq!(kind.is_exact(), want, "{title}");
625        }
626    }
627}