workflow_wasm/extensions/
jsvalue.rs

1//! JsValue to native Rust datatypes conversion utilities.
2//! These utilities marshall incoming data for different types
3//! of representations and convert them to native Rust datatypes.
4//! For example, a `Vec<u8>` can be converted from a Uint8Array
5//! or from a hex-encoded string.
6
7use crate::error::Error;
8use js_sys::Uint8Array;
9use wasm_bindgen::prelude::*;
10
11pub trait JsValueExtension {
12    fn try_as_u8(&self) -> Result<u8, Error>;
13    fn try_as_u16(&self) -> Result<u16, Error>;
14    fn try_as_u32(&self) -> Result<u32, Error>;
15    fn try_as_u64(&self) -> Result<u64, Error>;
16    fn try_as_vec_u8(&self) -> Result<Vec<u8>, Error>;
17}
18
19impl JsValueExtension for JsValue {
20    fn try_as_u8(&self) -> Result<u8, Error> {
21        let f = self
22            .as_f64()
23            .ok_or_else(|| Error::WrongType(format!("value is not a number: `{self:?}`")))?;
24        if f < 0.0 || f > u8::MAX as f64 {
25            Err(Error::Bounds(format!(
26                "value `{f}` is out of bounds (0..{})",
27                u8::MAX
28            )))
29        } else {
30            Ok(f as u8)
31        }
32    }
33
34    fn try_as_u16(&self) -> Result<u16, Error> {
35        let f = self
36            .as_f64()
37            .ok_or_else(|| Error::WrongType(format!("value is not a number: `{self:?}`")))?;
38        if f < 0.0 || f > u16::MAX as f64 {
39            Err(Error::Bounds(format!(
40                "value `{f}` is ount of bounds (0..{})",
41                u16::MAX
42            )))
43        } else {
44            Ok(f as u16)
45        }
46    }
47
48    fn try_as_u32(&self) -> Result<u32, Error> {
49        let f = self
50            .as_f64()
51            .ok_or_else(|| Error::WrongType(format!("value is not a number: `{self:?}`")))?;
52        if f < 0.0 || f > u32::MAX as f64 {
53            Err(Error::Bounds(format!(
54                "value `{f}` is ount of bounds (0..{})",
55                u32::MAX
56            )))
57        } else {
58            Ok(f as u32)
59        }
60    }
61
62    fn try_as_u64(&self) -> Result<u64, Error> {
63        if self.is_string() {
64            let hex_str = self.as_string().unwrap();
65            if hex_str.len() > 16 {
66                Err(Error::WrongSize(
67                    "try_as_u64(): supplied string must be < 16 chars".to_string(),
68                ))
69            } else {
70                let mut out = [0u8; 8];
71                let mut input = [b'0'; 16];
72                let start = input.len() - hex_str.len();
73                input[start..].copy_from_slice(hex_str.as_bytes());
74                faster_hex::hex_decode(&input, &mut out)?;
75                Ok(u64::from_be_bytes(out))
76            }
77        } else if self.is_bigint() {
78            Ok(self.clone().try_into().map_err(|err| {
79                Error::Convert(format!(
80                    "try_as_u64(): unable to convert BigInt value to u64: `{self:?}`: {err:?}"
81                ))
82            })?)
83        } else {
84            Ok(self
85                .as_f64()
86                .ok_or_else(|| Error::WrongType(format!("value is not a number ({self:?})")))?
87                as u64)
88        }
89    }
90
91    fn try_as_vec_u8(&self) -> Result<Vec<u8>, Error> {
92        if self.is_string() {
93            let hex_string = self.as_string().unwrap();
94            let len = hex_string.len();
95            if len == 0 {
96                Ok(vec![])
97            } else if len & 0x1 == 1 {
98                Err(Error::HexStringNotEven(hex_string))
99            } else {
100                let mut vec = vec![0u8; hex_string.len() / 2];
101                faster_hex::hex_decode(hex_string.as_bytes(), &mut vec)?;
102                Ok(vec)
103            }
104        } else if self.is_object() {
105            let array = Uint8Array::new(self);
106            let vec: Vec<u8> = array.to_vec();
107            Ok(vec)
108        } else {
109            Err(Error::WrongType(
110                "value is not a hex string or an array".to_string(),
111            ))
112        }
113    }
114}