Skip to main content

icydb_core/value/ops/
collection.rs

1//! Module: value::ops::collection
2//!
3//! Responsibility: collection membership and emptiness operations for `Value`.
4//! Does not own: text casefolding internals or map canonicalization.
5//! Boundary: representation-local list/scalar membership helpers.
6
7use crate::value::{Value, ops::text};
8use std::slice;
9
10fn list_or_scalar(value: &Value) -> &[Value] {
11    match value {
12        Value::List(values) => values,
13        value => slice::from_ref(value),
14    }
15}
16
17fn contains_by<F>(value: &Value, needle: &Value, eq: F) -> Option<bool>
18where
19    F: Fn(&Value, &Value) -> bool,
20{
21    value
22        .as_list()
23        .map(|items| items.iter().any(|item| eq(item, needle)))
24}
25
26fn contains_any_by<F>(value: &Value, needles: &Value, eq: F) -> bool
27where
28    F: Fn(&Value, &Value) -> bool,
29{
30    let needles = list_or_scalar(needles);
31    match value {
32        Value::List(items) => needles
33            .iter()
34            .any(|needle| items.iter().any(|item| eq(item, needle))),
35        scalar => needles.iter().any(|needle| eq(scalar, needle)),
36    }
37}
38
39fn contains_all_by<F>(value: &Value, needles: &Value, eq: F) -> bool
40where
41    F: Fn(&Value, &Value) -> bool,
42{
43    let needles = list_or_scalar(needles);
44    match value {
45        Value::List(items) => needles
46            .iter()
47            .all(|needle| items.iter().any(|item| eq(item, needle))),
48        scalar => match needles {
49            [needle] => eq(scalar, needle),
50            _ => false,
51        },
52    }
53}
54
55fn in_list_by<F>(value: &Value, haystack: &Value, eq: F) -> Option<bool>
56where
57    F: Fn(&Value, &Value) -> bool,
58{
59    if let Value::List(items) = haystack {
60        Some(items.iter().any(|item| eq(item, value)))
61    } else {
62        None
63    }
64}
65
66/// Return whether a value is empty when emptiness is defined for its variant.
67#[must_use]
68const fn is_empty(value: &Value) -> Option<bool> {
69    match value {
70        Value::List(values) => Some(values.is_empty()),
71        Value::Map(entries) => Some(entries.is_empty()),
72        Value::Text(text) => Some(text.is_empty()),
73        Value::Blob(blob) => Some(blob.is_empty()),
74
75        // Fields represented as Value::Null behave as empty values.
76        Value::Null => Some(true),
77
78        _ => None,
79    }
80}
81
82/// Logical negation of [`is_empty`].
83#[must_use]
84fn is_not_empty(value: &Value) -> Option<bool> {
85    is_empty(value).map(|empty| !empty)
86}
87
88/// Returns true if `value` contains `needle`.
89#[must_use]
90fn contains(value: &Value, needle: &Value) -> Option<bool> {
91    contains_by(value, needle, |left, right| left == right)
92}
93
94/// Returns true if any item in `needles` matches a member of `value`.
95#[must_use]
96fn contains_any(value: &Value, needles: &Value) -> bool {
97    contains_any_by(value, needles, |left, right| left == right)
98}
99
100/// Returns true if every item in `needles` matches a member of `value`.
101#[must_use]
102fn contains_all(value: &Value, needles: &Value) -> bool {
103    contains_all_by(value, needles, |left, right| left == right)
104}
105
106/// Returns true if `value` exists inside the provided list.
107#[must_use]
108fn in_list(value: &Value, haystack: &Value) -> Option<bool> {
109    in_list_by(value, haystack, |left, right| left == right)
110}
111
112/// Case-insensitive `contains` supporting text and identifier variants.
113#[must_use]
114fn contains_ci(value: &Value, needle: &Value) -> Option<bool> {
115    match value {
116        Value::List(_) => contains_by(value, needle, text::eq_ci),
117        _ => Some(text::eq_ci(value, needle)),
118    }
119}
120
121/// Case-insensitive variant of [`contains_any`].
122#[must_use]
123fn contains_any_ci(value: &Value, needles: &Value) -> bool {
124    contains_any_by(value, needles, text::eq_ci)
125}
126
127/// Case-insensitive variant of [`contains_all`].
128#[must_use]
129fn contains_all_ci(value: &Value, needles: &Value) -> bool {
130    contains_all_by(value, needles, text::eq_ci)
131}
132
133/// Case-insensitive variant of [`in_list`].
134#[must_use]
135fn in_list_ci(value: &Value, haystack: &Value) -> Option<bool> {
136    in_list_by(value, haystack, text::eq_ci)
137}
138
139impl Value {
140    #[must_use]
141    pub const fn is_empty(&self) -> Option<bool> {
142        is_empty(self)
143    }
144
145    /// Logical negation of [`is_empty`](Self::is_empty).
146    #[must_use]
147    pub fn is_not_empty(&self) -> Option<bool> {
148        is_not_empty(self)
149    }
150
151    /// Returns true if `self` contains `needle` (or equals it for scalars).
152    #[must_use]
153    pub fn contains(&self, needle: &Self) -> Option<bool> {
154        contains(self, needle)
155    }
156
157    /// Returns true if any item in `needles` matches a member of `self`.
158    #[must_use]
159    pub fn contains_any(&self, needles: &Self) -> Option<bool> {
160        Some(contains_any(self, needles))
161    }
162
163    /// Returns true if every item in `needles` matches a member of `self`.
164    #[must_use]
165    pub fn contains_all(&self, needles: &Self) -> Option<bool> {
166        Some(contains_all(self, needles))
167    }
168
169    /// Returns true if `self` exists inside the provided list.
170    #[must_use]
171    pub fn in_list(&self, haystack: &Self) -> Option<bool> {
172        in_list(self, haystack)
173    }
174
175    /// Case-insensitive `contains` supporting text and identifier variants.
176    #[must_use]
177    pub fn contains_ci(&self, needle: &Self) -> Option<bool> {
178        contains_ci(self, needle)
179    }
180
181    /// Case-insensitive variant of [`contains_any`](Self::contains_any).
182    #[must_use]
183    pub fn contains_any_ci(&self, needles: &Self) -> Option<bool> {
184        Some(contains_any_ci(self, needles))
185    }
186
187    /// Case-insensitive variant of [`contains_all`](Self::contains_all).
188    #[must_use]
189    pub fn contains_all_ci(&self, needles: &Self) -> Option<bool> {
190        Some(contains_all_ci(self, needles))
191    }
192
193    /// Case-insensitive variant of [`in_list`](Self::in_list).
194    #[must_use]
195    pub fn in_list_ci(&self, haystack: &Self) -> Option<bool> {
196        in_list_ci(self, haystack)
197    }
198}