workflow_wasm/
utils.rs

1//!
2//! Utilities for calling JavaScript functions and retrieving values
3//! from JavaScript object properties.
4//!
5
6use crate::error::Error;
7use crate::extensions::jsvalue::*;
8use js_sys::{Array, Reflect, Uint8Array};
9use wasm_bindgen::prelude::*;
10
11/// Call a JavaScript function without arguments
12pub fn apply_with_args0(this_jsv: &JsValue, fn_name: &str) -> Result<JsValue, JsValue> {
13    let fn_jsv = Reflect::get(this_jsv, &JsValue::from(fn_name))?;
14    let args = Array::new();
15    let ret_jsv = Reflect::apply(&fn_jsv.into(), this_jsv, &args)?;
16    Ok(ret_jsv)
17}
18
19/// Call a JavaScript function with a single argument
20pub fn apply_with_args1(
21    this_jsv: &JsValue,
22    fn_name: &str,
23    arg_jsv: JsValue,
24) -> Result<JsValue, JsValue> {
25    let fn_jsv = Reflect::get(this_jsv, &JsValue::from(fn_name))?;
26    let args = Array::new_with_length(1);
27    args.set(0, arg_jsv);
28    let ret_jsv = Reflect::apply(&fn_jsv.into(), this_jsv, &args)?;
29    Ok(ret_jsv)
30}
31
32/// Call a JavaScript function with two arguments
33pub fn apply_with_args2(
34    this_jsv: &JsValue,
35    fn_name: &str,
36    arg_jsv: JsValue,
37    arg2_jsv: JsValue,
38) -> Result<JsValue, JsValue> {
39    let fn_jsv = Reflect::get(this_jsv, &JsValue::from(fn_name))?;
40    let args = Array::new_with_length(2);
41    args.set(0, arg_jsv);
42    args.set(1, arg2_jsv);
43    let ret_jsv = Reflect::apply(&fn_jsv.into(), this_jsv, &args)?;
44    Ok(ret_jsv)
45}
46
47/// Obtain a JsValue from a JavaScript object property.
48/// Results in an `Error` if the property does not exist.
49#[inline]
50pub fn try_get_js_value_prop(jsv: &JsValue, prop: &str) -> Result<JsValue, Error> {
51    let v = Reflect::get(jsv, &JsValue::from(prop))
52        .map_err(|_| Error::PropertyAccess(prop.to_string()))?;
53    if v == JsValue::UNDEFINED {
54        return Err(Error::MissingProperty(prop.to_string()));
55    }
56    Ok(v)
57}
58
59/// Obtain a `u64` value from an object property.
60/// Results in an `Error` if the value is not a number, rounded `u64` if the value is a number.
61#[inline]
62pub fn try_get_u64_from_prop(jsv: &JsValue, prop: &str) -> Result<u64, Error> {
63    let v = try_get_js_value_prop(jsv, prop)?;
64    if v.is_bigint() {
65        Ok(v.clone().try_into().map_err(|err| {
66            Error::Convert(format!(
67                "unable to convert property `{prop}` (BigInt) value: `{v:?}`: {err:?}"
68            ))
69        })?)
70    } else {
71        Ok(v.as_f64()
72            .ok_or_else(|| Error::WrongType(format!("property `{prop}` is not a number ({v:?})")))?
73            as u64)
74    }
75}
76
77/// Obtain `f64` value from an object property.
78/// Results in an `Error` if the value is not a number.
79#[inline]
80pub fn try_get_f64_from_prop(jsv: &JsValue, prop: &str) -> Result<f64, Error> {
81    let v = try_get_js_value_prop(jsv, prop)?;
82    let f = v
83        .as_f64()
84        .ok_or_else(|| Error::WrongType(format!("property `{prop}` is not a number ({v:?})")))?;
85    Ok(f)
86}
87
88/// Obtain `u8` value from the object property `prop`.
89/// Results in an `Error` if the value is not a number or the number value is out of bounds (0..u8::MAX).
90#[inline]
91pub fn try_get_u8_from_prop(jsv: &JsValue, prop: &str) -> Result<u8, Error> {
92    try_get_js_value_prop(jsv, prop)?
93        .try_as_u8()
94        .map_err(|err| {
95            Error::WrongType(format!("unable to convert property `{prop}` to u8: {err}"))
96        })
97}
98
99/// Obtain `u16` value from the object property `prop`.
100/// Results in an `Error` if the value is not a number or the number value is out of bounds (0..u16::MAX).
101#[inline]
102pub fn try_get_u16_from_prop(jsv: &JsValue, prop: &str) -> Result<u16, Error> {
103    try_get_js_value_prop(jsv, prop)?
104        .try_as_u16()
105        .map_err(|err| Error::Convert(format!("unable to convert property `{prop}` to u16: {err}")))
106}
107
108/// Obtain `u32` value from the object property `prop`.
109#[inline]
110pub fn try_get_u32_from_prop(jsv: &JsValue, prop: &str) -> Result<u32, Error> {
111    try_get_js_value_prop(jsv, prop)?
112        .try_as_u32()
113        .map_err(|err| Error::Convert(format!("unable to convert property `{prop}` to u32: {err}")))
114}
115
116/// Obtain a `bool` value from the object property `prop`
117#[inline]
118pub fn try_get_bool_from_prop(jsv: &JsValue, prop: &str) -> Result<bool, Error> {
119    try_get_js_value_prop(jsv, prop)?
120        .as_bool()
121        .ok_or_else(|| Error::WrongType(format!("property {prop} is not a boolean",)))
122}
123
124/// Obtain a `Vec<u8>` value from the object property `prop` (using `Uint8Array`)
125#[inline]
126pub fn try_get_vec_u8_from_number_array_prop(jsv: &JsValue, prop: &str) -> Result<Vec<u8>, Error> {
127    if try_get_js_value_prop(jsv, prop)?.is_array() {
128        let array = Array::from(jsv);
129        let array: Result<Vec<u8>, Error> = array.to_vec().iter().map(|v| v.try_as_u8()).collect();
130        Ok(array?)
131    } else {
132        Err(Error::WrongType(format!(
133            "try_get_vec_u8_from_number_array_prop: property {prop} is not an array"
134        )))
135    }
136}
137
138/// Obtain `Vec<JsValue>` by treating the object property `prop` as an array
139#[inline]
140pub fn try_get_vec_from_prop(jsv: &JsValue, prop: &str) -> Result<Vec<JsValue>, Error> {
141    let array = try_get_js_value_prop(jsv, prop)?.dyn_into::<Array>()?;
142    Ok(array.to_vec())
143}
144
145/// Obtain a `Vec<u8>` value from the object property `prop` (using `Uint8Array`)
146#[inline]
147pub fn try_get_vec_u8_from_uint8_array_prop(jsv: &JsValue, prop: &str) -> Result<Vec<u8>, Error> {
148    let buffer = try_get_js_value_prop(jsv, prop)?;
149    let array = Uint8Array::new(&buffer);
150    let data: Vec<u8> = array.to_vec();
151    Ok(data)
152}
153
154/// Obtain a `Vec<u8>` from the property `prop` expressed as a big number
155#[inline]
156pub fn try_get_vec_u8_from_bn_prop(jsv: &JsValue, prop: &str) -> Result<Vec<u8>, Error> {
157    let bn_jsv = try_get_js_value_prop(jsv, prop)?;
158    let bytes = apply_with_args0(&bn_jsv, "toBytes")?;
159    let array = Uint8Array::new(&bytes);
160    Ok(array.to_vec())
161}
162
163/// Obtain `Vec<u8>` from the supplied big number
164#[inline]
165pub fn try_get_vec_u8_from_bn(bn_jsv: &JsValue) -> Result<Vec<u8>, Error> {
166    let bytes = apply_with_args0(bn_jsv, "toBytes")?;
167    let array = Uint8Array::new(&bytes);
168    Ok(array.to_vec())
169}
170
171/// Obtain a `String` value from the object property `prop`
172#[inline]
173pub fn try_get_string_from_prop(jsv: &JsValue, prop: &str) -> Result<String, Error> {
174    match try_get_js_value_prop(jsv, prop)?.as_string() {
175        Some(str) => Ok(str),
176        None => Err(Error::WrongType(format!(
177            "property '{prop}' is not a string",
178        ))),
179    }
180}