Skip to main content

dataflow_rs/expr/
expr.rs

1//! `Expr<T>`: compiled JSONLogic that resolves to a `T`.
2
3use super::config::ValueKind;
4use super::path::Path;
5use super::rules::Shape;
6use crate::base::{Error, ErrorKind, ExprCause};
7use datalogic_rs::Logic;
8use datavalue::{DataValue, NumberValue, OwnedDataValue};
9use serde_json::Value;
10use std::borrow::Cow;
11use std::fmt;
12use std::str::FromStr;
13
14pub(crate) mod sealed {
15    use datalogic_rs::{DataValue, Logic};
16    use datavalue::OwnedDataValue;
17
18    pub trait Sealed {}
19
20    /// The proof a caller is this crate. A `dyn` [`ResolveExpr`] exposes its
21    /// supertraits' methods to any crate holding one, so each method below
22    /// takes a `Crate`, which only this crate can make.
23    ///
24    /// [`ResolveExpr`]: super::ResolveExpr
25    #[derive(Clone, Copy, Debug)]
26    pub struct Crate(());
27
28    /// The one `Crate`.
29    pub(crate) const CRATE: Crate = Crate(());
30
31    /// The seal, and the crate's face of an expression: no datalogic type
32    /// reaches [`ResolveExpr`](super::ResolveExpr)'s public face.
33    pub trait SealedExpr {
34        /// The folded constant as an owned value, when the expression reads
35        /// nothing.
36        fn constant_value(&self, _: Crate) -> Option<OwnedDataValue>;
37
38        /// The compiled logic.
39        fn logic(&self, _: Crate) -> &Logic;
40
41        /// Whether the expression reads a secret anywhere.
42        fn reads_secret(&self, _: Crate) -> bool;
43
44        /// An evaluation's result as the expression's type, then as an
45        /// owned value: the `Eval` error naming the expression when it is
46        /// not of that type.
47        fn value_of(
48            &self,
49            view: &DataValue<'_>,
50            ops: Option<u64>,
51            _: Crate,
52        ) -> crate::Result<OwnedDataValue>;
53    }
54}
55
56/// A type an expression can resolve to.
57///
58/// Sealed: the implementations are [`OwnedDataValue`] (lent as
59/// `Cow<OwnedDataValue>`), [`String`] (`Cow<str>`; a result that is not a
60/// string is its compact JSON), [`u64`] and [`i64`] (themselves: an integer,
61/// or a whole float, in the type's range; above `i64::MAX` an integer is read
62/// as a float and may round; anything else is an error), [`f64`] (itself:
63/// any finite number, the only kind JSON carries; anything else is an
64/// error), [`bool`] (itself; anything else is an error) and [`Path`]
65/// (`Cow<Path>`; a string parsed as a path).
66pub trait FromValue: Sized + Clone + Send + Sync + 'static + sealed::Sealed {
67    /// What kind of value the type is, for a field's schema.
68    #[doc(hidden)]
69    const KIND: ValueKind;
70
71    /// What an evaluation hands back: a folded constant borrowed from the
72    /// expression, a dynamic result owned.
73    type Out<'a>;
74
75    #[doc(hidden)]
76    fn from_view(v: &DataValue<'_>) -> Result<Self, String>;
77    #[doc(hidden)]
78    fn lend(c: &Self) -> Self::Out<'_>;
79    #[doc(hidden)]
80    fn give<'a>(v: Self) -> Self::Out<'a>;
81    /// A folded constant as an owned value, for a resolved config.
82    #[doc(hidden)]
83    fn to_value(c: &Self) -> OwnedDataValue {
84        Self::into_value(c.clone())
85    }
86    /// An evaluated value as an owned one, for a resolved config.
87    #[doc(hidden)]
88    fn into_value(v: Self) -> OwnedDataValue;
89    /// The type's name, for messages.
90    #[doc(hidden)]
91    fn type_name() -> &'static str;
92}
93
94impl sealed::Sealed for OwnedDataValue {}
95impl sealed::Sealed for String {}
96impl sealed::Sealed for u64 {}
97impl sealed::Sealed for i64 {}
98impl sealed::Sealed for f64 {}
99impl sealed::Sealed for bool {}
100impl sealed::Sealed for Path {}
101
102impl FromValue for OwnedDataValue {
103    const KIND: ValueKind = ValueKind::Any;
104    type Out<'a> = Cow<'a, Self>;
105    fn from_view(v: &DataValue<'_>) -> Result<Self, String> {
106        Ok(v.to_owned())
107    }
108    fn lend(c: &Self) -> Self::Out<'_> {
109        Cow::Borrowed(c)
110    }
111    fn give<'a>(v: Self) -> Self::Out<'a> {
112        Cow::Owned(v)
113    }
114    fn into_value(v: Self) -> OwnedDataValue {
115        v
116    }
117    fn type_name() -> &'static str {
118        "a value"
119    }
120}
121
122impl FromValue for String {
123    const KIND: ValueKind = ValueKind::String;
124    type Out<'a> = Cow<'a, str>;
125    /// A string's contents; anything else as compact JSON (v3's
126    /// `resolve_string`).
127    fn from_view(v: &DataValue<'_>) -> Result<Self, String> {
128        Ok(match v {
129            DataValue::String(s) => (*s).to_owned(),
130            other => other.to_string(),
131        })
132    }
133    fn lend(c: &Self) -> Self::Out<'_> {
134        Cow::Borrowed(c)
135    }
136    fn give<'a>(v: Self) -> Self::Out<'a> {
137        Cow::Owned(v)
138    }
139    fn into_value(v: Self) -> OwnedDataValue {
140        OwnedDataValue::String(v)
141    }
142    fn type_name() -> &'static str {
143        "a string"
144    }
145}
146
147impl FromValue for u64 {
148    const KIND: ValueKind = ValueKind::Number;
149    type Out<'a> = Self;
150    fn from_view(v: &DataValue<'_>) -> Result<Self, String> {
151        match v {
152            DataValue::Number(n) => n
153                .as_u64()
154                .ok_or_else(|| "a negative or fractional number is not a u64".to_owned()),
155            other => Err(format!("{} is not a u64", Shape::of_view(other).describe())),
156        }
157    }
158    fn lend(c: &Self) -> Self::Out<'_> {
159        *c
160    }
161    fn give<'a>(v: Self) -> Self::Out<'a> {
162        v
163    }
164    fn into_value(v: Self) -> OwnedDataValue {
165        v.into()
166    }
167    fn type_name() -> &'static str {
168        "a u64"
169    }
170}
171
172impl FromValue for i64 {
173    const KIND: ValueKind = ValueKind::Number;
174    type Out<'a> = Self;
175    /// An integer in range, or a whole float that is one, as [`u64`]'s.
176    fn from_view(v: &DataValue<'_>) -> Result<Self, String> {
177        match v {
178            DataValue::Number(n) => n
179                .as_i64()
180                .ok_or_else(|| "an out-of-range or fractional number is not an i64".to_owned()),
181            other => Err(format!(
182                "{} is not an i64",
183                Shape::of_view(other).describe()
184            )),
185        }
186    }
187    fn lend(c: &Self) -> Self::Out<'_> {
188        *c
189    }
190    fn give<'a>(v: Self) -> Self::Out<'a> {
191        v
192    }
193    fn into_value(v: Self) -> OwnedDataValue {
194        v.into()
195    }
196    fn type_name() -> &'static str {
197        "an i64"
198    }
199}
200
201impl FromValue for f64 {
202    const KIND: ValueKind = ValueKind::Number;
203    type Out<'a> = Self;
204    /// Any finite number: an infinity, which arithmetic can make, has no
205    /// JSON spelling and would be written as `null`.
206    fn from_view(v: &DataValue<'_>) -> Result<Self, String> {
207        match v {
208            DataValue::Number(n) if n.as_f64().is_finite() => Ok(n.as_f64()),
209            DataValue::Number(_) => Err("a non-finite number is not a finite f64".to_owned()),
210            other => Err(format!(
211                "{} is not an f64",
212                Shape::of_view(other).describe()
213            )),
214        }
215    }
216    fn lend(c: &Self) -> Self::Out<'_> {
217        *c
218    }
219    fn give<'a>(v: Self) -> Self::Out<'a> {
220        v
221    }
222    /// A float, as the JSON bridge builds one: `2.0` stays `2.0`.
223    fn into_value(v: Self) -> OwnedDataValue {
224        OwnedDataValue::Number(NumberValue::Float(v))
225    }
226    fn type_name() -> &'static str {
227        "an f64"
228    }
229}
230
231impl FromValue for bool {
232    const KIND: ValueKind = ValueKind::Bool;
233    type Out<'a> = Self;
234    fn from_view(v: &DataValue<'_>) -> Result<Self, String> {
235        match v {
236            DataValue::Bool(b) => Ok(*b),
237            other => Err(format!(
238                "{} is not a bool",
239                Shape::of_view(other).describe()
240            )),
241        }
242    }
243    fn lend(c: &Self) -> Self::Out<'_> {
244        *c
245    }
246    fn give<'a>(v: Self) -> Self::Out<'a> {
247        v
248    }
249    fn into_value(v: Self) -> OwnedDataValue {
250        v.into()
251    }
252    fn type_name() -> &'static str {
253        "a bool"
254    }
255}
256
257impl FromValue for Path {
258    const KIND: ValueKind = ValueKind::Path;
259    type Out<'a> = Cow<'a, Self>;
260    fn from_view(v: &DataValue<'_>) -> Result<Self, String> {
261        match v {
262            DataValue::String(s) => Self::from_str(s).map_err(|e| e.message().to_owned()),
263            other => Err(format!(
264                "{} is not a path",
265                Shape::of_view(other).describe()
266            )),
267        }
268    }
269    fn lend(c: &Self) -> Self::Out<'_> {
270        Cow::Borrowed(c)
271    }
272    fn give<'a>(v: Self) -> Self::Out<'a> {
273        Cow::Owned(v)
274    }
275    fn into_value(v: Self) -> OwnedDataValue {
276        OwnedDataValue::String(v.to_string())
277    }
278    fn type_name() -> &'static str {
279        "a path"
280    }
281}
282
283/// What an evaluation returns: the value and what it cost.
284#[derive(Clone, Debug, PartialEq)]
285#[non_exhaustive]
286pub struct Evaluated<T> {
287    /// The result.
288    pub value: T,
289    /// Operations the evaluation spent: `Some` when the `budget` feature is on
290    /// (zero for a folded constant), `None` otherwise.
291    pub ops: Option<u64>,
292}
293
294/// Compiled JSONLogic resolving to a `T`, made by
295/// [`ExprEngine::compile`](super::ExprEngine::compile).
296///
297/// An expression that reads nothing folds at compile into a constant, and
298/// every evaluation lends that constant from the expression, never cloning it.
299pub struct Expr<T: FromValue = OwnedDataValue> {
300    pub(super) raw: Value,
301    pub(super) logic: Logic,
302    pub(super) constant: Option<T>,
303    pub(super) reads_secret: bool,
304}
305
306/// `raw` as compact JSON, truncated at 120 characters so arbitrarily large
307/// expressions cannot bloat error records, traces or logs.
308pub(crate) fn snippet(raw: &Value) -> String {
309    const MAX: usize = 120;
310    let text = raw.to_string();
311    if text.len() <= MAX {
312        return text;
313    }
314    let end = text.floor_char_boundary(MAX);
315    format!("{}…", &text[..end])
316}
317
318impl Expr<OwnedDataValue> {
319    /// Whether this expression folds at compile time to the literal `true`:
320    /// the one definition shared by lowering (`compile.rs`) and the
321    /// `UNGUARDED_VALIDATION` check (`checks.rs`).
322    pub(crate) fn is_trivially_true(&self) -> bool {
323        self.constant() == Some(&OwnedDataValue::Bool(true))
324    }
325}
326
327impl<T: FromValue> Expr<T> {
328    /// The folded constant, if the expression reads nothing.
329    pub fn constant(&self) -> Option<&T> {
330        self.constant.as_ref()
331    }
332
333    /// The authored JSON.
334    pub fn raw(&self) -> &Value {
335        &self.raw
336    }
337
338    /// The compiled logic, for an evaluation against the run view.
339    pub(crate) fn logic(&self) -> &Logic {
340        &self.logic
341    }
342
343    /// Whether the expression reads a secret anywhere.
344    pub(crate) fn reads_secret(&self) -> bool {
345        self.reads_secret
346    }
347
348    /// An evaluation's result as a `T`, or the [`ErrorKind::Eval`] error
349    /// naming this expression, with `ops_spent` attached to its structured
350    /// [`ExprCause`]. The reason names only the value's kind, and an
351    /// expression that reads a secret gives only the type it expected.
352    pub(crate) fn convert(&self, view: &DataValue<'_>, ops: Option<u64>) -> crate::Result<T> {
353        T::from_view(view).map_err(|why| {
354            let shown = snippet(&self.raw);
355            let message = if self.reads_secret {
356                format!("`{shown}` did not give {}", T::type_name())
357            } else {
358                format!("`{shown}` did not give {}: {why}", T::type_name())
359            };
360            Error::new(ErrorKind::Eval, message).with_expr_cause(ExprCause::type_mismatch(ops))
361        })
362    }
363}
364
365impl<T: FromValue> fmt::Debug for Expr<T> {
366    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
367        f.debug_struct("Expr")
368            .field("raw", &self.raw)
369            .field("folded", &self.constant.is_some())
370            .finish()
371    }
372}
373
374/// The object-safe face of an [`Expr`], which a
375/// [`Resolver`](super::Resolver) evaluates: its logic, and its type's
376/// conversion to an owned value (`u64`, `i64` and `f64` as numbers, `bool` as
377/// a boolean, `String` as a string, [`Path`] as its dotted text, and
378/// `OwnedDataValue` as itself).
379///
380/// Sealed: every `Expr<T>` is one, and nothing else is. Its evaluation
381/// is the crate's, so no datalogic type is part of it:
382///
383/// ```compile_fail
384/// fn logic(expr: &dyn dataflow_rs::expr::ResolveExpr) {
385///     let _ = expr.logic();
386/// }
387/// ```
388pub trait ResolveExpr: Send + Sync + sealed::SealedExpr {
389    /// The authored JSON.
390    fn raw(&self) -> &Value;
391}
392
393impl<T: FromValue> ResolveExpr for Expr<T> {
394    fn raw(&self) -> &Value {
395        &self.raw
396    }
397}
398
399impl<T: FromValue> sealed::SealedExpr for Expr<T> {
400    fn constant_value(&self, _: sealed::Crate) -> Option<OwnedDataValue> {
401        self.constant.as_ref().map(T::to_value)
402    }
403
404    fn logic(&self, _: sealed::Crate) -> &Logic {
405        &self.logic
406    }
407
408    fn reads_secret(&self, _: sealed::Crate) -> bool {
409        self.reads_secret
410    }
411
412    fn value_of(
413        &self,
414        view: &DataValue<'_>,
415        ops: Option<u64>,
416        _: sealed::Crate,
417    ) -> crate::Result<OwnedDataValue> {
418        self.convert(view, ops).map(T::into_value)
419    }
420}