Skip to content

Proposal: ForeignObject API (name pending) #578

@Sharktheone

Description

@Sharktheone

Firstly, thanks for this amazing library, I'm using it in my own JS engine Yavashark.

I would propose an API where temporal_rs code can directly get properties from an JS object.

In my opinion, this makes sense, since there are many structs that essentially are an 1-to-1 mapping from a JS object to a struct in Rust.
In some cases, these mappings require validation (e.g PartialTime, CalendarFields), which with the API can then be moved into the temporal_rs implementation.
Additionally, it would also reduce the amount of binding code between the JS Engine and temporal_rs.

The API might look something like this:

ForeignObject trait
    pub trait ForeignObject {
        type Error: From<&'static str>;
        type Context;
        
        /// checks if the value is a string (e.g for things like timezone which can be a string or an object with a `timeZone` property)
        fn is_string(&self, context: &mut Self::Context) -> Result<bool, Self::Error>;
        
        /// converts the value to a string, errors if not a string
        fn as_string(&self, context: &mut Self::Context) -> Result<String, Self::Error>;
        
        /// gets a property by key, returns None if the property does not exist
        fn get_number(&self, key: &str, context: &mut Self::Context) -> Result<Option<f64>, Self::Error>;
        
        /// gets a property by key, returns None if the property does not exist
        fn get_string(&self, key: &str, context: &mut Self::Context) -> Result<Option<String>, Self::Error>;
        
        /// gets a property by key, returns None if the property does not exist
        fn get_bool(&self, key: &str, context: &mut Self::Context) -> Result<Option<bool>, Self::Error>;
        
        /// checks if the object has a property by key
        fn has_key(&self, key: &str, context: &mut Self::Context) -> Result<bool, Self::Error>;
        
        /// gets a property by key, returns None if the property does not exist (can be optimized by the implementor)
        fn get_tiny_str<const LEN: usize>(
            &self,
            key: &str,
            context: &mut Self::Context,
        ) -> Result<Option<TinyAsciiStr<LEN>>, Self::Error> { 
            match self.get_string(key, context)? {
                Some(s) => {
                    let tiny_str = TinyAsciiStr::<LEN>::from_str(&s)
                        .map_err(|_| "String too long or contains non-ascii characters")?;
                    Ok(Some(tiny_str))
                }
                None => Ok(None),
            }
            
        }
        
        /// gets a property by key, returns None if the property does not exist
        fn get_u8(&self, key: &str, context: &mut Self::Context) -> Result<Option<u8>, Self::Error> {
            match self.get_number(key, context)? {
                Some(n) if n >= 0.0 && n <= u8::MAX as f64 && n.fract() == 0.0 => Ok(Some(n as u8)),
                Some(_) => Err("Number out of range for u8".into()),
                None => Ok(None),
            }
        }
        
        /// gets a property by key, returns None if the property does not exist
        fn get_u16(&self, key: &str, context: &mut Self::Context) -> Result<Option<u16>, Self::Error> {
            match self.get_number(key, context)? {
                Some(n) if n >= 0.0 && n <= u16::MAX as f64 && n.fract() == 0.0 => Ok(Some(n as u16)),
                Some(_) => Err("Number out of range for u16".into()),
                None => Ok(None),
            }
        }
        
        /// gets a property by key, returns None if the property does not exist
        fn get_i32(&self, key: &str, context: &mut Self::Context) -> Result<Option<i32>, Self::Error> {
            match self.get_number(key, context)? {
                Some(n) if n >= i32::MIN as f64 && n <= i32::MAX as f64 && n.fract() == 0.0 => Ok(Some(n as i32)),
                Some(_) => Err("Number out of range for i32".into()),
                None => Ok(None),
            }
        }
        
        //... more methods for different number types as needed
    }

I think this can also be used in the C API (so with v8), however it requires some more binding glue between Rust and C

Is this something that is interesting to the temporal_rs maintainers? I am happy to discuss, also on the discord.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Status

    No status

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions