stepflow_data/
value.rs

1//! [`Value`]s store data within StepFlow. They can support high-level types such as [`EmailValue`] by validating values
2//! on creation, typically with a [`<YourValue>::try_new`](EmailValue::try_new) constructor.
3//!
4//! Every value is expected to support one of the fixed [`BaseValue`] which is used to manage persistence across the system.
5//! Often this is done by storing the actual value internally as a BaseValue.
6//!
7//! When needed, they can be downcast to their original type via `Value::downcast` and `Value::is`.
8//!
9//! # Examples
10//! ```
11//! # use stepflow_data::value::EmailValue;
12//! assert!(matches!(EmailValue::try_new("bad email"), Err(_)));
13//! assert!(matches!(EmailValue::try_new("test@stepflow.dev"), Ok(_)));
14//! ```
15
16use std::fmt::Debug;
17use super::{BaseValue, InvalidValue};
18
19pub trait Value: Debug + Sync + Send + stepflow_base::as_any::AsAny {
20  fn get_baseval(&self) -> BaseValue;
21  fn clone_box(&self) -> Box<dyn Value>;
22  fn eq_box(&self, other: &Box<dyn Value>) -> bool;
23}
24
25// implement downcast helpers that have trait bounds to make it a little safer
26impl dyn Value {
27  pub fn downcast<T>(&self) -> Option<&T>
28    where T: Value + std::any::Any
29  {
30    self.as_any().downcast_ref::<T>()
31  }
32  pub fn is<T>(&self) -> bool 
33    where T: Value + std::any::Any
34  {
35    self.as_any().is::<T>()
36  }
37}
38
39impl Clone for Box<dyn Value> {
40    fn clone(&self) -> Box<dyn Value> {
41        self.clone_box()
42    }
43}
44
45impl PartialEq for Box<dyn Value> {
46    fn eq(&self, other: &Box<dyn Value>) -> bool {
47      self.eq_box(other)
48    }
49}
50
51#[cfg(feature = "serde-support")]
52impl serde::Serialize for Box<dyn Value> {
53    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
54        where S: serde::Serializer
55    {
56      match self.get_baseval() {
57          BaseValue::String(s) => s.serialize(serializer),
58          BaseValue::Boolean(b) => b.serialize(serializer),
59          BaseValue::Float(float) => float.serialize(serializer),
60      }
61    }
62}
63
64#[macro_use]
65macro_rules! define_value_impl {
66  ($name:ident) => {
67    impl Value for $name {
68      fn get_baseval(&self) -> BaseValue {
69        self.val.clone().into()
70      }
71      fn clone_box(&self) -> Box<dyn Value> {
72        Box::new(self.clone())
73      }
74      fn eq_box(&self, other: &Box<dyn Value>) -> bool {
75        // check type is same
76        if !other.is::<Self>() {
77          return false;
78        }
79
80        // check baseval is same
81        self.get_baseval() == other.get_baseval()
82      }
83    }
84  }
85}
86
87#[macro_use]
88macro_rules! define_base_value {
89  ($name:ident, $basetype:ident) => {
90    #[derive(Debug, PartialEq, Clone)]
91    pub struct $name {
92      val: $basetype,
93    }
94
95    impl $name {
96      pub fn val(&self) -> &$basetype {
97        &self.val
98      }
99      pub fn boxed(self) -> Box<dyn Value> {
100        Box::new(self)
101      }
102    }
103
104    define_value_impl!($name);
105  };
106}
107
108#[macro_use]
109macro_rules! define_value {
110  ($name:ident, $basetype:ident) => {
111    define_base_value!($name, $basetype);
112    impl $name {
113      pub fn new(val: $basetype) -> Self {
114        $name { val }
115      }
116    }
117  };
118
119  ($name:ident, $basetype:ident, $validate_fn:ident) => {
120    define_base_value!($name, $basetype);
121    impl $name {
122      pub fn try_new(val: $basetype) -> Result<Self, InvalidValue> {
123        Self::$validate_fn(&val)?;
124        Ok(Self { val })
125      }
126    }
127  };
128}
129
130mod valid_value;
131pub use valid_value::ValidVal;
132
133mod string_value;
134pub use string_value::StringValue;
135
136mod email_value;
137pub use email_value::EmailValue;
138
139mod bool_value;
140pub use bool_value::BoolValue;
141
142mod true_value;
143pub use true_value::TrueValue;
144
145
146#[cfg(test)]
147mod tests {
148  use super::{EmailValue, Value, StringValue, TrueValue};
149
150  #[test]
151  fn val_downcast() {
152    // try with reference
153    let strval = StringValue::try_new("hi").unwrap();
154    let r: &(dyn Value + 'static) = &strval;
155    assert!(r.as_any().is::<StringValue>());
156
157    // try with box ... if it fails, we're getting AsAny of the Box<T> as opposed to T
158    let val: Box<dyn Value> = Box::new(strval.clone());
159    assert!(val.as_any().is::<StringValue>());
160    assert!(val.as_ref().as_any().is::<StringValue>());
161    let stringval: Option<&StringValue> = val.downcast::<StringValue>();
162    assert!(matches!(stringval, Some(_)));
163
164    // try our helper fn
165    assert_eq!(val.downcast::<StringValue>().unwrap().val(), "hi");
166    assert_eq!(val.is::<StringValue>(), true);
167    assert_eq!(val.downcast::<EmailValue>(), None);
168    assert_eq!(val.is::<EmailValue>(), false);
169  }
170
171  #[test]
172  fn partial_eq() {
173    const EMAIL: &str = "a@b.com";
174    let true_val: Box<dyn Value> = TrueValue::new().boxed();
175    let email_val: Box<dyn Value> = EmailValue::try_new(EMAIL).unwrap().boxed();
176    let string_val: Box<dyn Value> = StringValue::try_new(EMAIL).unwrap().boxed();
177    assert!(email_val.clone() == email_val.clone());  // same thing
178    assert!(true_val != email_val.clone());           // different types
179    assert!(email_val.clone() != string_val);         // different types, same base value
180  }
181}