openstranded_common_wasmcontract/
value.rs1use std::collections::HashMap;
2
3use crate::ServiceError;
4
5#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
10pub enum Value {
11 Null,
13
14 Bool(bool),
16
17 I32(i32),
19
20 U32(u32),
22
23 F64(f64),
25
26 String(String),
28
29 Bytes(Vec<u8>),
31
32 Array(Vec<Value>),
34
35 Map(HashMap<String, Value>),
37}
38
39impl Value {
42 pub fn as_u32(&self) -> Result<u32, ServiceError> {
47 match self {
48 Value::U32(n) => Ok(*n),
49 Value::I32(n) if *n >= 0 => Ok(*n as u32),
50 Value::F64(n) if *n >= 0.0 && *n <= f64::from(u32::MAX) => Ok(*n as u32),
51 Value::String(s) => s.parse::<u32>().map_err(|e| {
52 ServiceError::TypeMismatch {
53 expected: "u32".into(),
54 found: format!("string \"{s}\" ({e})"),
55 }
56 }),
57 other => Err(ServiceError::TypeMismatch {
58 expected: "u32".into(),
59 found: format!("{other:?}"),
60 }),
61 }
62 }
63
64 pub fn as_i32(&self) -> Result<i32, ServiceError> {
66 match self {
67 Value::I32(n) => Ok(*n),
68 Value::U32(n) if i32::try_from(*n).is_ok() => Ok(*n as i32),
69 Value::F64(n) if *n >= f64::from(i32::MIN) && *n <= f64::from(i32::MAX) => {
70 Ok(*n as i32)
71 }
72 Value::String(s) => s.parse::<i32>().map_err(|e| {
73 ServiceError::TypeMismatch {
74 expected: "i32".into(),
75 found: format!("string \"{s}\" ({e})"),
76 }
77 }),
78 other => Err(Self::type_mismatch("i32", other)),
79 }
80 }
81
82 pub fn as_f64(&self) -> Result<f64, ServiceError> {
84 match self {
85 Value::F64(n) => Ok(*n),
86 Value::I32(n) => Ok(f64::from(*n)),
87 Value::U32(n) => Ok(f64::from(*n)),
88 Value::String(s) => s.parse::<f64>().map_err(|e| {
89 ServiceError::TypeMismatch {
90 expected: "f64".into(),
91 found: format!("string \"{s}\" ({e})"),
92 }
93 }),
94 other => Err(Self::type_mismatch("f64", other)),
95 }
96 }
97
98 pub fn as_str(&self) -> Result<&str, ServiceError> {
100 match self {
101 Value::String(s) => Ok(s.as_str()),
102 other => Err(Self::type_mismatch("string", other)),
103 }
104 }
105
106 pub fn as_bool(&self) -> Result<bool, ServiceError> {
108 match self {
109 Value::Bool(b) => Ok(*b),
110 Value::U32(n) => Ok(*n != 0),
111 Value::I32(n) => Ok(*n != 0),
112 Value::String(s) if s == "true" || s == "1" => Ok(true),
113 Value::String(s) if s == "false" || s == "0" => Ok(false),
114 other => Err(Self::type_mismatch("bool", other)),
115 }
116 }
117
118 pub fn as_bytes(&self) -> Result<&[u8], ServiceError> {
120 match self {
121 Value::Bytes(b) => Ok(b.as_slice()),
122 other => Err(Self::type_mismatch("bytes", other)),
123 }
124 }
125
126 pub fn as_array(&self) -> Result<&[Value], ServiceError> {
128 match self {
129 Value::Array(arr) => Ok(arr.as_slice()),
130 other => Err(Self::type_mismatch("array", other)),
131 }
132 }
133
134 pub fn as_map(&self) -> Result<&HashMap<String, Value>, ServiceError> {
136 match self {
137 Value::Map(map) => Ok(map),
138 other => Err(Self::type_mismatch("map", other)),
139 }
140 }
141
142 pub fn get(&self, key: &str) -> Result<&Value, ServiceError> {
144 match self {
145 Value::Map(map) => {
146 map.get(key).ok_or_else(|| ServiceError::TypeMismatch {
147 expected: format!("map with key \"{key}\""),
148 found: format!("{self:?}"),
149 })
150 }
151 other => Err(Self::type_mismatch("map", other)),
152 }
153 }
154
155 pub(crate) fn type_mismatch(expected: &str, found: &Self) -> ServiceError {
157 ServiceError::TypeMismatch {
158 expected: expected.into(),
159 found: format!("{found:?}"),
160 }
161 }
162}
163
164impl From<u32> for Value {
167 fn from(v: u32) -> Self {
168 Value::U32(v)
169 }
170}
171
172impl From<i32> for Value {
173 fn from(v: i32) -> Self {
174 Value::I32(v)
175 }
176}
177
178impl From<f64> for Value {
179 fn from(v: f64) -> Self {
180 Value::F64(v)
181 }
182}
183
184impl From<bool> for Value {
185 fn from(v: bool) -> Self {
186 Value::Bool(v)
187 }
188}
189
190impl From<String> for Value {
191 fn from(v: String) -> Self {
192 Value::String(v)
193 }
194}
195
196impl From<&str> for Value {
197 fn from(v: &str) -> Self {
198 Value::String(v.to_owned())
199 }
200}
201
202impl From<Vec<u8>> for Value {
203 fn from(v: Vec<u8>) -> Self {
204 Value::Bytes(v)
205 }
206}
207
208impl From<Vec<Value>> for Value {
209 fn from(v: Vec<Value>) -> Self {
210 Value::Array(v)
211 }
212}
213
214impl<K, V> From<HashMap<K, V>> for Value
215where
216 K: Into<String>,
217 V: Into<Value>,
218{
219 fn from(map: HashMap<K, V>) -> Self {
220 Value::Map(map.into_iter().map(|(k, v)| (k.into(), v.into())).collect())
221 }
222}
223
224#[cfg(test)]
227mod tests {
228 use super::*;
229
230 #[test]
231 fn test_as_u32_from_u32() {
232 assert_eq!(Value::U32(42).as_u32().unwrap(), 42);
233 }
234
235 #[test]
236 fn test_as_u32_from_i32_positive() {
237 assert_eq!(Value::I32(42).as_u32().unwrap(), 42);
238 }
239
240 #[test]
241 fn test_as_u32_from_i32_negative() {
242 assert!(Value::I32(-1).as_u32().is_err());
243 }
244
245 #[test]
246 fn test_as_u32_from_string() {
247 assert_eq!(Value::String("42".into()).as_u32().unwrap(), 42);
248 }
249
250 #[test]
251 fn test_as_u32_from_string_invalid() {
252 assert!(Value::String("hello".into()).as_u32().is_err());
253 }
254
255 #[test]
256 fn test_as_u32_type_mismatch() {
257 let err = Value::Bool(true).as_u32().unwrap_err();
258 assert!(matches!(err, ServiceError::TypeMismatch { .. }));
259 }
260
261 #[test]
262 fn test_as_str_ok() {
263 assert_eq!(Value::String("hello".into()).as_str().unwrap(), "hello");
264 }
265
266 #[test]
267 fn test_as_str_type_mismatch() {
268 assert!(Value::U32(42).as_str().is_err());
269 }
270
271 #[test]
272 fn test_as_array_ok() {
273 let arr = Value::Array(vec![Value::U32(1), Value::U32(2)]);
274 assert_eq!(arr.as_array().unwrap().len(), 2);
275 }
276
277 #[test]
278 fn test_as_array_empty() {
279 let arr = Value::Array(vec![]);
280 assert!(arr.as_array().unwrap().is_empty());
281 }
282
283 #[test]
284 fn test_get_from_map() {
285 let mut map = HashMap::new();
286 map.insert("id".into(), Value::U32(42));
287 let val = Value::Map(map);
288 assert_eq!(val.get("id").unwrap().as_u32().unwrap(), 42);
289 }
290
291 #[test]
292 fn test_get_missing_key() {
293 let map = Value::Map(HashMap::new());
294 assert!(map.get("missing").is_err());
295 }
296
297 #[test]
298 fn test_get_from_non_map() {
299 assert!(Value::U32(42).get("id").is_err());
300 }
301
302 #[test]
303 fn test_from_primitives() {
304 assert_eq!(Value::from(42u32), Value::U32(42));
305 assert_eq!(Value::from(-1i32), Value::I32(-1));
306 assert_eq!(Value::from(3.14f64), Value::F64(3.14));
307 assert_eq!(Value::from(true), Value::Bool(true));
308 assert_eq!(Value::from("hello"), Value::String("hello".into()));
309 }
310
311 #[test]
312 fn test_as_i32_from_i32() {
313 assert_eq!(Value::I32(42).as_i32().unwrap(), 42);
314 assert_eq!(Value::I32(-5).as_i32().unwrap(), -5);
315 }
316
317 #[test]
318 fn test_as_i32_from_u32() {
319 assert_eq!(Value::U32(42).as_i32().unwrap(), 42);
320 assert!(Value::U32(u32::MAX).as_i32().is_err());
321 }
322
323 #[test]
324 fn test_as_f64_from_f64() {
325 assert!((Value::F64(3.14).as_f64().unwrap() - 3.14).abs() < 1e-10);
326 }
327
328 #[test]
329 fn test_as_f64_from_i32() {
330 assert!((Value::I32(5).as_f64().unwrap() - 5.0).abs() < 1e-10);
331 }
332
333 #[test]
334 fn test_as_bytes() {
335 let bytes = vec![1u8, 2, 3];
336 assert_eq!(Value::Bytes(bytes).as_bytes().unwrap(), &[1, 2, 3]);
337 assert!(Value::U32(42).as_bytes().is_err());
338 }
339
340 #[test]
341 fn test_as_map_ok() {
342 let mut map = HashMap::new();
343 map.insert("a".into(), Value::U32(1));
344 map.insert("b".into(), Value::U32(2));
345 let val = Value::Map(map);
346 let m = val.as_map().unwrap();
347 assert!(m.contains_key("a"));
348 assert!(m.contains_key("b"));
349 }
350
351 #[test]
352 fn test_as_map_type_mismatch() {
353 assert!(Value::U32(42).as_map().is_err());
354 }
355
356 #[test]
357 fn test_as_bool_from_bool() {
358 assert_eq!(Value::Bool(true).as_bool().unwrap(), true);
359 }
360
361 #[test]
362 fn test_as_bool_from_u32() {
363 assert_eq!(Value::U32(1).as_bool().unwrap(), true);
364 assert_eq!(Value::U32(0).as_bool().unwrap(), false);
365 }
366
367 #[test]
368 fn test_as_bool_from_string() {
369 assert_eq!(Value::String("true".into()).as_bool().unwrap(), true);
370 assert_eq!(Value::String("false".into()).as_bool().unwrap(), false);
371 }
372}