Skip to main content

teaql_core/
safe_expression.rs

1use std::sync::Arc;
2
3use crate::{BaseEntity, SmartList, Value};
4
5pub trait TeaqlEmpty {
6    fn teaql_is_empty(&self) -> bool;
7}
8
9impl TeaqlEmpty for String {
10    fn teaql_is_empty(&self) -> bool {
11        self.is_empty()
12    }
13}
14
15impl TeaqlEmpty for &str {
16    fn teaql_is_empty(&self) -> bool {
17        self.is_empty()
18    }
19}
20
21impl<T> TeaqlEmpty for Vec<T> {
22    fn teaql_is_empty(&self) -> bool {
23        self.is_empty()
24    }
25}
26
27impl<T> TeaqlEmpty for SmartList<T> {
28    fn teaql_is_empty(&self) -> bool {
29        self.is_empty()
30    }
31}
32
33impl<T> TeaqlEmpty for Option<T> {
34    fn teaql_is_empty(&self) -> bool {
35        self.is_none()
36    }
37}
38
39impl TeaqlEmpty for Value {
40    fn teaql_is_empty(&self) -> bool {
41        match self {
42            Self::Null => true,
43            Self::Text(value) => value.is_empty(),
44            Self::List(values) => values.is_empty(),
45            Self::Object(values) => values.is_empty(),
46            Self::Json(serde_json::Value::Null) => true,
47            Self::Json(serde_json::Value::String(value)) => value.is_empty(),
48            Self::Json(serde_json::Value::Array(values)) => values.is_empty(),
49            Self::Json(serde_json::Value::Object(values)) => values.is_empty(),
50            _ => false,
51        }
52    }
53}
54
55#[derive(Clone)]
56pub struct SafeExpression<R, T> {
57    root: Arc<R>,
58    #[allow(clippy::type_complexity)]
59    evaluator: Arc<dyn Fn(&R) -> Option<T> + Send + Sync>,
60}
61
62impl<R, T> SafeExpression<R, T>
63where
64    R: Send + Sync + 'static,
65    T: 'static,
66{
67    pub fn new(root: R, evaluator: impl Fn(&R) -> Option<T> + Send + Sync + 'static) -> Self {
68        Self {
69            root: Arc::new(root),
70            evaluator: Arc::new(evaluator),
71        }
72    }
73
74    pub fn eval(&self) -> Option<T> {
75        (self.evaluator)(&self.root)
76    }
77
78    pub fn eval_with(&self, root: &R) -> Option<T> {
79        (self.evaluator)(root)
80    }
81
82    pub fn apply<U>(self, mapper: impl Fn(T) -> U + Send + Sync + 'static) -> SafeExpression<R, U>
83    where
84        U: 'static,
85    {
86        self.apply_optional(move |value| Some(mapper(value)))
87    }
88
89    pub fn apply_optional<U>(
90        self,
91        mapper: impl Fn(T) -> Option<U> + Send + Sync + 'static,
92    ) -> SafeExpression<R, U>
93    where
94        U: 'static,
95    {
96        let root = Arc::clone(&self.root);
97        let evaluator = Arc::clone(&self.evaluator);
98        SafeExpression {
99            root,
100            evaluator: Arc::new(move |root| evaluator(root).and_then(&mapper)),
101        }
102    }
103
104    pub fn or_else(&self, default_value: T) -> T {
105        self.eval().unwrap_or(default_value)
106    }
107
108    pub fn or_else_with(&self, default_value: impl FnOnce() -> T) -> T {
109        self.eval().unwrap_or_else(default_value)
110    }
111
112    pub fn or_else_throw<E>(&self, error: impl FnOnce() -> E) -> Result<T, E> {
113        self.eval().ok_or_else(error)
114    }
115
116    pub fn is_null(&self) -> bool {
117        self.eval().is_none()
118    }
119
120    pub fn is_not_null(&self) -> bool {
121        self.eval().is_some()
122    }
123
124    pub fn is_empty(&self) -> bool
125    where
126        T: TeaqlEmpty,
127    {
128        self.eval()
129            .map(|value| value.teaql_is_empty())
130            .unwrap_or(true)
131    }
132
133    pub fn is_not_empty(&self) -> bool
134    where
135        T: TeaqlEmpty,
136    {
137        !self.is_empty()
138    }
139
140    pub fn when_is_null(&self, function: impl FnOnce()) {
141        if self.is_null() {
142            function();
143        }
144    }
145
146    pub fn when_is_not_null(&self, consumer: impl FnOnce(T)) {
147        if let Some(value) = self.eval() {
148            consumer(value);
149        }
150    }
151
152    pub fn when_is_empty(&self, function: impl FnOnce())
153    where
154        T: TeaqlEmpty,
155    {
156        if self.is_empty() {
157            function();
158        }
159    }
160
161    pub fn when_not_empty(&self, consumer: impl FnOnce(T))
162    where
163        T: TeaqlEmpty,
164    {
165        if let Some(value) = self.eval().filter(|value| !value.teaql_is_empty()) {
166            consumer(value);
167        }
168    }
169}
170
171impl<R> SafeExpression<R, R>
172where
173    R: Clone + Send + Sync + 'static,
174{
175    pub fn value(root: R) -> Self {
176        Self::new(root, |root| Some(root.clone()))
177    }
178
179    pub fn root(&self) -> &R {
180        &self.root
181    }
182}
183
184impl<R, E> SafeExpression<R, E>
185where
186    R: Send + Sync + 'static,
187    E: BaseEntity + Clone + 'static,
188{
189    pub fn entity_id(self) -> SafeExpression<R, u64> {
190        self.apply(|entity| entity.id())
191    }
192
193    pub fn entity_version(self) -> SafeExpression<R, i64> {
194        self.apply(|entity| entity.version_value())
195    }
196
197    pub fn update_entity_id(self, id: u64) -> SafeExpression<R, E> {
198        self.apply(move |mut entity| {
199            entity.set_id(id);
200            entity
201        })
202    }
203}
204
205impl<R, T> SafeExpression<R, SmartList<T>>
206where
207    R: Send + Sync + 'static,
208    T: Clone + 'static,
209{
210    pub fn size(self) -> SafeExpression<R, usize> {
211        self.apply(|list| list.len())
212    }
213
214    pub fn first(self) -> SafeExpression<R, T> {
215        self.apply_optional(|list| list.first().cloned())
216    }
217
218    pub fn get(self, index: usize) -> SafeExpression<R, T> {
219        self.apply_optional(move |list| list.get(index).cloned())
220    }
221}
222
223#[cfg(test)]
224mod tests {
225    use std::sync::Arc;
226    use std::sync::atomic::{AtomicUsize, Ordering};
227
228    use super::SafeExpression;
229
230    #[test]
231    fn safe_expression_eval_with_uses_the_supplied_root() {
232        let expression = SafeExpression::new(2_i32, |root| Some(root * 3));
233
234        assert_eq!(expression.eval(), Some(6));
235        assert_eq!(expression.eval_with(&4), Some(12));
236    }
237
238    #[test]
239    fn safe_expression_apply_optional_short_circuits_remaining_mappers() {
240        let optional_calls = Arc::new(AtomicUsize::new(0));
241        let remaining_calls = Arc::new(AtomicUsize::new(0));
242
243        let optional_calls_for_mapper = Arc::clone(&optional_calls);
244        let remaining_calls_for_mapper = Arc::clone(&remaining_calls);
245        let expression = SafeExpression::value(5_i32)
246            .apply_optional(move |_| {
247                optional_calls_for_mapper.fetch_add(1, Ordering::SeqCst);
248                None::<i32>
249            })
250            .apply(move |value| {
251                remaining_calls_for_mapper.fetch_add(1, Ordering::SeqCst);
252                value * 2
253            });
254
255        assert_eq!(expression.eval(), None);
256        assert_eq!(optional_calls.load(Ordering::SeqCst), 1);
257        assert_eq!(remaining_calls.load(Ordering::SeqCst), 0);
258    }
259
260    #[test]
261    fn safe_expression_lazy_fallback_and_error_only_run_for_missing_values() {
262        let mut present_fallback_calls = 0;
263        let present = SafeExpression::value(7_i32);
264        assert_eq!(
265            present.or_else_with(|| {
266                present_fallback_calls += 1;
267                9
268            }),
269            7
270        );
271        assert_eq!(present_fallback_calls, 0);
272        assert_eq!(present.or_else_throw(|| "unused error"), Ok(7));
273
274        let missing = SafeExpression::new((), |_| None::<i32>);
275        let mut missing_fallback_calls = 0;
276        assert_eq!(
277            missing.or_else_with(|| {
278                missing_fallback_calls += 1;
279                9
280            }),
281            9
282        );
283        assert_eq!(missing_fallback_calls, 1);
284        assert_eq!(
285            missing.or_else_throw(|| "missing value"),
286            Err("missing value")
287        );
288    }
289
290    #[test]
291    fn safe_expression_callbacks_only_run_for_their_matching_branch() {
292        let present = SafeExpression::value("teaql".to_owned());
293        let mut present_null_calls = 0;
294        let mut present_value = None;
295        present.when_is_null(|| present_null_calls += 1);
296        present.when_is_not_null(|value| present_value = Some(value));
297        assert_eq!(present_null_calls, 0);
298        assert_eq!(present_value.as_deref(), Some("teaql"));
299
300        let missing = SafeExpression::new((), |_| None::<String>);
301        let mut missing_null_calls = 0;
302        let mut missing_value_calls = 0;
303        missing.when_is_null(|| missing_null_calls += 1);
304        missing.when_is_not_null(|_| missing_value_calls += 1);
305        assert_eq!(missing_null_calls, 1);
306        assert_eq!(missing_value_calls, 0);
307    }
308}