Skip to main content

rust_runes/
facts.rs

1use chrono::{DateTime, FixedOffset};
2use serde::de::DeserializeOwned;
3use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5
6/// A value rules can read and write. Integers and floats are distinct, as in Grule:
7/// integer literals and arithmetic stay integers, and `/` always produces a float.
8///
9/// With serde, a `FactValue` uses the plain representation of its contents: strings,
10/// numbers, booleans, `null`, arrays and maps, with times as RFC 3339 strings. When
11/// deserializing, integers that fit in an `i64` become `Integer` and other numbers
12/// `Float`; strings always stay strings.
13#[derive(Debug, Clone)]
14pub enum FactValue {
15    /// A string.
16    String(String),
17    /// An integer.
18    Integer(i64),
19    /// A floating-point number.
20    Float(f64),
21    /// `true` or `false`.
22    Boolean(bool),
23    /// A point in time, with its UTC offset.
24    Time(DateTime<FixedOffset>),
25    /// Named fields, like a struct or map.
26    Object(HashMap<String, FactValue>),
27    /// A list of values.
28    Array(Vec<FactValue>),
29    /// `nil`: no value.
30    Null,
31}
32
33/// Numbers compare by value across integer and float (`Integer(2) == Float(2.0)`),
34/// matching how `==` behaves in rules. Times compare by the instant they denote.
35impl PartialEq for FactValue {
36    fn eq(&self, other: &Self) -> bool {
37        use FactValue::*;
38        match (self, other) {
39            (String(a), String(b)) => a == b,
40            (Integer(a), Integer(b)) => a == b,
41            (Float(a), Float(b)) => a == b,
42            (Integer(a), Float(b)) | (Float(b), Integer(a)) => *a as f64 == *b,
43            (Boolean(a), Boolean(b)) => a == b,
44            (Time(a), Time(b)) => a == b,
45            (Object(a), Object(b)) => a == b,
46            (Array(a), Array(b)) => a == b,
47            (Null, Null) => true,
48            _ => false,
49        }
50    }
51}
52
53impl FactValue {
54    /// The value as a float, for either kind of number.
55    pub fn as_number(&self) -> Option<f64> {
56        match self {
57            FactValue::Integer(n) => Some(*n as f64),
58            FactValue::Float(n) => Some(*n),
59            _ => None,
60        }
61    }
62
63    /// The value if it is an integer.
64    pub fn as_integer(&self) -> Option<i64> {
65        match self {
66            FactValue::Integer(n) => Some(*n),
67            _ => None,
68        }
69    }
70
71    /// The value if it is a string.
72    pub fn as_string(&self) -> Option<&str> {
73        match self {
74            FactValue::String(s) => Some(s),
75            _ => None,
76        }
77    }
78
79    /// The value if it is a boolean.
80    pub fn as_boolean(&self) -> Option<bool> {
81        match self {
82            FactValue::Boolean(b) => Some(*b),
83            _ => None,
84        }
85    }
86
87    /// The value if it is a time.
88    pub fn as_time(&self) -> Option<&DateTime<FixedOffset>> {
89        match self {
90            FactValue::Time(t) => Some(t),
91            _ => None,
92        }
93    }
94
95    /// The fields if the value is an object.
96    pub fn as_object(&self) -> Option<&HashMap<String, FactValue>> {
97        match self {
98            FactValue::Object(obj) => Some(obj),
99            _ => None,
100        }
101    }
102
103    /// The items if the value is an array.
104    pub fn as_array(&self) -> Option<&[FactValue]> {
105        match self {
106            FactValue::Array(items) => Some(items),
107            _ => None,
108        }
109    }
110
111    /// The value's type as rules and error messages describe it: `"string"`,
112    /// `"integer"`, `"float"`, `"boolean"`, `"time"`, `"object"`, `"array"` or `"nil"`.
113    pub fn type_name(&self) -> &'static str {
114        match self {
115            FactValue::String(_) => "string",
116            FactValue::Integer(_) => "integer",
117            FactValue::Float(_) => "float",
118            FactValue::Boolean(_) => "boolean",
119            FactValue::Time(_) => "time",
120            FactValue::Object(_) => "object",
121            FactValue::Array(_) => "array",
122            FactValue::Null => "nil",
123        }
124    }
125
126    /// Whether the value is "truthy": not `nil`, `false`, zero, or empty. (Rule conditions
127    /// themselves require booleans, as in Grule.)
128    pub fn is_truthy(&self) -> bool {
129        match self {
130            FactValue::Boolean(b) => *b,
131            FactValue::Integer(n) => *n != 0,
132            FactValue::Float(n) => *n != 0.0,
133            FactValue::String(s) => !s.is_empty(),
134            FactValue::Time(_) => true,
135            FactValue::Array(arr) => !arr.is_empty(),
136            FactValue::Object(obj) => !obj.is_empty(),
137            FactValue::Null => false,
138        }
139    }
140
141    /// Converts any serializable value (typically a struct) into a fact value.
142    /// Structs and maps become objects, sequences become arrays, and integers that
143    /// fit in an `i64` become integers. Time types serialize as strings.
144    pub fn from_serialize<T: Serialize + ?Sized>(value: &T) -> Result<FactValue, String> {
145        let _converting = serde_time::ConvertingGuard::enter();
146        serde_json::to_value(value)
147            .map(from_json)
148            .map_err(|e| format!("Cannot convert to a fact: {}", e))
149    }
150
151    /// Converts this value back into a deserializable type. Times become RFC 3339
152    /// strings, and non-finite floats become null.
153    pub fn deserialize<T: DeserializeOwned>(&self) -> Result<T, String> {
154        serde_json::from_value(to_json(self)).map_err(|e| format!("Cannot convert fact: {}", e))
155    }
156}
157
158fn from_json(value: serde_json::Value) -> FactValue {
159    use serde_json::Value;
160    match value {
161        Value::Null => FactValue::Null,
162        Value::Bool(b) => FactValue::Boolean(b),
163        Value::Number(n) => match n.as_i64() {
164            Some(i) => FactValue::Integer(i),
165            None => FactValue::Float(n.as_f64().unwrap_or(f64::NAN)),
166        },
167        Value::String(s) => match serde_time::parse_marked(&s) {
168            Some(time) => FactValue::Time(time),
169            None => FactValue::String(s),
170        },
171        Value::Array(items) => FactValue::Array(items.into_iter().map(from_json).collect()),
172        Value::Object(obj) => {
173            FactValue::Object(obj.into_iter().map(|(k, v)| (k, from_json(v))).collect())
174        }
175    }
176}
177
178fn to_json(value: &FactValue) -> serde_json::Value {
179    use serde_json::Value;
180    match value {
181        FactValue::Null => Value::Null,
182        FactValue::Boolean(b) => Value::Bool(*b),
183        FactValue::Integer(n) => Value::from(*n),
184        FactValue::Float(n) => serde_json::Number::from_f64(*n).map_or(Value::Null, Value::Number),
185        FactValue::String(s) => Value::String(s.clone()),
186        FactValue::Time(t) => Value::String(t.to_rfc3339()),
187        FactValue::Array(items) => Value::Array(items.iter().map(to_json).collect()),
188        FactValue::Object(obj) => {
189            Value::Object(obj.iter().map(|(k, v)| (k.clone(), to_json(v))).collect())
190        }
191    }
192}
193
194impl Serialize for FactValue {
195    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
196        use serde::ser::{SerializeMap, SerializeSeq};
197        match self {
198            FactValue::Null => serializer.serialize_unit(),
199            FactValue::Boolean(b) => serializer.serialize_bool(*b),
200            FactValue::Integer(n) => serializer.serialize_i64(*n),
201            FactValue::Float(n) => serializer.serialize_f64(*n),
202            FactValue::String(s) => serializer.serialize_str(s),
203            FactValue::Time(t) => serializer.serialize_str(&t.to_rfc3339()),
204            FactValue::Array(items) => {
205                let mut seq = serializer.serialize_seq(Some(items.len()))?;
206                for item in items {
207                    seq.serialize_element(item)?;
208                }
209                seq.end()
210            }
211            FactValue::Object(fields) => {
212                let mut map = serializer.serialize_map(Some(fields.len()))?;
213                for (key, value) in fields {
214                    map.serialize_entry(key, value)?;
215                }
216                map.end()
217            }
218        }
219    }
220}
221
222impl<'de> Deserialize<'de> for FactValue {
223    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
224        struct Visitor;
225
226        impl<'de> serde::de::Visitor<'de> for Visitor {
227            type Value = FactValue;
228
229            fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
230                f.write_str("a string, number, boolean, null, array or map")
231            }
232            fn visit_unit<E>(self) -> Result<FactValue, E> {
233                Ok(FactValue::Null)
234            }
235            fn visit_none<E>(self) -> Result<FactValue, E> {
236                Ok(FactValue::Null)
237            }
238            fn visit_some<D: serde::Deserializer<'de>>(self, d: D) -> Result<FactValue, D::Error> {
239                <FactValue as Deserialize>::deserialize(d)
240            }
241            fn visit_bool<E>(self, b: bool) -> Result<FactValue, E> {
242                Ok(FactValue::Boolean(b))
243            }
244            fn visit_i64<E>(self, n: i64) -> Result<FactValue, E> {
245                Ok(FactValue::Integer(n))
246            }
247            fn visit_u64<E>(self, n: u64) -> Result<FactValue, E> {
248                Ok(i64::try_from(n).map_or(FactValue::Float(n as f64), FactValue::Integer))
249            }
250            fn visit_f64<E>(self, n: f64) -> Result<FactValue, E> {
251                Ok(FactValue::Float(n))
252            }
253            fn visit_str<E>(self, s: &str) -> Result<FactValue, E> {
254                Ok(FactValue::String(s.to_string()))
255            }
256            fn visit_string<E>(self, s: String) -> Result<FactValue, E> {
257                Ok(FactValue::String(s))
258            }
259            fn visit_seq<A: serde::de::SeqAccess<'de>>(
260                self,
261                mut seq: A,
262            ) -> Result<FactValue, A::Error> {
263                let mut items = Vec::new();
264                while let Some(item) = seq.next_element()? {
265                    items.push(item);
266                }
267                Ok(FactValue::Array(items))
268            }
269            fn visit_map<A: serde::de::MapAccess<'de>>(
270                self,
271                mut map: A,
272            ) -> Result<FactValue, A::Error> {
273                let mut fields = HashMap::new();
274                while let Some((key, value)) = map.next_entry::<String, FactValue>()? {
275                    fields.insert(key, value);
276                }
277                Ok(FactValue::Object(fields))
278            }
279        }
280
281        deserializer.deserialize_any(Visitor)
282    }
283}
284
285impl From<i64> for FactValue {
286    fn from(value: i64) -> Self {
287        FactValue::Integer(value)
288    }
289}
290
291impl From<f64> for FactValue {
292    fn from(value: f64) -> Self {
293        FactValue::Float(value)
294    }
295}
296
297impl From<bool> for FactValue {
298    fn from(value: bool) -> Self {
299        FactValue::Boolean(value)
300    }
301}
302
303impl From<&str> for FactValue {
304    fn from(value: &str) -> Self {
305        FactValue::String(value.to_string())
306    }
307}
308
309impl From<String> for FactValue {
310    fn from(value: String) -> Self {
311        FactValue::String(value)
312    }
313}
314
315impl From<DateTime<FixedOffset>> for FactValue {
316    fn from(value: DateTime<FixedOffset>) -> Self {
317        FactValue::Time(value)
318    }
319}
320
321/// A named value rules can refer to, e.g. `Ride` in `Ride.Distance > 5000`.
322#[derive(Debug, Clone, Serialize, Deserialize)]
323pub struct Fact {
324    /// The name rules use for this fact.
325    pub name: String,
326    /// The fact's value.
327    pub value: FactValue,
328}
329
330impl Fact {
331    /// A fact with the given name and value.
332    pub fn new(name: String, value: FactValue) -> Self {
333        Self { name, value }
334    }
335
336    /// A field of an object fact.
337    pub fn get_field(&self, field_name: &str) -> Option<&FactValue> {
338        match &self.value {
339            FactValue::Object(obj) => obj.get(field_name),
340            _ => None,
341        }
342    }
343
344    /// Sets a field of an object fact. Fails if the fact isn't an object.
345    pub fn set_field(&mut self, field_name: String, value: FactValue) -> Result<(), String> {
346        match &mut self.value {
347            FactValue::Object(obj) => {
348                obj.insert(field_name, value);
349                Ok(())
350            }
351            _ => Err("Cannot set field on non-object fact".to_string()),
352        }
353    }
354
355    /// Converts this fact's value back into a deserializable type,
356    /// e.g. the struct it was created from with `Fact::from_serialize`.
357    pub fn deserialize<T: DeserializeOwned>(&self) -> Result<T, String> {
358        self.value.deserialize()
359    }
360}
361
362// Convenience methods for creating facts
363impl Fact {
364    /// An object fact with the given fields.
365    pub fn from_object(name: String, obj: HashMap<String, FactValue>) -> Self {
366        Self::new(name, FactValue::Object(obj))
367    }
368
369    /// Creates a fact from any serializable value, typically a struct.
370    pub fn from_serialize<T: Serialize + ?Sized>(name: &str, value: &T) -> Result<Self, String> {
371        Ok(Self::new(
372            name.to_string(),
373            FactValue::from_serialize(value)?,
374        ))
375    }
376
377    /// A string fact.
378    pub fn string_fact(name: String, value: String) -> Self {
379        Self::new(name, FactValue::String(value))
380    }
381
382    /// An integer fact.
383    pub fn integer_fact(name: String, value: i64) -> Self {
384        Self::new(name, FactValue::Integer(value))
385    }
386
387    /// A float fact.
388    pub fn number_fact(name: String, value: f64) -> Self {
389        Self::new(name, FactValue::Float(value))
390    }
391
392    /// A boolean fact.
393    pub fn boolean_fact(name: String, value: bool) -> Self {
394        Self::new(name, FactValue::Boolean(value))
395    }
396}
397
398/// Serde support for time fields in fact structs. Annotate `DateTime` fields with
399/// `#[serde(with = "rust_runes::serde_time")]` (or `serde_time::option` for
400/// `Option<DateTime<_>>`) and `Fact::from_serialize` turns them into time values
401/// that rules can compare and pass to the time functions. Without it, chrono
402/// serializes times as strings, and they reach rules as strings.
403///
404/// Outside fact conversion the fields serialize as ordinary RFC 3339 strings, so the
405/// annotation doesn't change your other serialization.
406pub mod serde_time {
407    use chrono::{DateTime, FixedOffset, SecondsFormat, TimeZone};
408    use serde::de::Error as _;
409    use serde::{Deserialize, Deserializer, Serializer};
410    use std::cell::Cell;
411
412    /// Prefix that tells fact conversion a string is a time. It only appears while
413    /// `FactValue::from_serialize` is running.
414    const MARKER: &str = "\u{1}rust-runes:time\u{1}";
415
416    thread_local! {
417        static CONVERTING: Cell<bool> = const { Cell::new(false) };
418    }
419
420    /// Marks the current thread as converting values into facts, until dropped.
421    pub(crate) struct ConvertingGuard(bool);
422
423    impl ConvertingGuard {
424        pub(crate) fn enter() -> Self {
425            ConvertingGuard(CONVERTING.replace(true))
426        }
427    }
428
429    impl Drop for ConvertingGuard {
430        fn drop(&mut self) {
431            CONVERTING.set(self.0);
432        }
433    }
434
435    pub(crate) fn parse_marked(text: &str) -> Option<DateTime<FixedOffset>> {
436        DateTime::parse_from_rfc3339(text.strip_prefix(MARKER)?).ok()
437    }
438
439    /// Serializes a time field (see the module docs).
440    pub fn serialize<Tz: TimeZone, S: Serializer>(
441        time: &DateTime<Tz>,
442        serializer: S,
443    ) -> Result<S::Ok, S::Error> {
444        let text = time
445            .fixed_offset()
446            .to_rfc3339_opts(SecondsFormat::AutoSi, true);
447        if CONVERTING.get() {
448            serializer.serialize_str(&format!("{}{}", MARKER, text))
449        } else {
450            serializer.serialize_str(&text)
451        }
452    }
453
454    /// Deserializes a time field from an RFC 3339 string.
455    pub fn deserialize<'de, Tz, D>(deserializer: D) -> Result<DateTime<Tz>, D::Error>
456    where
457        Tz: TimeZone,
458        DateTime<Tz>: From<DateTime<FixedOffset>>,
459        D: Deserializer<'de>,
460    {
461        let text = String::deserialize(deserializer)?;
462        let text = text.strip_prefix(MARKER).unwrap_or(&text);
463        DateTime::parse_from_rfc3339(text)
464            .map(Into::into)
465            .map_err(D::Error::custom)
466    }
467
468    /// For `Option<DateTime<_>>` fields: `#[serde(with = "rust_runes::serde_time::option")]`.
469    pub mod option {
470        use super::*;
471
472        /// Serializes an optional time field.
473        pub fn serialize<Tz: TimeZone, S: Serializer>(
474            time: &Option<DateTime<Tz>>,
475            serializer: S,
476        ) -> Result<S::Ok, S::Error> {
477            match time {
478                Some(time) => super::serialize(time, serializer),
479                None => serializer.serialize_none(),
480            }
481        }
482
483        /// Deserializes an optional time field.
484        pub fn deserialize<'de, Tz, D>(deserializer: D) -> Result<Option<DateTime<Tz>>, D::Error>
485        where
486            Tz: TimeZone,
487            DateTime<Tz>: From<DateTime<FixedOffset>>,
488            D: Deserializer<'de>,
489        {
490            match Option::<String>::deserialize(deserializer)? {
491                Some(text) => {
492                    let text = text.strip_prefix(MARKER).unwrap_or(&text);
493                    DateTime::parse_from_rfc3339(text)
494                        .map(|t| Some(t.into()))
495                        .map_err(D::Error::custom)
496                }
497                None => Ok(None),
498            }
499        }
500    }
501}