serde_wasm_bindgen/
lib.rs

1#![doc = include_str!("../README.md")]
2#![warn(missing_docs)]
3#![warn(clippy::missing_const_for_fn)]
4
5use js_sys::JsString;
6use wasm_bindgen::prelude::*;
7
8mod de;
9mod error;
10mod ser;
11
12pub use de::Deserializer;
13pub use error::Error;
14pub use ser::Serializer;
15
16type Result<T> = std::result::Result<T, Error>;
17
18fn static_str_to_js(s: &'static str) -> JsString {
19    use std::cell::RefCell;
20    use std::collections::HashMap;
21
22    #[derive(Default)]
23    struct PtrHasher {
24        addr: usize,
25    }
26
27    impl std::hash::Hasher for PtrHasher {
28        fn write(&mut self, _bytes: &[u8]) {
29            unreachable!();
30        }
31
32        fn write_usize(&mut self, addr_or_len: usize) {
33            if self.addr == 0 {
34                self.addr = addr_or_len;
35            }
36        }
37
38        fn finish(&self) -> u64 {
39            self.addr as _
40        }
41    }
42
43    type PtrBuildHasher = std::hash::BuildHasherDefault<PtrHasher>;
44
45    thread_local! {
46        // Since we're mainly optimising for converting the exact same string literal over and over again,
47        // which will always have the same pointer, we can speed things up by indexing by the string's pointer
48        // instead of its value.
49        static CACHE: RefCell<HashMap<*const str, JsString, PtrBuildHasher>> = Default::default();
50    }
51    CACHE.with(|cache| {
52        cache
53            .borrow_mut()
54            .entry(s)
55            .or_insert_with(|| s.into())
56            .clone()
57    })
58}
59
60/// Custom bindings to avoid using fallible `Reflect` for plain objects.
61#[wasm_bindgen]
62extern "C" {
63    type ObjectExt;
64
65    #[wasm_bindgen(method, indexing_getter)]
66    fn get_with_ref_key(this: &ObjectExt, key: &JsString) -> JsValue;
67
68    #[wasm_bindgen(method, indexing_setter)]
69    fn set(this: &ObjectExt, key: JsString, value: JsValue);
70}
71
72/// Converts [`JsValue`] into a Rust type.
73pub fn from_value<T: serde::de::DeserializeOwned>(value: JsValue) -> Result<T> {
74    T::deserialize(Deserializer::from(value))
75}
76
77/// Converts a Rust value into a [`JsValue`].
78pub fn to_value<T: serde::ser::Serialize + ?Sized>(value: &T) -> Result<JsValue> {
79    value.serialize(&Serializer::new())
80}
81
82/// Serialization and deserialization functions that pass JavaScript objects through unchanged.
83///
84/// This module is compatible with the `serde(with)` annotation, so for example if you create
85/// the struct
86///
87/// ```rust
88/// #[derive(serde::Serialize)]
89/// struct MyStruct {
90///     int_field: i32,
91///     #[serde(with = "serde_wasm_bindgen::preserve")]
92///     js_field: js_sys::Int8Array,
93/// }
94///
95/// let s = MyStruct {
96///     int_field: 5,
97///     js_field: js_sys::Int8Array::new_with_length(1000),
98/// };
99/// ```
100///
101/// then `serde_wasm_bindgen::to_value(&s)`
102/// will return a JsValue representing an object with two fields (`int_field` and `js_field`), where
103/// `js_field` will be an `Int8Array` pointing to the same underlying JavaScript object as `s.js_field` does.
104pub mod preserve {
105    use serde::{de::Error, Deserialize, Serialize};
106    use wasm_bindgen::{
107        convert::{FromWasmAbi, IntoWasmAbi},
108        JsCast, JsValue,
109    };
110
111    // Some arbitrary string that no one will collide with unless they try.
112    pub(crate) const PRESERVED_VALUE_MAGIC: &str = "1fc430ca-5b7f-4295-92de-33cf2b145d38";
113
114    struct Magic;
115
116    impl<'de> serde::de::Deserialize<'de> for Magic {
117        fn deserialize<D: serde::de::Deserializer<'de>>(de: D) -> Result<Self, D::Error> {
118            struct Visitor;
119
120            impl<'de> serde::de::Visitor<'de> for Visitor {
121                type Value = Magic;
122
123                fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
124                    formatter.write_str("serde-wasm-bindgen's magic string")
125                }
126
127                fn visit_str<E: Error>(self, s: &str) -> Result<Self::Value, E> {
128                    if s == PRESERVED_VALUE_MAGIC {
129                        Ok(Magic)
130                    } else {
131                        Err(E::invalid_value(serde::de::Unexpected::Str(s), &self))
132                    }
133                }
134            }
135
136            de.deserialize_str(Visitor)
137        }
138    }
139
140    #[derive(Serialize)]
141    #[serde(rename = "1fc430ca-5b7f-4295-92de-33cf2b145d38")]
142    struct PreservedValueSerWrapper(u32);
143
144    // Intentionally asymmetrical wrapper to ensure that only serde-wasm-bindgen preserves roundtrip.
145    #[derive(Deserialize)]
146    #[serde(rename = "1fc430ca-5b7f-4295-92de-33cf2b145d38")]
147    struct PreservedValueDeWrapper(Magic, u32);
148
149    /// Serialize any `JsCast` value.
150    ///
151    /// When used with the `Serializer` in `serde_wasm_bindgen`, this serializes the value by
152    /// passing it through as a `JsValue`.
153    ///
154    /// This function is compatible with the `serde(serialize_with)` derive annotation.
155    pub fn serialize<S: serde::Serializer, T: JsCast>(val: &T, ser: S) -> Result<S::Ok, S::Error> {
156        // It's responsibility of serde-wasm-bindgen's Serializer to clone the value.
157        // For all other serializers, using reference instead of cloning here will ensure that we don't
158        // create accidental leaks.
159        PreservedValueSerWrapper(val.as_ref().into_abi()).serialize(ser)
160    }
161
162    /// Deserialize any `JsCast` value.
163    ///
164    /// When used with the `Derializer` in `serde_wasm_bindgen`, this serializes the value by
165    /// passing it through as a `JsValue` and casting it.
166    ///
167    /// This function is compatible with the `serde(deserialize_with)` derive annotation.
168    pub fn deserialize<'de, D: serde::Deserializer<'de>, T: JsCast>(de: D) -> Result<T, D::Error> {
169        let wrap = PreservedValueDeWrapper::deserialize(de)?;
170        // When used with our deserializer this unsafe is correct, because the
171        // deserializer just converted a JsValue into_abi.
172        //
173        // Other deserializers are unlikely to end up here, thanks
174        // to the asymmetry between PreservedValueSerWrapper and
175        // PreservedValueDeWrapper. Even if some other deserializer ends up
176        // here, this may be incorrect but it shouldn't be UB because JsValues
177        // are represented using indices into a JS-side (i.e. bounds-checked)
178        // array.
179        let val: JsValue = unsafe { FromWasmAbi::from_abi(wrap.1) };
180        val.dyn_into().map_err(|e| {
181            D::Error::custom(format_args!(
182                "incompatible JS value {e:?} for type {}",
183                std::any::type_name::<T>()
184            ))
185        })
186    }
187}