Skip to main content

open_feature/evaluation/
context_field_value.rs

1use std::{any::Any, sync::Arc};
2
3use time::OffsetDateTime;
4
5/// Value type of evaluation context custom fields.
6#[derive(Clone, Debug)]
7#[allow(missing_docs)]
8pub enum EvaluationContextFieldValue {
9    Bool(bool),
10    Int(i64),
11    Float(f64),
12    String(String),
13    DateTime(OffsetDateTime),
14    Struct(Arc<dyn Any + Send + Sync>), // TODO: better to make structure in similar way as serde_json::Value
15}
16
17impl EvaluationContextFieldValue {
18    /// Create a new [`EvaluationContextFieldValue`] instance with given struct `value`.
19    pub fn new_struct<T>(value: T) -> Self
20    where
21        T: Any + Send + Sync,
22    {
23        Self::Struct(Arc::new(value))
24    }
25
26    /// Return `true` if this is a bool value.
27    pub fn is_bool(&self) -> bool {
28        matches!(self, Self::Bool(_))
29    }
30
31    /// Try to convert `self` to bool.
32    pub fn as_bool(&self) -> Option<bool> {
33        match self {
34            Self::Bool(value) => Some(*value),
35            _ => None,
36        }
37    }
38
39    /// Return `true` if this is an int value.
40    pub fn is_i64(&self) -> bool {
41        matches!(self, Self::Int(_))
42    }
43
44    /// Try to convert `self` to int.
45    pub fn as_i64(&self) -> Option<i64> {
46        match self {
47            Self::Int(value) => Some(*value),
48            _ => None,
49        }
50    }
51
52    /// Return `true` if this is a float value.
53    pub fn is_f64(&self) -> bool {
54        matches!(self, Self::Float(_))
55    }
56
57    /// Try to convert `self` to float.
58    pub fn as_f64(&self) -> Option<f64> {
59        match self {
60            Self::Float(value) => Some(*value),
61            _ => None,
62        }
63    }
64
65    /// Return `true` if this is a string value.
66    pub fn is_str(&self) -> bool {
67        matches!(self, Self::String(_))
68    }
69
70    /// Try to convert `self` to string.
71    pub fn as_str(&self) -> Option<&str> {
72        match self {
73            Self::String(value) => Some(value),
74            _ => None,
75        }
76    }
77
78    /// Return `true` if this is a [`OffsetDateTime`] value.
79    pub fn is_date_time(&self) -> bool {
80        matches!(self, Self::DateTime(_))
81    }
82
83    /// Try to convert `self` to [`OffsetDateTime`].
84    pub fn as_date_time(&self) -> Option<&OffsetDateTime> {
85        match self {
86            Self::DateTime(value) => Some(value),
87            _ => None,
88        }
89    }
90
91    /// Return `true` if this is a struct value.
92    pub fn is_struct(&self) -> bool {
93        matches!(self, Self::Struct(_))
94    }
95
96    /// Try to convert `self` to struct.
97    pub fn as_struct(&self) -> Option<Arc<dyn Any + Send + Sync>> {
98        match self {
99            Self::Struct(value) => Some(value.clone()),
100            _ => None,
101        }
102    }
103}
104
105impl From<bool> for EvaluationContextFieldValue {
106    fn from(value: bool) -> Self {
107        Self::Bool(value)
108    }
109}
110
111impl From<i8> for EvaluationContextFieldValue {
112    fn from(value: i8) -> Self {
113        Self::Int(value.into())
114    }
115}
116
117impl From<i16> for EvaluationContextFieldValue {
118    fn from(value: i16) -> Self {
119        Self::Int(value.into())
120    }
121}
122
123impl From<i32> for EvaluationContextFieldValue {
124    fn from(value: i32) -> Self {
125        Self::Int(value.into())
126    }
127}
128
129impl From<i64> for EvaluationContextFieldValue {
130    fn from(value: i64) -> Self {
131        Self::Int(value)
132    }
133}
134
135impl From<u8> for EvaluationContextFieldValue {
136    fn from(value: u8) -> Self {
137        Self::Int(value.into())
138    }
139}
140
141impl From<u16> for EvaluationContextFieldValue {
142    fn from(value: u16) -> Self {
143        Self::Int(value.into())
144    }
145}
146
147impl From<u32> for EvaluationContextFieldValue {
148    fn from(value: u32) -> Self {
149        Self::Int(value.into())
150    }
151}
152
153impl From<f32> for EvaluationContextFieldValue {
154    fn from(value: f32) -> Self {
155        Self::Float(value.into())
156    }
157}
158
159impl From<f64> for EvaluationContextFieldValue {
160    fn from(value: f64) -> Self {
161        Self::Float(value)
162    }
163}
164
165impl From<String> for EvaluationContextFieldValue {
166    fn from(value: String) -> Self {
167        Self::String(value)
168    }
169}
170
171impl From<&str> for EvaluationContextFieldValue {
172    fn from(value: &str) -> Self {
173        Self::String(value.to_string())
174    }
175}
176
177impl From<OffsetDateTime> for EvaluationContextFieldValue {
178    fn from(value: OffsetDateTime) -> Self {
179        Self::DateTime(value)
180    }
181}
182
183impl<T: Any + Send + Sync> From<Arc<T>> for EvaluationContextFieldValue {
184    fn from(value: Arc<T>) -> Self {
185        Self::Struct(value)
186    }
187}
188
189impl PartialEq for EvaluationContextFieldValue {
190    fn eq(&self, other: &Self) -> bool {
191        match (self, other) {
192            (EvaluationContextFieldValue::Bool(left), EvaluationContextFieldValue::Bool(right)) => {
193                left == right
194            }
195            (EvaluationContextFieldValue::Int(left), EvaluationContextFieldValue::Int(right)) => {
196                left == right
197            }
198            (
199                EvaluationContextFieldValue::Float(left),
200                EvaluationContextFieldValue::Float(right),
201            ) => left == right,
202            (
203                EvaluationContextFieldValue::String(left),
204                EvaluationContextFieldValue::String(right),
205            ) => left == right,
206            (
207                EvaluationContextFieldValue::DateTime(left),
208                EvaluationContextFieldValue::DateTime(right),
209            ) => left == right,
210            (_, _) => false,
211        }
212    }
213}
214
215#[cfg(test)]
216mod tests {
217    use super::*;
218    use crate::*;
219
220    #[test]
221    fn evaluation_context_custom_fields() {
222        let now = OffsetDateTime::now_utc();
223
224        let context = EvaluationContext::default()
225            .with_targeting_key("Some Key")
226            .with_custom_field("Bool", false)
227            .with_custom_field("Bool", EvaluationContextFieldValue::Bool(true))
228            .with_custom_field("Int", 42)
229            .with_custom_field("Float", 42.0)
230            .with_custom_field("String", "StringValue")
231            .with_custom_field("DateTime", now.clone())
232            .with_custom_field(
233                "Struct",
234                EvaluationContextFieldValue::new_struct(EvaluationReason::Cached),
235            );
236
237        // Assert bool
238        if let EvaluationContextFieldValue::Bool(value) = context.custom_fields.get("Bool").unwrap()
239        {
240            assert_eq!(true, *value);
241        } else {
242            panic!()
243        }
244
245        // Assert int.
246        if let EvaluationContextFieldValue::Int(value) = context.custom_fields.get("Int").unwrap() {
247            assert_eq!(42, *value);
248        } else {
249            panic!()
250        }
251
252        // Assert float.
253        if let EvaluationContextFieldValue::Float(value) =
254            context.custom_fields.get("Float").unwrap()
255        {
256            assert_eq!(42.0, *value);
257        } else {
258            panic!()
259        }
260
261        // Assert string.
262        if let EvaluationContextFieldValue::String(value) =
263            context.custom_fields.get("String").unwrap()
264        {
265            assert_eq!("StringValue", value);
266        } else {
267            panic!()
268        }
269
270        // Assert date time.
271        if let EvaluationContextFieldValue::DateTime(value) =
272            context.custom_fields.get("DateTime").unwrap()
273        {
274            assert_eq!(now, *value);
275        } else {
276            panic!()
277        }
278
279        // Assert struct.
280        if let EvaluationContextFieldValue::Struct(value) =
281            context.custom_fields.get("Struct").unwrap().clone()
282        {
283            let v = value.clone().downcast::<EvaluationReason>().unwrap();
284            assert_eq!(EvaluationReason::Cached, *v);
285        } else {
286            panic!()
287        };
288    }
289}