// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use std::collections::BTreeMap; use rquickjs::{ atom::PredefinedAtom, function::IntoJsFunc, prelude::Func, Array, Coerced, Ctx, Error, Exception, FromJs, IntoAtom, IntoJs, Object, Result, Symbol, Undefined, Value, }; use crate::primordials::{BasePrimordials, Primordial}; pub trait ObjectExt<'js> { fn get_optional + Clone, V: FromJs<'js>>(&self, k: K) -> Result>; fn get_required, V: FromJs<'js>>( &self, k: K, object_name: &'static str, ) -> Result; fn into_object_or_throw(self, ctx: &Ctx<'js>, object_name: &'static str) -> Result>; } impl<'js> ObjectExt<'js> for Object<'js> { fn get_optional + Clone, V: FromJs<'js> + Sized>( &self, k: K, ) -> Result> { self.get::>(k) } fn get_required, V: FromJs<'js>>( &self, k: K, object_name: &'static str, ) -> Result { let k = k.as_ref(); self.get::<&str, Option>(k)?.ok_or_else(|| { Exception::throw_type( self.ctx(), &[object_name, " '", k, "' property required"].concat(), ) }) } fn into_object_or_throw(self, _: &Ctx<'js>, _: &'static str) -> Result> { Ok(self) } } impl<'js> ObjectExt<'js> for Value<'js> { fn get_optional + Clone, V: FromJs<'js>>(&self, k: K) -> Result> { if let Some(obj) = self.as_object() { return obj.get_optional(k); } Ok(None) } fn get_required, V: FromJs<'js>>( &self, k: K, object_name: &'static str, ) -> Result { self.as_object() .ok_or_else(|| not_a_object_error(self.ctx(), object_name))? .get_required(k, object_name) } fn into_object_or_throw( self, ctx: &Ctx<'js>, object_name: &'static str, ) -> Result> { self.into_object() .ok_or_else(|| not_a_object_error(ctx, object_name)) } } pub fn not_a_object_error(ctx: &Ctx<'_>, object_name: &str) -> Error { Exception::throw_type(ctx, &[object_name, " is not an object"].concat()) } pub trait CreateSymbol<'js> { fn for_description(ctx: &Ctx<'js>, description: &str) -> Result>; } impl<'js> CreateSymbol<'js> for Symbol<'js> { fn for_description(ctx: &Ctx<'js>, description: &str) -> Result> { BasePrimordials::get(ctx)? .function_symbol_for .call((description,)) } } pub struct Proxy<'js> { target: Value<'js>, options: Object<'js>, } impl<'js> IntoJs<'js> for Proxy<'js> { fn into_js(self, ctx: &Ctx<'js>) -> Result> { BasePrimordials::get(ctx)? .constructor_proxy .construct::<_, Value>((self.target, self.options)) } } impl<'js> Proxy<'js> { pub fn new(ctx: Ctx<'js>) -> Result { let options = Object::new(ctx.clone())?; Ok(Self { target: Undefined.into_value(ctx), options, }) } pub fn with_target(ctx: Ctx<'js>, target: Value<'js>) -> Result { let options = Object::new(ctx)?; Ok(Self { target, options }) } pub fn setter(&self, setter: Func) -> Result<()> where T: IntoJsFunc<'js, P> + 'js, { self.options.set(PredefinedAtom::Setter, setter)?; Ok(()) } pub fn getter(&self, getter: Func) -> Result<()> where T: IntoJsFunc<'js, P> + 'js, { self.options.set(PredefinedAtom::Getter, getter)?; Ok(()) } } pub fn map_to_entries<'js, K, V, M>(ctx: &Ctx<'js>, map: M) -> Result> where M: IntoIterator, K: IntoJs<'js>, V: IntoJs<'js>, { let array = Array::new(ctx.clone())?; for (idx, (key, value)) in map.into_iter().enumerate() { let entry = Array::new(ctx.clone())?; entry.set(0, key)?; entry.set(1, value)?; array.set(idx, entry)?; } Ok(array) } pub fn array_to_btree_map<'js>( ctx: &Ctx<'js>, array: Array<'js>, ) -> Result>> { let value = object_from_entries(ctx, array)?; let value = value.into_value(); BTreeMap::from_js(ctx, value) } pub fn object_from_entries<'js>(ctx: &Ctx<'js>, array: Array<'js>) -> Result> { let obj = Object::new(ctx.clone())?; for value in array.into_iter().flatten() { if let Some(entry) = value.as_array() { if let Ok(key) = entry.get::(0) { if let Ok(value) = entry.get::(1) { let _ = obj.set(key, value); //ignore result of failed } } } } Ok(obj) }