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}