launchdarkly_server_sdk_evaluation/
flag_value.rs

1use log::warn;
2use serde::{Deserialize, Serialize};
3
4use crate::util::f64_to_i64_safe;
5
6/// FlagValue represents any of the data types supported by JSON, all of which can be used for a
7/// LaunchDarkly feature flag variation or a custom context attribute.
8#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
9#[serde(untagged)]
10pub enum FlagValue {
11    /// Used when the value is a boolean.
12    Bool(bool),
13    /// Used when the value is a string.
14    Str(String),
15    /// Used when the value is a number.
16    Number(f64),
17    /// Used when the value is an arbitrary JSON value.
18    Json(serde_json::Value),
19}
20
21impl From<bool> for FlagValue {
22    fn from(b: bool) -> FlagValue {
23        FlagValue::Bool(b)
24    }
25}
26
27impl From<String> for FlagValue {
28    fn from(s: String) -> FlagValue {
29        FlagValue::Str(s)
30    }
31}
32
33impl From<f64> for FlagValue {
34    fn from(f: f64) -> FlagValue {
35        FlagValue::Number(f)
36    }
37}
38
39impl From<i64> for FlagValue {
40    fn from(i: i64) -> FlagValue {
41        FlagValue::Number(i as f64)
42    }
43}
44
45impl From<serde_json::Value> for FlagValue {
46    fn from(v: serde_json::Value) -> Self {
47        use serde_json::Value;
48        match v {
49            Value::Bool(b) => b.into(),
50            Value::Number(n) => {
51                if let Some(f) = n.as_f64() {
52                    f.into()
53                } else {
54                    warn!("unrepresentable number {}, converting to string", n);
55                    FlagValue::Json(format!("{}", n).into())
56                }
57            }
58            Value::String(s) => s.into(),
59            Value::Null | Value::Object(_) | Value::Array(_) => FlagValue::Json(v),
60        }
61    }
62}
63
64impl FlagValue {
65    /// Attempts to convert the FlagValue into a boolean representation, returning None if the
66    /// conversion is invalid.
67    pub fn as_bool(&self) -> Option<bool> {
68        match self {
69            FlagValue::Bool(b) => Some(*b),
70            _ => {
71                warn!("variation type is not bool but {:?}", self);
72                None
73            }
74        }
75    }
76
77    /// Attempts to convert the FlagValue into a string representation, returning None if the
78    /// conversion is invalid.
79    pub fn as_string(&self) -> Option<String> {
80        match self {
81            FlagValue::Str(s) => Some(s.clone()),
82            _ => {
83                warn!("variation type is not str but {:?}", self);
84                None
85            }
86        }
87    }
88
89    /// Attempts to convert the FlagValue into a float representation, returning None if the
90    /// conversion is invalid.
91    pub fn as_float(&self) -> Option<f64> {
92        match self {
93            FlagValue::Number(f) => Some(*f),
94            _ => {
95                warn!("variation type is not number but {:?}", self);
96                None
97            }
98        }
99    }
100
101    /// Attempts to convert the FlagValue into a integer representation, returning None if the
102    /// conversion is invalid.
103    pub fn as_int(&self) -> Option<i64> {
104        match self {
105            FlagValue::Number(f) => f64_to_i64_safe(*f),
106            _ => {
107                warn!("variation type is not number but {:?}", self);
108                None
109            }
110        }
111    }
112
113    /// Attempts to convert the FlagValue into an arbitrary JSON representation, returning None if the
114    /// conversion is invalid.
115    pub fn as_json(&self) -> Option<serde_json::Value> {
116        use serde_json::Value;
117        match self {
118            FlagValue::Bool(b) => Some(Value::from(*b)),
119            FlagValue::Str(s) => Some(Value::from(s.as_str())),
120            FlagValue::Number(f) => Some(Value::from(*f)),
121            FlagValue::Json(v) => Some(v.clone()),
122        }
123    }
124}
125
126#[cfg(test)]
127mod tests {
128    use super::*;
129    use serde_json::json;
130    use spectral::prelude::*;
131
132    #[test]
133    fn float_bounds() {
134        let test_cases = vec![
135            (1.99, Some(1)),
136            (9007199254740990.0, Some(9007199254740990)),
137            (9007199254740991.0, Some(9007199254740991)),
138            (9007199254740992.0, None),
139            (-1.99, Some(-1)),
140            (-9007199254740990.0, Some(-9007199254740990)),
141            (-9007199254740991.0, Some(-9007199254740991)),
142            (-9007199254740992.0, None),
143        ];
144        for (have, expect) in test_cases {
145            assert_that!(FlagValue::Number(have).as_int()).is_equal_to(expect);
146        }
147    }
148
149    #[test]
150    fn deserialization() {
151        fn test_case(json: &str, expected: FlagValue) {
152            assert_eq!(serde_json::from_str::<FlagValue>(json).unwrap(), expected);
153        }
154
155        test_case("1.0", FlagValue::Number(1.0));
156        test_case("1", FlagValue::Number(1.0));
157        test_case("true", FlagValue::Bool(true));
158        test_case("\"foo\"", FlagValue::Str("foo".to_string()));
159        test_case("{}", FlagValue::Json(json!({})));
160    }
161
162    #[test]
163    fn can_handle_converting_between_types() {
164        let value: FlagValue = true.into();
165        assert_eq!(Some(true), value.as_bool());
166        assert!(value.as_string().is_none());
167        assert!(value.as_float().is_none());
168        assert!(value.as_float().is_none());
169        assert!(value.as_int().is_none());
170
171        let value: FlagValue = String::from("testing").into();
172        assert!(value.as_bool().is_none());
173        assert_eq!(Some(String::from("testing")), value.as_string());
174        assert!(value.as_float().is_none());
175        assert!(value.as_float().is_none());
176        assert!(value.as_int().is_none());
177
178        let value: FlagValue = 1_f64.into();
179        assert!(value.as_bool().is_none());
180        assert!(value.as_string().is_none());
181        assert_eq!(Some(1_f64), value.as_float());
182        assert_eq!(Some(1_i64), value.as_int());
183
184        let value: FlagValue = 1_i64.into();
185        assert!(value.as_bool().is_none());
186        assert!(value.as_string().is_none());
187        assert_eq!(Some(1_f64), value.as_float());
188        assert_eq!(Some(1_i64), value.as_int());
189
190        let value: FlagValue = serde_json::Value::Bool(true).into();
191        assert_eq!(Some(true), value.as_bool());
192        assert_eq!(Some(serde_json::Value::Bool(true)), value.as_json());
193
194        let value: FlagValue = serde_json::Value::String("testing".to_string()).into();
195        assert_eq!(Some(String::from("testing")), value.as_string());
196        assert_eq!(
197            Some(serde_json::Value::String("testing".to_string())),
198            value.as_json()
199        );
200
201        let value: FlagValue = json!(1_f64).into();
202        assert_eq!(Some(1_f64), value.as_float());
203        assert_eq!(Some(json!(1_f64)), value.as_json());
204
205        let value: FlagValue = serde_json::Value::Array(vec![serde_json::Value::Bool(true)]).into();
206        assert_eq!(
207            Some(serde_json::Value::Array(vec![serde_json::Value::Bool(
208                true
209            )])),
210            value.as_json()
211        );
212    }
213}