Skip to main content

pdk_script/
value.rs

1// Copyright 2023 Salesforce, Inc. All rights reserved.
2use crate::evaluator::EvaluationError;
3pub use num_traits::cast::FromPrimitive;
4use pel::runtime::value::Value as PelValue;
5use serde::{Deserialize, Serialize};
6use serde_json::{Map, Number, Value as JsonValue};
7use std::collections::HashMap;
8use std::convert::TryFrom;
9use std::iter::FromIterator;
10
11/// Represents any valid script value.
12#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Default)]
13#[serde(from = "serde_json::Value", into = "serde_json::Value")]
14pub enum Value {
15    /// Represents a Null value.
16    #[default]
17    Null,
18
19    /// Represents a Boolean.
20    Bool(bool),
21
22    /// Represents a floating point Number.
23    Number(f64),
24
25    /// Represents a String.
26    String(String),
27
28    /// Represents an Array of script values.
29    Array(Vec<Value>),
30
31    /// Represents an Object of script values.
32    Object(HashMap<String, Value>),
33}
34
35impl Value {
36    /// Returns [`true`] if the [`Value`] is a Null. Returns false otherwise.
37    pub fn is_null(&self) -> bool {
38        matches!(self, Value::Null)
39    }
40
41    /// If the [`Value`] is a Boolean, returns the associated [`bool`]`. Returns [`None`]`
42    /// otherwise.
43    pub fn as_bool(&self) -> Option<bool> {
44        match self {
45            Value::Bool(b) => Some(*b),
46            _ => None,
47        }
48    }
49
50    /// If the [`Value`] is a String, returns the associated [`str`]. Returns [`None`]`
51    /// otherwise.
52    pub fn as_str(&self) -> Option<&str> {
53        match self {
54            Value::String(s) => Some(s),
55            _ => None,
56        }
57    }
58
59    /// If the [`Value`] is a Number, returns the associated [`f64`]. Returns [`None`]`
60    /// otherwise.
61    pub fn as_num(&self) -> Option<f64> {
62        match self {
63            Value::Number(f) => Some(*f),
64            _ => None,
65        }
66    }
67
68    /// If the [`Value`] is an Array, returns the associated slice. Returns [`None`]`
69    /// otherwise.
70    pub fn as_slice(&self) -> Option<&[Value]> {
71        match self {
72            Value::Array(a) => Some(a),
73            _ => None,
74        }
75    }
76
77    /// If the [`Value`] is an Object, returns the associated [`HashMap`]. Returns [`None`]`
78    /// otherwise.
79    pub fn as_object(&self) -> Option<&HashMap<String, Value>> {
80        match self {
81            Value::Object(o) => Some(o),
82            _ => None,
83        }
84    }
85}
86
87impl From<JsonValue> for Value {
88    fn from(value: JsonValue) -> Self {
89        match value {
90            JsonValue::Null => Value::Null,
91            JsonValue::Bool(val) => val.into_value(),
92            JsonValue::Number(num) => num.as_f64().unwrap_or_default().into_value(),
93            JsonValue::String(str) => str.into_value(),
94            JsonValue::Array(vec) => vec.into_value(),
95            JsonValue::Object(map) => map.into_iter().collect(),
96        }
97    }
98}
99
100impl From<Value> for JsonValue {
101    fn from(value: Value) -> Self {
102        JsonValue::try_from_value(value).unwrap_or_default()
103    }
104}
105
106impl TryFrom<&PelValue> for Value {
107    type Error = EvaluationError;
108
109    fn try_from(value: &PelValue) -> Result<Self, Self::Error> {
110        if value.is_null() {
111            return Ok(Value::Null);
112        };
113
114        if let Some(val) = value.as_bool() {
115            return Ok(val.into_value());
116        };
117
118        if let Some(val) = value.as_f64() {
119            return Ok(val.into_value());
120        };
121
122        if let Some(val) = value.as_str() {
123            return Ok(val.into_value());
124        };
125
126        if let Some(array) = value.as_slice() {
127            let mut vec = Vec::new();
128            for item in array {
129                vec.push(Value::try_from(item)?);
130            }
131            return Ok(vec.into_value());
132        };
133
134        if let Some(obj) = value.as_object() {
135            let mut map = HashMap::new();
136            for (key, val) in obj {
137                map.insert(key.to_string(), Value::try_from(val)?);
138            }
139            return Ok(map.into_value());
140        };
141
142        Err(EvaluationError::TypeMismatch)
143    }
144}
145
146impl IntoValue for JsonValue {
147    fn into_value(self) -> Value {
148        Value::from(self)
149    }
150}
151
152impl Value {
153    pub(crate) fn into_pel(self) -> pel::runtime::value::Value {
154        match self {
155            Value::Null => pel::runtime::value::Value::null(),
156            Value::Bool(val) => pel::runtime::value::Value::bool(val),
157            Value::Number(num) => pel::runtime::value::Value::number(num),
158            Value::String(val) => pel::runtime::value::Value::string(val),
159            Value::Array(vec) => {
160                pel::runtime::value::Value::array(vec.into_iter().map(Value::into_pel).collect())
161            }
162            Value::Object(obj) => pel::runtime::value::Value::object(
163                obj.into_iter()
164                    .map(|(key, value)| (key, value.into_pel()))
165                    .collect(),
166            ),
167        }
168    }
169}
170
171/// A Rust-value to Script-value conversion that consumes the input.
172pub trait IntoValue {
173    /// Converts this type into the related [`Value`] variant.
174    fn into_value(self) -> Value;
175}
176
177impl IntoValue for Value {
178    fn into_value(self) -> Value {
179        self
180    }
181}
182
183impl IntoValue for &str {
184    fn into_value(self) -> Value {
185        Value::String(self.to_string())
186    }
187}
188
189impl IntoValue for String {
190    fn into_value(self) -> Value {
191        Value::String(self)
192    }
193}
194
195macro_rules! into_value_num {
196    // Match rule that takes an argument expression
197    [$($num_type:ty), +] => {
198        $(
199        impl IntoValue for $num_type {
200            fn into_value(self) -> Value {
201                Value::Number(self as f64)
202            }
203        }
204        )*
205    };
206}
207into_value_num![i8, i16, i32, i64, u8, u16, u32, u64, f32, f64];
208
209impl IntoValue for bool {
210    fn into_value(self) -> Value {
211        Value::Bool(self)
212    }
213}
214
215impl<K: IntoValue> FromIterator<K> for Value {
216    fn from_iter<T: IntoIterator<Item = K>>(iter: T) -> Self {
217        Value::Array(iter.into_iter().map(IntoValue::into_value).collect())
218    }
219}
220
221impl<'a, K: IntoValue> FromIterator<(&'a str, K)> for Value {
222    fn from_iter<T: IntoIterator<Item = (&'a str, K)>>(iter: T) -> Self {
223        Value::Object(
224            iter.into_iter()
225                .map(|(key, val)| (key.to_string(), val.into_value()))
226                .collect(),
227        )
228    }
229}
230
231impl<K: IntoValue> FromIterator<(String, K)> for Value {
232    fn from_iter<T: IntoIterator<Item = (String, K)>>(iter: T) -> Self {
233        Value::Object(
234            iter.into_iter()
235                .map(|(key, val)| (key, val.into_value()))
236                .collect(),
237        )
238    }
239}
240
241impl<K: IntoValue> IntoValue for Vec<K> {
242    fn into_value(self) -> Value {
243        self.into_iter().collect()
244    }
245}
246
247impl<K: IntoValue> IntoValue for HashMap<String, K> {
248    fn into_value(self) -> Value {
249        self.into_iter().collect()
250    }
251}
252
253impl<K: IntoValue> IntoValue for HashMap<&str, K> {
254    fn into_value(self) -> Value {
255        self.into_iter().collect()
256    }
257}
258
259impl<K: IntoValue> IntoValue for Option<K> {
260    fn into_value(self) -> Value {
261        match self {
262            None => Value::Null,
263            Some(k) => k.into_value(),
264        }
265    }
266}
267
268/// A falible Script-value to Rust-value conversion that consumes the input.
269pub trait TryFromValue: Sized {
270    /// Performs the conversion.
271    fn try_from_value(value: Value) -> Result<Self, EvaluationError>;
272}
273
274impl TryFromValue for Value {
275    fn try_from_value(value: Value) -> Result<Self, EvaluationError> {
276        Ok(value)
277    }
278}
279
280impl TryFromValue for bool {
281    fn try_from_value(value: Value) -> Result<Self, EvaluationError> {
282        match value {
283            Value::Bool(val) => Ok(val),
284            _ => Err(EvaluationError::TypeMismatch),
285        }
286    }
287}
288macro_rules! try_from_value_num {
289    // Match rule that takes an argument expression
290    [$($num_type:ty), +] => {
291        $(
292        impl TryFromValue for $num_type {
293
294            fn try_from_value(value: Value) -> Result<Self, EvaluationError> {
295                match value {
296                    Value::Number(num) => {
297                        <$num_type>::from_f64(num)
298                            .ok_or(EvaluationError::TypeMismatch)
299                    }
300                    _ => Err(EvaluationError::TypeMismatch)
301                }
302            }
303        }
304        )*
305    };
306}
307
308try_from_value_num![i8, i16, i32, i64, u8, u16, u32, u64, f32, f64];
309
310impl TryFromValue for String {
311    fn try_from_value(value: Value) -> Result<Self, EvaluationError> {
312        match value {
313            Value::String(val) => Ok(val),
314            _ => Err(EvaluationError::TypeMismatch),
315        }
316    }
317}
318
319impl<K: TryFromValue> TryFromValue for Vec<K> {
320    fn try_from_value(value: Value) -> Result<Self, EvaluationError> {
321        match value {
322            Value::Array(array) => array.into_iter().map(K::try_from_value).collect(),
323            _ => Err(EvaluationError::TypeMismatch),
324        }
325    }
326}
327
328impl<K: TryFromValue> TryFromValue for HashMap<String, K> {
329    fn try_from_value(value: Value) -> Result<Self, EvaluationError> {
330        match value {
331            Value::Object(obj) => obj
332                .into_iter()
333                .map(|(key, val)| Ok((key, K::try_from_value(val)?)))
334                .collect(),
335            _ => Err(EvaluationError::TypeMismatch),
336        }
337    }
338}
339
340impl TryFromValue for Map<String, JsonValue> {
341    fn try_from_value(value: Value) -> Result<Self, EvaluationError> {
342        match value {
343            Value::Object(obj) => obj
344                .into_iter()
345                .map(|(key, val)| Ok((key, JsonValue::try_from_value(val)?)))
346                .collect(),
347            _ => Err(EvaluationError::TypeMismatch),
348        }
349    }
350}
351
352impl TryFromValue for JsonValue {
353    fn try_from_value(value: Value) -> Result<Self, EvaluationError> {
354        match value {
355            Value::Null => Ok(JsonValue::Null),
356            Value::Bool(val) => Ok(JsonValue::Bool(val)),
357            Value::Number(n) => Number::from_f64(n)
358                .map(JsonValue::Number)
359                .ok_or(EvaluationError::TypeMismatch),
360            Value::String(s) => Ok(JsonValue::String(s)),
361            Value::Array(_) => Vec::<JsonValue>::try_from_value(value).map(JsonValue::Array),
362            Value::Object(_) => {
363                Map::<String, JsonValue>::try_from_value(value).map(JsonValue::Object)
364            }
365        }
366    }
367}