stepflow_data/
var.rs

1//! [`Var`]s are placeholders for [`Value`]s. They can be used to define what values are needed
2//! later without creating the value.
3//!
4//! When needed, they can be downcast to their original type via `Var::downcast` and `Var::is`.
5use stepflow_base::{ObjectStoreContent, IdError, generate_id_type};
6use super::InvalidValue;
7use super::value::Value;
8
9generate_id_type!(VarId);
10
11pub trait Var: std::fmt::Debug + stepflow_base::as_any::AsAny {
12  fn id(&self) -> &VarId;
13  fn value_from_str(&self, s: &str) -> Result<Box<dyn Value>, InvalidValue>;
14  fn validate_val_type(&self, val: &Box<dyn Value>) -> Result<(), InvalidValue>;
15}
16
17// implement downcast helpers that have trait bounds to make it a little safer
18impl dyn Var + Send + Sync {
19  pub fn downcast<T>(&self) -> Option<&T>
20    where T: Var + std::any::Any
21  {
22    self.as_any().downcast_ref::<T>()
23  }
24  pub fn is<T>(&self) -> bool 
25    where T: Var + std::any::Any
26  {
27    self.as_any().is::<T>()
28  }
29}
30
31impl ObjectStoreContent for Box<dyn Var + Sync + Send> {
32  type IdType = VarId;
33
34  fn new_id(id_val: u16) -> Self::IdType {
35    VarId::new(id_val)
36  }
37
38  fn id(&self) -> &Self::IdType {
39    self.as_ref().id()
40  }
41}
42
43macro_rules! define_var {
44  ($name:ident, $valuetype:ident) => {
45
46    #[derive(Debug)]
47    pub struct $name {
48      id: VarId,
49    }
50    impl $name {
51      /// Create a new var
52      pub fn new(id: VarId) -> Self {
53        Self { id }
54      }
55
56      /// Box the value
57      pub fn boxed(self) -> Box<dyn Var + Send + Sync> {
58        Box::new(self)
59      }
60    }
61    impl Var for $name {
62      /// Gets the ID
63      fn id(&self) -> &VarId { &self.id }
64
65      /// Convert a &str to this Var's corresponding value
66      fn value_from_str(&self, s: &str) -> Result<Box<dyn Value>, InvalidValue> {
67        Ok(Box::new(s.parse::<$valuetype>()?) as Box<dyn Value>)
68      }
69
70      /// Validate the value type corresponds to this Var
71      fn validate_val_type(&self, val: &Box<dyn Value>) -> Result<(), InvalidValue> {
72        if val.is::<$valuetype>() {
73          Ok(())
74        } else {
75          Err(InvalidValue::WrongType)
76        }
77      }
78    }
79  };
80}
81
82use super::value::EmailValue;
83define_var!(EmailVar, EmailValue);
84
85use super::value::StringValue;
86define_var!(StringVar, StringValue);
87
88use super::value::TrueValue;
89define_var!(TrueVar, TrueValue);
90
91use super::value::BoolValue;
92define_var!(BoolVar, BoolValue);
93
94
95#[cfg(test)]
96pub fn test_var_val() -> (Box<dyn Var + Send + Sync>, Box<dyn Value>) {
97  let var = Box::new(StringVar::new(stepflow_test_util::test_id!(VarId)));
98  let val: Box<dyn Value> = StringValue::try_new("test").unwrap().boxed();
99  (var, val)
100}
101
102#[cfg(test)]
103mod tests {
104  use stepflow_test_util::test_id;
105  use crate::value::{Value, StringValue, EmailValue};
106  use super::{Var, VarId, EmailVar, StringVar, InvalidValue};
107
108  #[test]
109  fn validate_val_type() {
110    let email_addr = "is@email.com";
111    let email_var = EmailVar::new(test_id!(VarId));
112
113    let email_strval: Box<dyn Value> = StringValue::try_new(email_addr).unwrap().boxed();
114    let email_emailval: Box<dyn Value> = EmailValue::try_new(email_addr).unwrap().boxed();
115    assert!(matches!(email_var.validate_val_type(&email_strval), Err(InvalidValue::WrongType)));
116    assert!(matches!(email_var.validate_val_type(&email_emailval), Ok(())));
117  }
118
119  #[test]
120  fn downcast() {
121    let stringvar = StringVar::new(test_id!(VarId));
122    let stringvar_boxed = stringvar.boxed();
123    assert!(matches!(stringvar_boxed.as_any().downcast_ref::<StringVar>(), Some(_)));
124
125    // try our helper 
126    assert!(matches!(stringvar_boxed.downcast::<StringVar>(), Some(_)));
127    assert_eq!(stringvar_boxed.is::<StringVar>(), true);
128  }
129}