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