teaql-core 4.2.0

TeaQL core, SQL, runtime, dialect, and macro crates for model-driven data access
Documentation
use std::sync::Arc;

use crate::{BaseEntity, SmartList, Value};

pub trait TeaqlEmpty {
    fn teaql_is_empty(&self) -> bool;
}

impl TeaqlEmpty for String {
    fn teaql_is_empty(&self) -> bool {
        self.is_empty()
    }
}

impl TeaqlEmpty for &str {
    fn teaql_is_empty(&self) -> bool {
        self.is_empty()
    }
}

impl<T> TeaqlEmpty for Vec<T> {
    fn teaql_is_empty(&self) -> bool {
        self.is_empty()
    }
}

impl<T> TeaqlEmpty for SmartList<T> {
    fn teaql_is_empty(&self) -> bool {
        self.is_empty()
    }
}

impl<T> TeaqlEmpty for Option<T> {
    fn teaql_is_empty(&self) -> bool {
        self.is_none()
    }
}

impl TeaqlEmpty for Value {
    fn teaql_is_empty(&self) -> bool {
        match self {
            Self::Null => true,
            Self::Text(value) => value.is_empty(),
            Self::List(values) => values.is_empty(),
            Self::Object(values) => values.is_empty(),
            Self::Json(serde_json::Value::Null) => true,
            Self::Json(serde_json::Value::String(value)) => value.is_empty(),
            Self::Json(serde_json::Value::Array(values)) => values.is_empty(),
            Self::Json(serde_json::Value::Object(values)) => values.is_empty(),
            _ => false,
        }
    }
}

#[derive(Clone)]
pub struct SafeExpression<R, T> {
    root: Arc<R>,
    #[allow(clippy::type_complexity)]
    evaluator: Arc<dyn Fn(&R) -> Option<T> + Send + Sync>,
}

impl<R, T> SafeExpression<R, T>
where
    R: Send + Sync + 'static,
    T: 'static,
{
    pub fn new(root: R, evaluator: impl Fn(&R) -> Option<T> + Send + Sync + 'static) -> Self {
        Self {
            root: Arc::new(root),
            evaluator: Arc::new(evaluator),
        }
    }

    pub fn eval(&self) -> Option<T> {
        (self.evaluator)(&self.root)
    }

    pub fn eval_with(&self, root: &R) -> Option<T> {
        (self.evaluator)(root)
    }

    pub fn apply<U>(self, mapper: impl Fn(T) -> U + Send + Sync + 'static) -> SafeExpression<R, U>
    where
        U: 'static,
    {
        self.apply_optional(move |value| Some(mapper(value)))
    }

    pub fn apply_optional<U>(
        self,
        mapper: impl Fn(T) -> Option<U> + Send + Sync + 'static,
    ) -> SafeExpression<R, U>
    where
        U: 'static,
    {
        let root = Arc::clone(&self.root);
        let evaluator = Arc::clone(&self.evaluator);
        SafeExpression {
            root,
            evaluator: Arc::new(move |root| evaluator(root).and_then(&mapper)),
        }
    }

    pub fn or_else(&self, default_value: T) -> T {
        self.eval().unwrap_or(default_value)
    }

    pub fn or_else_with(&self, default_value: impl FnOnce() -> T) -> T {
        self.eval().unwrap_or_else(default_value)
    }

    pub fn or_else_throw<E>(&self, error: impl FnOnce() -> E) -> Result<T, E> {
        self.eval().ok_or_else(error)
    }

    pub fn is_null(&self) -> bool {
        self.eval().is_none()
    }

    pub fn is_not_null(&self) -> bool {
        self.eval().is_some()
    }

    pub fn is_empty(&self) -> bool
    where
        T: TeaqlEmpty,
    {
        self.eval()
            .map(|value| value.teaql_is_empty())
            .unwrap_or(true)
    }

    pub fn is_not_empty(&self) -> bool
    where
        T: TeaqlEmpty,
    {
        !self.is_empty()
    }

    pub fn when_is_null(&self, function: impl FnOnce()) {
        if self.is_null() {
            function();
        }
    }

    pub fn when_is_not_null(&self, consumer: impl FnOnce(T)) {
        if let Some(value) = self.eval() {
            consumer(value);
        }
    }

    pub fn when_is_empty(&self, function: impl FnOnce())
    where
        T: TeaqlEmpty,
    {
        if self.is_empty() {
            function();
        }
    }

    pub fn when_not_empty(&self, consumer: impl FnOnce(T))
    where
        T: TeaqlEmpty,
    {
        if let Some(value) = self.eval().filter(|value| !value.teaql_is_empty()) {
            consumer(value);
        }
    }
}

impl<R> SafeExpression<R, R>
where
    R: Clone + Send + Sync + 'static,
{
    pub fn value(root: R) -> Self {
        Self::new(root, |root| Some(root.clone()))
    }

    pub fn root(&self) -> &R {
        &self.root
    }
}

impl<R, E> SafeExpression<R, E>
where
    R: Send + Sync + 'static,
    E: BaseEntity + Clone + 'static,
{
    pub fn entity_id(self) -> SafeExpression<R, u64> {
        self.apply(|entity| entity.id())
    }

    pub fn entity_version(self) -> SafeExpression<R, i64> {
        self.apply(|entity| entity.version_value())
    }

    pub fn update_entity_id(self, id: u64) -> SafeExpression<R, E> {
        self.apply(move |mut entity| {
            entity.set_id(id);
            entity
        })
    }
}

impl<R, T> SafeExpression<R, SmartList<T>>
where
    R: Send + Sync + 'static,
    T: Clone + 'static,
{
    pub fn size(self) -> SafeExpression<R, usize> {
        self.apply(|list| list.len())
    }

    pub fn first(self) -> SafeExpression<R, T> {
        self.apply_optional(|list| list.first().cloned())
    }

    pub fn get(self, index: usize) -> SafeExpression<R, T> {
        self.apply_optional(move |list| list.get(index).cloned())
    }
}

#[cfg(test)]
mod tests {
    use std::sync::Arc;
    use std::sync::atomic::{AtomicUsize, Ordering};

    use super::SafeExpression;

    #[test]
    fn safe_expression_eval_with_uses_the_supplied_root() {
        let expression = SafeExpression::new(2_i32, |root| Some(root * 3));

        assert_eq!(expression.eval(), Some(6));
        assert_eq!(expression.eval_with(&4), Some(12));
    }

    #[test]
    fn safe_expression_apply_optional_short_circuits_remaining_mappers() {
        let optional_calls = Arc::new(AtomicUsize::new(0));
        let remaining_calls = Arc::new(AtomicUsize::new(0));

        let optional_calls_for_mapper = Arc::clone(&optional_calls);
        let remaining_calls_for_mapper = Arc::clone(&remaining_calls);
        let expression = SafeExpression::value(5_i32)
            .apply_optional(move |_| {
                optional_calls_for_mapper.fetch_add(1, Ordering::SeqCst);
                None::<i32>
            })
            .apply(move |value| {
                remaining_calls_for_mapper.fetch_add(1, Ordering::SeqCst);
                value * 2
            });

        assert_eq!(expression.eval(), None);
        assert_eq!(optional_calls.load(Ordering::SeqCst), 1);
        assert_eq!(remaining_calls.load(Ordering::SeqCst), 0);
    }

    #[test]
    fn safe_expression_lazy_fallback_and_error_only_run_for_missing_values() {
        let mut present_fallback_calls = 0;
        let present = SafeExpression::value(7_i32);
        assert_eq!(
            present.or_else_with(|| {
                present_fallback_calls += 1;
                9
            }),
            7
        );
        assert_eq!(present_fallback_calls, 0);
        assert_eq!(present.or_else_throw(|| "unused error"), Ok(7));

        let missing = SafeExpression::new((), |_| None::<i32>);
        let mut missing_fallback_calls = 0;
        assert_eq!(
            missing.or_else_with(|| {
                missing_fallback_calls += 1;
                9
            }),
            9
        );
        assert_eq!(missing_fallback_calls, 1);
        assert_eq!(
            missing.or_else_throw(|| "missing value"),
            Err("missing value")
        );
    }

    #[test]
    fn safe_expression_callbacks_only_run_for_their_matching_branch() {
        let present = SafeExpression::value("teaql".to_owned());
        let mut present_null_calls = 0;
        let mut present_value = None;
        present.when_is_null(|| present_null_calls += 1);
        present.when_is_not_null(|value| present_value = Some(value));
        assert_eq!(present_null_calls, 0);
        assert_eq!(present_value.as_deref(), Some("teaql"));

        let missing = SafeExpression::new((), |_| None::<String>);
        let mut missing_null_calls = 0;
        let mut missing_value_calls = 0;
        missing.when_is_null(|| missing_null_calls += 1);
        missing.when_is_not_null(|_| missing_value_calls += 1);
        assert_eq!(missing_null_calls, 1);
        assert_eq!(missing_value_calls, 0);
    }
}