wasm-bindgen icon indicating copy to clipboard operation
wasm-bindgen copied to clipboard

Getter and Setter types

Open gluax opened this issue 3 years ago • 2 comments

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?

gluax avatar Apr 20 '22 00:04 gluax

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

trevyn avatar Apr 20 '22 04:04 trevyn

@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.

gluax avatar Apr 20 '22 04:04 gluax