rustwasm / wasm-bindgen

Facilitating high-level interactions between Wasm modules and JavaScript
https://rustwasm.github.io/docs/wasm-bindgen/
Apache License 2.0
7.82k stars 1.08k forks source link

Getter and Setter types #2869

Open gluax opened 2 years ago

gluax commented 2 years ago

Hi all we are trying to set proper types on getter and setters rather than it just showing as any from typescript on the field test3.

However, we can find very little documentation on how this crate actually works despite the guidebook.

The code is as follows:

use indexmap::IndexMap;
use serde_json::Value;
use wasm_bindgen::{prelude::wasm_bindgen, JsValue};

#[wasm_bindgen(typescript_custom_section)]
const METADATA_TYPE: &'static str = r#"
export type JSMetadata = {
    [key in string]: any
}
"#;

#[wasm_bindgen(typescript_type = "JSMetadata")]
struct Metadata;

pub trait ToJS: Sized {
    type Error;

    fn to_js(&self) -> Result<JsValue, Self::Error>;
}

impl ToJS for IndexMap<String, Value> {
    type Error = Box<dyn std::error::Error>;

    fn to_js(&self) -> Result<JsValue, Self::Error> {
        Ok(JsValue::from_serde(self)?)
    }
}

#[wasm_bindgen]
pub struct Tester {
    #[wasm_bindgen(skip)]
    pub test1: String,
    pub test2: u32,
    #[wasm_bindgen(skip, typescript_type = "JSMetadata")]
    pub test3: IndexMap<String, Value>,
}

#[wasm_bindgen]
impl Tester {
    #[wasm_bindgen(constructor)]
    pub fn new(a: &str) -> Self {
        Self {
            test2: 10,
            test3: {
                let mut aaa = IndexMap::new();
                aaa.insert(a.to_string(), a.into());
                aaa
            },
            test1: a.to_string(),
        }
    }

    #[wasm_bindgen(getter)]
    pub fn test1(&self) -> String {
        self.test1.clone()
    }

    #[wasm_bindgen(setter)]
    pub fn set_test1(&mut self, field: String) {
        self.test1 = field;
    }

    #[wasm_bindgen(getter)]
    pub fn test3(&self) -> JsValue {
        self.test3
            .to_js()
            .expect("Failed to convert field to JSMetadata")
    }

    #[wasm_bindgen(setter)]
    pub fn set_test3(&mut self, field: JsValue) {
        self.test3 = field
            .into_serde()
            .expect("JsValue was not of type {  [key in string]: any }.")
    }
}

How do we specify that for the field test3 it should be of type JSMetadata as we defined above?

trevyn commented 2 years ago

Is there a reason you have #[wasm_bindgen(skip)] on the test3 field? It seems like this might be causing your problem, and you don't need this even if you override both the getter and the setter, see the example here: https://rustwasm.github.io/docs/wasm-bindgen/reference/attributes/on-rust-exports/getter-and-setter.html

gluax commented 2 years ago

@trevyn I do have to skip since the type there since IndexMap, nor do HashMap, support the into_wasm_abi traits. Nor does the Value type from serde.

We managed to find a way around it by doing the following(though it still feels like this process could be improved):

use indexmap::IndexMap;
use serde_json::Value;
use wasm_bindgen::{prelude::wasm_bindgen, JsValue};

#[wasm_bindgen(typescript_custom_section)]
const METADATA_TYPE: &'static str = r#"
export type JSMetadata = {
    [key in string]: any
};
"#;

#[wasm_bindgen]
extern "C" {
    #[wasm_bindgen(typescript_type = "JSMetadata")]
    pub type Test3;
}

#[wasm_bindgen(getter_with_clone)]
pub struct Tester {
    pub test1: String,
    pub test2: u32,
    #[wasm_bindgen(skip, typescript_type = "JSMetadata")]
    pub test3: IndexMap<String, Value>,
}

#[wasm_bindgen]
impl Tester {
    #[wasm_bindgen(catch, constructor, js_class = "Tester")]
    pub fn new(test1: String, test2: u32, test3: Option<Test3>) -> Self {
        Self {
            test1,
            test2,
            test3: if let Some(t) = test3 {
                t.into_serde().unwrap()
            } else {
                Default::default()
            },
        }
    }

    #[wasm_bindgen(getter = test3, typescript_type = "JSMetadata")]
    pub fn test3(&self) -> JsValue {
        JsValue::from_serde(&self.test3).unwrap()
    }

    #[wasm_bindgen(setter = test3, typescript_type = "JSMetadata")]
    pub fn set_test3(&mut self, field: Test3) {
        self.test3 = field.into_serde().unwrap()
    }
}

Not sure if doing typescript_type on all of those instances of wasm_bindgen is necessary though.