pdk_script/
value.rs

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