use crate::value::{Value, ops::text};
fn normalize_list_ref(value: &Value) -> Vec<&Value> {
match value {
Value::List(values) => values.iter().collect(),
value => vec![value],
}
}
fn contains_by<F>(value: &Value, needle: &Value, eq: F) -> Option<bool>
where
F: Fn(&Value, &Value) -> bool,
{
value
.as_list()
.map(|items| items.iter().any(|item| eq(item, needle)))
}
#[expect(clippy::unnecessary_wraps)]
fn contains_any_by<F>(value: &Value, needles: &Value, eq: F) -> Option<bool>
where
F: Fn(&Value, &Value) -> bool,
{
let needles = normalize_list_ref(needles);
match value {
Value::List(items) => Some(
needles
.iter()
.any(|needle| items.iter().any(|item| eq(item, needle))),
),
scalar => Some(needles.iter().any(|needle| eq(scalar, needle))),
}
}
#[expect(clippy::unnecessary_wraps)]
fn contains_all_by<F>(value: &Value, needles: &Value, eq: F) -> Option<bool>
where
F: Fn(&Value, &Value) -> bool,
{
let needles = normalize_list_ref(needles);
match value {
Value::List(items) => Some(
needles
.iter()
.all(|needle| items.iter().any(|item| eq(item, needle))),
),
scalar => Some(needles.len() == 1 && eq(scalar, needles[0])),
}
}
fn in_list_by<F>(value: &Value, haystack: &Value, eq: F) -> Option<bool>
where
F: Fn(&Value, &Value) -> bool,
{
if let Value::List(items) = haystack {
Some(items.iter().any(|item| eq(item, value)))
} else {
None
}
}
#[must_use]
pub const fn is_empty(value: &Value) -> Option<bool> {
match value {
Value::List(values) => Some(values.is_empty()),
Value::Map(entries) => Some(entries.is_empty()),
Value::Text(text) => Some(text.is_empty()),
Value::Blob(blob) => Some(blob.is_empty()),
Value::Null => Some(true),
_ => None,
}
}
#[must_use]
pub fn is_not_empty(value: &Value) -> Option<bool> {
is_empty(value).map(|empty| !empty)
}
#[must_use]
pub fn contains(value: &Value, needle: &Value) -> Option<bool> {
contains_by(value, needle, |left, right| left == right)
}
#[must_use]
pub fn contains_any(value: &Value, needles: &Value) -> Option<bool> {
contains_any_by(value, needles, |left, right| left == right)
}
#[must_use]
pub fn contains_all(value: &Value, needles: &Value) -> Option<bool> {
contains_all_by(value, needles, |left, right| left == right)
}
#[must_use]
pub fn in_list(value: &Value, haystack: &Value) -> Option<bool> {
in_list_by(value, haystack, |left, right| left == right)
}
#[must_use]
pub fn contains_ci(value: &Value, needle: &Value) -> Option<bool> {
match value {
Value::List(_) => contains_by(value, needle, text::eq_ci),
_ => Some(text::eq_ci(value, needle)),
}
}
#[must_use]
pub fn contains_any_ci(value: &Value, needles: &Value) -> Option<bool> {
contains_any_by(value, needles, text::eq_ci)
}
#[must_use]
pub fn contains_all_ci(value: &Value, needles: &Value) -> Option<bool> {
contains_all_by(value, needles, text::eq_ci)
}
#[must_use]
pub fn in_list_ci(value: &Value, haystack: &Value) -> Option<bool> {
in_list_by(value, haystack, text::eq_ci)
}
impl Value {
#[must_use]
pub const fn is_empty(&self) -> Option<bool> {
is_empty(self)
}
#[must_use]
pub fn is_not_empty(&self) -> Option<bool> {
is_not_empty(self)
}
#[must_use]
pub fn contains(&self, needle: &Self) -> Option<bool> {
contains(self, needle)
}
#[must_use]
pub fn contains_any(&self, needles: &Self) -> Option<bool> {
contains_any(self, needles)
}
#[must_use]
pub fn contains_all(&self, needles: &Self) -> Option<bool> {
contains_all(self, needles)
}
#[must_use]
pub fn in_list(&self, haystack: &Self) -> Option<bool> {
in_list(self, haystack)
}
#[must_use]
pub fn contains_ci(&self, needle: &Self) -> Option<bool> {
contains_ci(self, needle)
}
#[must_use]
pub fn contains_any_ci(&self, needles: &Self) -> Option<bool> {
contains_any_ci(self, needles)
}
#[must_use]
pub fn contains_all_ci(&self, needles: &Self) -> Option<bool> {
contains_all_ci(self, needles)
}
#[must_use]
pub fn in_list_ci(&self, haystack: &Self) -> Option<bool> {
in_list_ci(self, haystack)
}
}