Skip to main content

ferrijs_std/utils/
object.rs

1// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0
3use std::collections::BTreeMap;
4
5use rquickjs::{
6    atom::PredefinedAtom,
7    function::{Constructor, IntoJsFunc},
8    object::Property,
9    prelude::Func,
10    Array, Coerced, Ctx, Error, Exception, FromJs, IntoAtom, IntoJs, Object, Result, Undefined,
11    Value,
12};
13
14use crate::utils::primordials::{BasePrimordials, Primordial};
15
16pub trait ObjectExt<'js> {
17    fn get_optional<K: IntoAtom<'js> + Clone, V: FromJs<'js>>(&self, k: K) -> Result<Option<V>>;
18    fn get_required<K: AsRef<str>, V: FromJs<'js>>(
19        &self,
20        k: K,
21        object_name: &'static str,
22    ) -> Result<V>;
23    fn into_object_or_throw(self, ctx: &Ctx<'js>, object_name: &'static str)
24        -> Result<Object<'js>>;
25}
26
27impl<'js> ObjectExt<'js> for Object<'js> {
28    fn get_optional<K: IntoAtom<'js> + Clone, V: FromJs<'js> + Sized>(
29        &self,
30        k: K,
31    ) -> Result<Option<V>> {
32        self.get::<K, Option<V>>(k)
33    }
34
35    fn get_required<K: AsRef<str>, V: FromJs<'js>>(
36        &self,
37        k: K,
38        object_name: &'static str,
39    ) -> Result<V> {
40        let k = k.as_ref();
41        self.get::<&str, Option<V>>(k)?.ok_or_else(|| {
42            Exception::throw_type(
43                self.ctx(),
44                &[object_name, " '", k, "' property required"].concat(),
45            )
46        })
47    }
48
49    fn into_object_or_throw(self, _: &Ctx<'js>, _: &'static str) -> Result<Object<'js>> {
50        Ok(self)
51    }
52}
53
54impl<'js> ObjectExt<'js> for Value<'js> {
55    fn get_optional<K: IntoAtom<'js> + Clone, V: FromJs<'js>>(&self, k: K) -> Result<Option<V>> {
56        if let Some(obj) = self.as_object() {
57            return obj.get_optional(k);
58        }
59        Ok(None)
60    }
61
62    fn get_required<K: AsRef<str>, V: FromJs<'js>>(
63        &self,
64        k: K,
65        object_name: &'static str,
66    ) -> Result<V> {
67        self.as_object()
68            .ok_or_else(|| not_a_object_error(self.ctx(), object_name))?
69            .get_required(k, object_name)
70    }
71
72    fn into_object_or_throw(
73        self,
74        ctx: &Ctx<'js>,
75        object_name: &'static str,
76    ) -> Result<Object<'js>> {
77        self.into_object()
78            .ok_or_else(|| not_a_object_error(ctx, object_name))
79    }
80}
81
82pub fn not_a_object_error(ctx: &Ctx<'_>, object_name: &str) -> Error {
83    Exception::throw_type(ctx, &[object_name, " is not an object"].concat())
84}
85
86pub struct Proxy<'js> {
87    target: Value<'js>,
88    options: Object<'js>,
89}
90
91impl<'js> IntoJs<'js> for Proxy<'js> {
92    fn into_js(self, ctx: &Ctx<'js>) -> Result<Value<'js>> {
93        BasePrimordials::get(ctx)?
94            .constructor_proxy
95            .construct::<_, Value>((self.target, self.options))
96    }
97}
98
99impl<'js> Proxy<'js> {
100    pub fn new(ctx: Ctx<'js>) -> Result<Self> {
101        let options = Object::new(ctx.clone())?;
102        Ok(Self {
103            target: Undefined.into_value(ctx),
104            options,
105        })
106    }
107
108    pub fn with_target(ctx: Ctx<'js>, target: Value<'js>) -> Result<Self> {
109        let options = Object::new(ctx)?;
110        Ok(Self { target, options })
111    }
112
113    pub fn setter<T, P: 'js>(&self, setter: Func<T, P>) -> Result<()>
114    where
115        T: IntoJsFunc<'js, P> + 'js,
116    {
117        self.options.set(PredefinedAtom::Setter, setter)?;
118        Ok(())
119    }
120
121    pub fn getter<T, P: 'js>(&self, getter: Func<T, P>) -> Result<()>
122    where
123        T: IntoJsFunc<'js, P> + 'js,
124    {
125        self.options.set(PredefinedAtom::Getter, getter)?;
126        Ok(())
127    }
128}
129
130pub fn array_to_btree_map<'js>(
131    ctx: &Ctx<'js>,
132    array: Array<'js>,
133) -> Result<BTreeMap<String, Coerced<String>>> {
134    let value = object_from_entries(ctx, array)?;
135    let value = value.into_value();
136    BTreeMap::from_js(ctx, value)
137}
138
139pub fn object_from_entries<'js>(ctx: &Ctx<'js>, array: Array<'js>) -> Result<Object<'js>> {
140    let obj = Object::new(ctx.clone())?;
141    for value in array.into_iter().flatten() {
142        if let Some(entry) = value.as_array() {
143            if let Ok(key) = entry.get::<Value>(0) {
144                if let Ok(value) = entry.get::<Value>(1) {
145                    let _ = obj.set(key, value); //ignore result of failed
146                }
147            }
148        }
149    }
150    Ok(obj)
151}
152
153/// Build a constructor that behaves like `class Name extends Parent`
154pub fn define_subclass<'js, F, P>(
155    ctx: &Ctx<'js>,
156    name: &str,
157    parent: &Constructor<'js>,
158    construct: F,
159) -> Result<Constructor<'js>>
160where
161    F: IntoJsFunc<'js, P> + 'js,
162{
163    let parent_proto: Object = parent.get(PredefinedAtom::Prototype)?;
164    let proto = Object::new(ctx.clone())?;
165    proto.set_prototype(Some(&parent_proto))?;
166    let constructor = Constructor::new_prototype(ctx, proto, construct)?;
167    constructor.set_prototype(parent.as_object())?;
168    constructor.prop(PredefinedAtom::Name, Property::from(name).configurable())?;
169    Ok(constructor)
170}