diff --git a/core/engine/src/value/conversions/try_from_js.rs b/core/engine/src/value/conversions/try_from_js.rs index 55164e931f..07d0fadeb7 100644 --- a/core/engine/src/value/conversions/try_from_js.rs +++ b/core/engine/src/value/conversions/try_from_js.rs @@ -1,8 +1,9 @@ //! This module contains the [`TryFromJs`] trait, and conversions to basic Rust types. -use crate::{Context, JsBigInt, JsNativeError, JsResult, JsValue}; use num_bigint::BigInt; +use crate::{js_string, Context, JsBigInt, JsNativeError, JsResult, JsValue}; + /// This trait adds a fallible and efficient conversions from a [`JsValue`] to Rust types. pub trait TryFromJs: Sized { /// This function tries to convert a JavaScript value into `Self`. @@ -58,6 +59,38 @@ where } } +impl TryFromJs for Vec +where + T: TryFromJs, +{ + fn try_from_js(value: &JsValue, context: &mut Context) -> JsResult { + let JsValue::Object(object) = value else { + return Err(JsNativeError::typ() + .with_message("cannot convert value to a Vec") + .into()); + }; + + let length = object + .get(js_string!("length"), context)? + .to_length(context)?; + let length = match usize::try_from(length) { + Ok(length) => length, + Err(e) => { + return Err(JsNativeError::typ() + .with_message(format!("could not convert length to usize: {e}")) + .into()); + } + }; + let mut vec = Vec::with_capacity(length); + for i in 0..length { + let value = object.get(i, context)?; + vec.push(T::try_from_js(&value, context)?); + } + + Ok(vec) + } +} + impl TryFromJs for JsBigInt { fn try_from_js(value: &JsValue, _context: &mut Context) -> JsResult { match value { @@ -250,3 +283,62 @@ impl TryFromJs for u128 { } } } + +#[test] +fn value_into_vec() { + use boa_engine::{run_test_actions, TestAction}; + use indoc::indoc; + + #[derive(Debug, PartialEq, Eq, boa_macros::TryFromJs)] + struct TestStruct { + inner: bool, + my_int: i16, + my_vec: Vec, + } + + run_test_actions([ + TestAction::assert_with_op( + indoc! {r#" + let value = { + inner: true, + my_int: 11, + my_vec: ["a", "b", "c"] + }; + value + "#}, + |value, context| { + let value = TestStruct::try_from_js(&value, context); + + match value { + Ok(value) => { + value + == TestStruct { + inner: true, + my_int: 11, + my_vec: vec!["a".to_string(), "b".to_string(), "c".to_string()], + } + } + _ => false, + } + }, + ), + TestAction::assert_with_op( + indoc!( + r#" + let wrong = { + inner: false, + my_int: 22, + my_vec: [{}, "e", "f"] + }; + wrong"# + ), + |value, context| { + let Err(value) = TestStruct::try_from_js(&value, context) else { + return false; + }; + assert!(value.to_string().contains("TypeError")); + true + }, + ), + ]); +}