serde-wasm-bindgen-0.6.5/.cargo/config.toml000064400000000000000000000003560072674642500166560ustar 00000000000000[build] # Compare sizes using non-trapping float-to-int conversions. # This feature has landed long ago in all engines, but Rust doesn't enable it by default yet. rustflags = ["-C", "target-feature=+nontrapping-fptoint,+bulk-memory"] serde-wasm-bindgen-0.6.5/.cargo_vcs_info.json0000644000000001360000000000100145170ustar { "git": { "sha1": "f073bd40ee5a354e3e9e2ac376300368d28067fe" }, "path_in_vcs": "" }serde-wasm-bindgen-0.6.5/.github/FUNDING.yml000064400000000000000000000014720072674642500165200ustar 00000000000000# These are supported funding model platforms github: RReverser # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] patreon: # Replace with a single Patreon username open_collective: # Replace with a single Open Collective username ko_fi: # Replace with a single Ko-fi username tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry liberapay: # Replace with a single Liberapay username issuehunt: # Replace with a single IssueHunt username otechie: # Replace with a single Otechie username lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] serde-wasm-bindgen-0.6.5/.github/workflows/main.yml000064400000000000000000000014670072674642500204130ustar 00000000000000name: CI on: [push, pull_request] jobs: test: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v2 - name: Install Rust uses: hecrj/setup-rust-action@v1 with: components: clippy,rustfmt - name: Install wasm-pack uses: jetli/wasm-pack-action@v0.3.0 - name: Run tests in Node.js run: wasm-pack test --node - name: Run tests in Chrome run: wasm-pack test --headless --chrome - name: Run tests in Firefox run: wasm-pack test --headless --firefox - name: Clippy checks run: cargo clippy --all-targets -- -D warnings - name: Format run: cargo fmt - name: Commit changes if any uses: stefanzweifel/git-auto-commit-action@v4 serde-wasm-bindgen-0.6.5/.gitignore000064400000000000000000000000250072674642500153240ustar 00000000000000/target **/*.rs.bk serde-wasm-bindgen-0.6.5/Cargo.toml0000644000000030720000000000100125170ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2018" name = "serde-wasm-bindgen" version = "0.6.5" authors = ["Ingvar Stepanyan "] description = "Native Serde adapter for wasm-bindgen" readme = "README.md" keywords = [ "serde", "serialization", "javascript", "wasm", "webassembly", ] categories = [ "development-tools::ffi", "wasm", "encoding", ] license = "MIT" repository = "https://github.com/RReverser/serde-wasm-bindgen" [package.metadata.docs.rs] targets = ["wasm32-unknown-unknown"] [profile.release] lto = true codegen-units = 1 debug = 2 [dependencies.js-sys] version = "^0.3" [dependencies.serde] version = "1.0.193" features = ["derive"] [dependencies.wasm-bindgen] version = "0.2.83" [dev-dependencies.bincode] version = "1.3.3" [dev-dependencies.getrandom] version = "0.2" features = ["js"] [dev-dependencies.maplit] version = "1.0.2" [dev-dependencies.proptest] version = "1.0" features = ["std"] default-features = false [dev-dependencies.serde_bytes] version = "0.11.1" [dev-dependencies.serde_json] version = "1.0.39" [dev-dependencies.wasm-bindgen-test] version = "0.3.24" serde-wasm-bindgen-0.6.5/Cargo.toml.orig000064400000000000000000000022700072674642500162270ustar 00000000000000[package] name = "serde-wasm-bindgen" version = "0.6.5" authors = ["Ingvar Stepanyan "] edition = "2018" readme = "README.md" license = "MIT" repository = "https://github.com/RReverser/serde-wasm-bindgen" description = "Native Serde adapter for wasm-bindgen" categories = ["development-tools::ffi", "wasm", "encoding"] keywords = ["serde", "serialization", "javascript", "wasm", "webassembly"] [dependencies] serde = { version = "1.0.193", features = ["derive"] } js-sys = "^0.3" wasm-bindgen = "0.2.83" [dev-dependencies] wasm-bindgen-test = "0.3.24" serde_bytes = "0.11.1" serde_json = "1.0.39" maplit = "1.0.2" bincode = "1.3.3" [dev-dependencies.proptest] version = "1.0" # The default feature set includes things like process forking which are not # supported in Web Assembly. default-features = false # Enable using the `std` crate. features = ["std"] [dev-dependencies.getrandom] version = "0.2" features = ["js"] [workspace] members = ["benchmarks"] [profile.release] lto = true codegen-units = 1 debug = true [patch.crates-io] proptest = { git = "https://github.com/RReverser/proptest", branch = "fix-wasm" } [package.metadata.docs.rs] targets = ["wasm32-unknown-unknown"] serde-wasm-bindgen-0.6.5/LICENSE000064400000000000000000000021060072674642500143430ustar 00000000000000MIT License Copyright (c) 2019 Cloudflare, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. serde-wasm-bindgen-0.6.5/README.md000064400000000000000000000205500072674642500146200ustar 00000000000000[![Crates.io](https://img.shields.io/crates/d/serde-wasm-bindgen?logo=rust)](https://crates.io/crates/serde-wasm-bindgen) [![docs.rs](https://img.shields.io/docsrs/serde-wasm-bindgen)](https://docs.rs/serde-wasm-bindgen/) [![GitHub Sponsors](https://img.shields.io/github/sponsors/rreverser)](https://github.com/sponsors/RReverser) This is a native integration of [Serde](https://serde.rs/) with [wasm-bindgen](https://github.com/rustwasm/wasm-bindgen). It allows to convert Rust data types into native JavaScript types and vice versa. Initially this library was created while working for [@Cloudflare](https://github.com/cloudflare) as [an alternative implementation](https://github.com/rustwasm/wasm-bindgen/issues/1258) to the JSON-based Serde support built into the `wasm-bindgen` but, [nowadays](https://github.com/rustwasm/wasm-bindgen/pull/3031) `serde-wasm-bindgen` is the officially preferred approach. It provides much smaller code size overhead than JSON, and, in most common cases, provides much faster serialization/deserialization as well. ## Usage Copied almost verbatim from the [`wasm-bindgen` guide](https://rustwasm.github.io/wasm-bindgen/reference/arbitrary-data-with-serde.html#serializing-and-deserializing-arbitrary-data-into-and-from-jsvalue-with-serde): ### Add dependencies To use `serde-wasm-bindgen`, you first have to add it as a dependency in your `Cargo.toml`. You also need the `serde` crate, with the `derive` feature enabled, to allow your types to be serialized and deserialized with Serde. ```toml [dependencies] serde = { version = "1.0", features = ["derive"] } serde-wasm-bindgen = "0.4" ``` ### Derive the `Serialize` and `Deserialize` Traits Add `#[derive(Serialize, Deserialize)]` to your type. All of your type members must also be supported by Serde, i.e. their types must also implement the `Serialize` and `Deserialize` traits. Note that you don't need to use the `#[wasm_bindgen]` macro. ```rust use serde::{Serialize, Deserialize}; #[derive(Serialize, Deserialize)] pub struct Example { pub field1: HashMap, pub field2: Vec>, pub field3: [f32; 4], } ``` ### Send it to JavaScript with `serde_wasm_bindgen::to_value` ```rust #[wasm_bindgen] pub fn send_example_to_js() -> Result { let mut field1 = HashMap::new(); field1.insert(0, String::from("ex")); let example = Example { field1, field2: vec![vec![1., 2.], vec![3., 4.]], field3: [1., 2., 3., 4.] }; Ok(serde_wasm_bindgen::to_value(&example)?) } ``` ### Receive it from JavaScript with `serde_wasm_bindgen::from_value` ```rust #[wasm_bindgen] pub fn receive_example_from_js(val: JsValue) -> Result<(), JsValue> { let example: Example = serde_wasm_bindgen::from_value(val)?; /* …do something with `example`… */ Ok(()) } ``` ### JavaScript Usage In the `JsValue` that JavaScript gets, `field1` will be a `Map`, `field2` will be an `Array>`, and `field3` will be an `Array`. ```js import { send_example_to_js, receive_example_from_js } from "example"; // Get the example object from wasm. let example = send_example_to_js(); // Add another "Vec" element to the end of the "Vec>" example.field2.push([5, 6]); // Send the example object back to wasm. receive_example_from_js(example); ``` ## Supported Types Note that, even though it might often be the case, by default this library doesn't attempt to be strictly compatible with JSON, instead prioritising better compatibility with common JavaScript idioms and representations. If you need JSON compatibility (e.g. you want to serialize `HashMap` as plain objects instead of JavaScript `Map` instances), use the [`Serializer::json_compatible()`](https://docs.rs/serde-wasm-bindgen/latest/serde_wasm_bindgen/struct.Serializer.html#method.json_compatible) preset. By default, Rust ⬄ JavaScript conversions in `serde-wasm-bindgen` follow this table: | Rust | JavaScript | Also supported in `from_value` | |-----------------------------------|--------------------------------------|--------------------------------| | `()` and `Option::None` | `undefined` | `null` | | `bool` | `boolean` | | | `f32`, `f64` | `number` | | | `u8`, `i8`, …, `u32`, `i32` | `number` in the [safe integer] range | | | `u64`, `i64`, `usize`, `isize` | `number` in the [safe integer] range | `bigint` | | `u128`, `i128` | `bigint` | | | `String` | `string` | | | `char` | single-codepoint `string` | | | `Enum::Variant { … }` | [as configured in Serde] | | | `HashMap`, `BTreeMap`, etc. | `Map` | any iterable over `[K, V]` | | `Struct { key1: value1, … }` | `{ key1: value1, … }` object | | | tuple, `Vec`, `HashSet`, etc. | `T[]` array | any iterable over `T` | | [`serde_bytes`] byte buffer | `Uint8Array` | `ArrayBuffer`, `Array` | [as configured in Serde]: https://serde.rs/enum-representations.html [safe integer]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isSafeInteger [`serde_bytes`]: https://github.com/serde-rs/bytes The first two columns show idiomatic representations on Rust and JavaScript sides, while the 3rd column shows which JavaScript values are additionally supported when deserializing from JavaScript to the Rust type. ### Serializer configuration options You can customize serialization from Rust to JavaScript by setting the following options on the [`Serializer::new()`](https://docs.rs/serde-wasm-bindgen/latest/serde_wasm_bindgen/struct.Serializer.html) instance (all default to false): - `.serialize_missing_as_null(true)`: Serialize `()`, unit structs and `Option::None` to `null` instead of `undefined`. - `.serialize_maps_as_objects(true)`: Serialize maps into plain JavaScript objects instead of ES2015 Maps. - `.serialize_large_number_types_as_bigints(true)`: Serialize `u64`, `i64`, `usize` and `isize` to `bigint`s instead of attempting to fit them into the [safe integer] `number` or failing. - `.serialize_bytes_as_arrays(true)`: Serialize bytes into plain JavaScript arrays instead of ES2015 Uint8Arrays. You can also use the `Serializer::json_compatible()` preset to create a JSON compatible serializer. It enables `serialize_missing_as_null`, `serialize_maps_as_objects`, and `serialize_bytes_as_arrays` under the hood. ### Preserving JavaScript values Sometimes you want to preserve original JavaScript value instead of converting it into a Rust type. This is particularly useful for types that can't be converted without losing the data, such as [`Date`](https://docs.rs/js-sys/latest/js_sys/struct.Date.html), [`RegExp`](https://docs.rs/js-sys/latest/js_sys/struct.RegExp.html) or 3rd-party types. `serde_wasm_bindgen::preserve` allows you to do just that: ```rust #[derive(Serialize, Deserialize)] pub struct Example { pub regular_field: i32, #[serde(with = "serde_wasm_bindgen::preserve")] pub preserved_date: js_sys::Date, #[serde(with = "serde_wasm_bindgen::preserve")] pub preserved_arbitrary_value: JsValue, } ``` ## TypeScript support There's no built-in type generation in this crate, but you can [tsify](https://github.com/madonoharu/tsify) with the `js` feature which integrates with `serde-wasm-bindgen` under the hood. Aside from generating structural typings, it also allows to derive `IntoWasmAbi` / `FromWasmAbi` so that you don't have to write `from_value` / `to_value` by hand. ## License Licensed under the MIT license. See the [LICENSE](https://github.com/RReverser/serde-wasm-bindgen/blob/master/LICENSE) file for details. serde-wasm-bindgen-0.6.5/src/de.rs000064400000000000000000000562420072674642500150750ustar 00000000000000use js_sys::{Array, ArrayBuffer, JsString, Map, Number, Object, Symbol, Uint8Array}; use serde::de::value::{MapDeserializer, SeqDeserializer}; use serde::de::{self, IntoDeserializer}; use std::convert::TryFrom; use wasm_bindgen::convert::IntoWasmAbi; use wasm_bindgen::{JsCast, JsValue, UnwrapThrowExt}; use crate::preserve::PRESERVED_VALUE_MAGIC; use crate::{static_str_to_js, Error, ObjectExt, Result}; /// Provides [`de::SeqAccess`] from any JS iterator. struct SeqAccess { iter: js_sys::IntoIter, } impl<'de> de::SeqAccess<'de> for SeqAccess { type Error = Error; fn next_element_seed>( &mut self, seed: T, ) -> Result> { Ok(match self.iter.next().transpose()? { Some(value) => Some(seed.deserialize(Deserializer::from(value))?), None => None, }) } } /// Provides [`de::MapAccess`] from any JS iterator that returns `[key, value]` pairs. struct MapAccess { iter: js_sys::IntoIter, next_value: Option, } impl MapAccess { const fn new(iter: js_sys::IntoIter) -> Self { Self { iter, next_value: None, } } } impl<'de> de::MapAccess<'de> for MapAccess { type Error = Error; fn next_key_seed>(&mut self, seed: K) -> Result> { debug_assert!(self.next_value.is_none()); Ok(match self.iter.next().transpose()? { Some(pair) => { let (key, value) = convert_pair(pair); self.next_value = Some(value); Some(seed.deserialize(key)?) } None => None, }) } fn next_value_seed>(&mut self, seed: V) -> Result { seed.deserialize(self.next_value.take().unwrap_throw()) } } struct ObjectAccess { obj: ObjectExt, fields: std::slice::Iter<'static, &'static str>, next_value: Option, } impl ObjectAccess { fn new(obj: ObjectExt, fields: &'static [&'static str]) -> Self { Self { obj, fields: fields.iter(), next_value: None, } } } fn str_deserializer(s: &str) -> de::value::StrDeserializer { de::IntoDeserializer::into_deserializer(s) } impl<'de> de::MapAccess<'de> for ObjectAccess { type Error = Error; fn next_key_seed>(&mut self, seed: K) -> Result> { debug_assert!(self.next_value.is_none()); for field in &mut self.fields { let js_field = static_str_to_js(field); let next_value = self.obj.get_with_ref_key(&js_field); // If this value is `undefined`, it might be actually a missing field; // double-check with an `in` operator if so. let is_missing_field = next_value.is_undefined() && !js_field.js_in(&self.obj); if !is_missing_field { self.next_value = Some(Deserializer::from(next_value)); return Ok(Some(seed.deserialize(str_deserializer(field))?)); } } Ok(None) } fn next_value_seed>(&mut self, seed: V) -> Result { seed.deserialize(self.next_value.take().unwrap_throw()) } } enum PreservedValueAccess { OnMagic(JsValue), OnValue(JsValue), Done, } impl<'de> de::SeqAccess<'de> for PreservedValueAccess { type Error = Error; fn next_element_seed>( &mut self, seed: T, ) -> Result> { // Temporary replacement to avoid borrow checker issues when moving out `JsValue`. let this = std::mem::replace(self, Self::Done); match this { Self::OnMagic(value) => { *self = Self::OnValue(value); seed.deserialize(str_deserializer(PRESERVED_VALUE_MAGIC)) .map(Some) } Self::OnValue(value) => seed .deserialize(Deserializer { value: JsValue::from(value.into_abi()), }) .map(Some), Self::Done => Ok(None), } } } /// Provides [`de::EnumAccess`] from given JS values for the `tag` and the `payload`. struct EnumAccess { tag: Deserializer, payload: Deserializer, } impl<'de> de::EnumAccess<'de> for EnumAccess { type Error = Error; type Variant = Deserializer; fn variant_seed>( self, seed: V, ) -> Result<(V::Value, Self::Variant)> { Ok((seed.deserialize(self.tag)?, self.payload)) } } /// A newtype that allows using any [`JsValue`] as a [`de::Deserializer`]. pub struct Deserializer { value: JsValue, } impl From for Deserializer { fn from(value: JsValue) -> Self { Self { value } } } // Ideally this would be implemented for `JsValue` instead, but we can't because // of the orphan rule. impl<'de> IntoDeserializer<'de, Error> for Deserializer { type Deserializer = Self; fn into_deserializer(self) -> Self::Deserializer { self } } /// Destructures a JS `[key, value]` pair into a tuple of [`Deserializer`]s. fn convert_pair(pair: JsValue) -> (Deserializer, Deserializer) { let pair = pair.unchecked_into::(); (pair.get(0).into(), pair.get(1).into()) } impl Deserializer { fn as_object_entries(&self) -> Option { if self.value.is_object() { Some(Object::entries(self.value.unchecked_ref())) } else { None } } fn is_nullish(&self) -> bool { self.value.loose_eq(&JsValue::NULL) } fn as_bytes(&self) -> Option> { let temp; let v = if let Some(v) = self.value.dyn_ref::() { v } else if let Some(v) = self.value.dyn_ref::() { temp = Uint8Array::new(v); &temp } else { return None; }; Some(v.to_vec()) } #[cold] fn invalid_type_(&self, visitor: &dyn de::Expected) -> Error { let string; let bytes; let unexpected = if self.is_nullish() { de::Unexpected::Unit } else if let Some(v) = self.value.as_bool() { de::Unexpected::Bool(v) } else if let Some(v) = self.value.as_f64() { de::Unexpected::Float(v) } else if let Some(v) = self.value.as_string() { string = v; de::Unexpected::Str(&string) } else if let Some(v) = self.as_bytes() { bytes = v; de::Unexpected::Bytes(&bytes) } else { string = format!("{:?}", self.value); de::Unexpected::Other(&string) }; de::Error::invalid_type(unexpected, visitor) } fn invalid_type<'de, V: de::Visitor<'de>>(&self, visitor: V) -> Result { Err(self.invalid_type_(&visitor)) } fn as_safe_integer(&self) -> Option { if Number::is_safe_integer(&self.value) { return Some(self.value.unchecked_into_f64() as i64); } None } fn deserialize_from_js_number_signed<'de, V: de::Visitor<'de>>( &self, visitor: V, ) -> Result { match self.as_safe_integer() { Some(v) => visitor.visit_i64(v), _ => self.invalid_type(visitor), } } fn deserialize_from_js_number_unsigned<'de, V: de::Visitor<'de>>( &self, visitor: V, ) -> Result { match self.as_safe_integer() { Some(v) if v >= 0 => visitor.visit_u64(v as _), _ => self.invalid_type(visitor), } } fn deserialize_from_array<'de, V: de::Visitor<'de>>( &self, visitor: V, array: &Array, ) -> Result { visitor.visit_seq(SeqDeserializer::new(array.iter().map(Deserializer::from))) } } impl<'de> de::Deserializer<'de> for Deserializer { type Error = Error; fn deserialize_any>(self, visitor: V) -> Result { if self.is_nullish() { // Ideally we would only treat `undefined` as `()` / `None` which would be semantically closer // to JS definitions, but, unfortunately, WebIDL generates missing values as `null` // and we probably want to support these as well. visitor.visit_unit() } else if let Some(v) = self.value.as_bool() { visitor.visit_bool(v) } else if self.value.is_bigint() { match i64::try_from(self.value) { Ok(v) => visitor.visit_i64(v), Err(value) => match u64::try_from(value) { Ok(v) => visitor.visit_u64(v), Err(_) => Err(de::Error::custom("Couldn't deserialize i64 or u64 from a BigInt outside i64::MIN..u64::MAX bounds")) } } } else if let Some(v) = self.value.as_f64() { if Number::is_safe_integer(&self.value) { visitor.visit_i64(v as i64) } else { visitor.visit_f64(v) } } else if let Some(v) = self.value.as_string() { visitor.visit_string(v) } else if Array::is_array(&self.value) { self.deserialize_seq(visitor) } else if let Some(bytes) = self.as_bytes() { // We need to handle this here because serde uses `deserialize_any` // for internally tagged enums visitor.visit_byte_buf(bytes) } else if self.value.is_object() && // The only reason we want to support objects here is because serde uses // `deserialize_any` for internally tagged enums // (see https://github.com/RReverser/serde-wasm-bindgen/pull/4#discussion_r352245020). // // We expect such enums to be represented via plain JS objects, so let's explicitly // exclude Sets and other iterables. These should be deserialized via concrete // `deserialize_*` methods instead of us trying to guess the right target type. // // We still do support Map, so that the format described here stays a self-describing // format: we happen to serialize to Map, and it is not ambiguous. // // Hopefully we can rid of these hacks altogether once // https://github.com/serde-rs/serde/issues/1183 is implemented / fixed on serde side. (!Symbol::iterator().js_in(&self.value) || self.value.has_type::()) { self.deserialize_map(visitor) } else { self.invalid_type(visitor) } } fn deserialize_unit>(self, visitor: V) -> Result { if self.is_nullish() { visitor.visit_unit() } else { self.invalid_type(visitor) } } fn deserialize_unit_struct>( self, _name: &'static str, visitor: V, ) -> Result { self.deserialize_unit(visitor) } fn deserialize_bool>(self, visitor: V) -> Result { if let Some(v) = self.value.as_bool() { visitor.visit_bool(v) } else { self.invalid_type(visitor) } } // Serde happily converts `f64` to `f32` (with checks), so we can forward. fn deserialize_f32>(self, visitor: V) -> Result { self.deserialize_f64(visitor) } fn deserialize_f64>(self, visitor: V) -> Result { if let Some(v) = self.value.as_f64() { visitor.visit_f64(v) } else { self.invalid_type(visitor) } } fn deserialize_identifier>(self, visitor: V) -> Result { self.deserialize_str(visitor) } fn deserialize_str>(self, visitor: V) -> Result { self.deserialize_string(visitor) } fn deserialize_string>(self, visitor: V) -> Result { if let Some(v) = self.value.as_string() { visitor.visit_string(v) } else { self.invalid_type(visitor) } } // Serde happily converts any integer to any integer (with checks), so let's forward all of // these to 64-bit methods to save some space in the generated WASM. fn deserialize_i8>(self, visitor: V) -> Result { self.deserialize_from_js_number_signed(visitor) } fn deserialize_i16>(self, visitor: V) -> Result { self.deserialize_from_js_number_signed(visitor) } fn deserialize_i32>(self, visitor: V) -> Result { self.deserialize_from_js_number_signed(visitor) } fn deserialize_u8>(self, visitor: V) -> Result { self.deserialize_from_js_number_unsigned(visitor) } fn deserialize_u16>(self, visitor: V) -> Result { self.deserialize_from_js_number_unsigned(visitor) } fn deserialize_u32>(self, visitor: V) -> Result { self.deserialize_from_js_number_unsigned(visitor) } /// Supported inputs: /// - `BigInt` within `i64` boundaries. /// - number within safe integer boundaries. fn deserialize_i64>(self, visitor: V) -> Result { if self.value.is_bigint() { match i64::try_from(self.value) { Ok(v) => visitor.visit_i64(v), Err(_) => Err(de::Error::custom( "Couldn't deserialize i64 from a BigInt outside i64::MIN..i64::MAX bounds", )), } } else { self.deserialize_from_js_number_signed(visitor) } } /// Supported inputs: /// - `BigInt` within `u64` boundaries. /// - number within safe integer boundaries. fn deserialize_u64>(self, visitor: V) -> Result { if self.value.is_bigint() { match u64::try_from(self.value) { Ok(v) => visitor.visit_u64(v), Err(_) => Err(de::Error::custom( "Couldn't deserialize u64 from a BigInt outside u64::MIN..u64::MAX bounds", )), } } else { self.deserialize_from_js_number_unsigned(visitor) } } /// Supported inputs: /// - `BigInt` within `i128` boundaries. fn deserialize_i128>(self, visitor: V) -> Result { if self.value.is_bigint() { match i128::try_from(self.value) { Ok(v) => visitor.visit_i128(v), Err(_) => Err(de::Error::custom( "Couldn't deserialize i128 from a BigInt outside i128::MIN..i128::MAX bounds", )), } } else { self.invalid_type(visitor) } } /// Supported inputs: /// - `BigInt` within `u128` boundaries. fn deserialize_u128>(self, visitor: V) -> Result { if self.value.is_bigint() { match u128::try_from(self.value) { Ok(v) => visitor.visit_u128(v), Err(_) => Err(de::Error::custom( "Couldn't deserialize u128 from a BigInt outside u128::MIN..u128::MAX bounds", )), } } else { self.invalid_type(visitor) } } /// Converts a JavaScript string to a Rust `char`. /// /// By default we don't perform detection of single chars because it's pretty complicated, /// but if we get a hint that they're expected, this methods allows to avoid heap allocations /// of an intermediate `String` by directly converting numeric codepoints instead. fn deserialize_char>(self, visitor: V) -> Result { if let Some(s) = self.value.dyn_ref::() { if let Some(c) = s.as_char() { return visitor.visit_char(c); } } self.invalid_type(visitor) } /// Deserializes `undefined` or `null` into `None` and any other value into `Some(value)`. // Serde can deserialize `visit_unit` into `None`, but can't deserialize arbitrary value // as `Some`, so we need to provide own simple implementation. fn deserialize_option>(self, visitor: V) -> Result { if !self.is_nullish() { visitor.visit_some(self) } else { visitor.visit_none() } } /// Forwards to deserializing newtype contents. fn deserialize_newtype_struct>( self, _name: &'static str, visitor: V, ) -> Result { visitor.visit_newtype_struct(self) } /// Supported inputs: /// - JS iterable (an object with [`[Symbol.iterator]`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/iterator)). /// /// Supported outputs: /// - Any Rust sequence from Serde point of view ([`Vec`], [`HashSet`](std::collections::HashSet), etc.) fn deserialize_seq>(self, visitor: V) -> Result { if let Some(arr) = self.value.dyn_ref::() { self.deserialize_from_array(visitor, arr) } else if let Some(iter) = js_sys::try_iter(&self.value)? { visitor.visit_seq(SeqAccess { iter }) } else { self.invalid_type(visitor) } } /// Forwards to [`Self::deserialize_seq`](#method.deserialize_seq). fn deserialize_tuple>(self, _len: usize, visitor: V) -> Result { self.deserialize_seq(visitor) } /// Forwards to [`Self::deserialize_tuple`](#method.deserialize_tuple). fn deserialize_tuple_struct>( self, name: &'static str, len: usize, visitor: V, ) -> Result { if name == PRESERVED_VALUE_MAGIC { return visitor.visit_seq(PreservedValueAccess::OnMagic(self.value)); } self.deserialize_tuple(len, visitor) } /// Supported inputs: /// - A JS iterable that is expected to return `[key, value]` pairs. /// - A JS object, which will be iterated using [`Object.entries`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries). /// /// Supported outputs: /// - A Rust key-value map ([`HashMap`](std::collections::HashMap), [`BTreeMap`](std::collections::BTreeMap), etc.). /// - A typed Rust structure with `#[derive(Deserialize)]`. fn deserialize_map>(self, visitor: V) -> Result { match js_sys::try_iter(&self.value)? { Some(iter) => visitor.visit_map(MapAccess::new(iter)), None => match self.as_object_entries() { Some(arr) => visitor.visit_map(MapDeserializer::new(arr.iter().map(convert_pair))), None => self.invalid_type(visitor), }, } } /// Supported inputs: /// - A plain JS object. /// /// Supported outputs: /// - A typed Rust structure with `#[derive(Deserialize)]`. fn deserialize_struct>( self, _name: &'static str, fields: &'static [&'static str], visitor: V, ) -> Result { let obj = if self.value.is_object() { self.value.unchecked_into::() } else { return self.invalid_type(visitor); }; visitor.visit_map(ObjectAccess::new(obj, fields)) } /// Here we try to be compatible with `serde-json`, which means supporting: /// - `"Variant"` - gets converted to a unit variant `MyEnum::Variant` /// - `{ Variant: ...payload... }` - gets converted to a `MyEnum::Variant { ...payload... }`. fn deserialize_enum>( self, _name: &'static str, _variants: &'static [&'static str], visitor: V, ) -> Result { let access = if self.value.is_string() { EnumAccess { tag: self.value.into(), payload: JsValue::UNDEFINED.into(), } } else if let Some(entries) = self.as_object_entries() { if entries.length() != 1 { return Err(de::Error::invalid_length(entries.length() as _, &"1")); } let entry = entries.get(0); let (tag, payload) = convert_pair(entry); EnumAccess { tag, payload } } else { return self.invalid_type(visitor); }; visitor.visit_enum(access) } /// Ignores any value without calling to the JS side even to check its type. fn deserialize_ignored_any>(self, visitor: V) -> Result { visitor.visit_unit() } /// We can't take references to JS memory, so forwards to an owned [`Self::deserialize_byte_buf`](#method.deserialize_byte_buf). fn deserialize_bytes>(self, visitor: V) -> Result { self.deserialize_byte_buf(visitor) } /// Serde expects `visit_byte_buf` to be called only in response to an explicit `deserialize_bytes`, /// so we provide conversions here. /// /// Supported inputs: /// - `ArrayBuffer` - converted to an `Uint8Array` view first. /// - `Uint8Array`, `Array` - copied to a newly created `Vec` on the Rust side. fn deserialize_byte_buf>(self, visitor: V) -> Result { if let Some(bytes) = self.as_bytes() { visitor.visit_byte_buf(bytes) } else if let Some(arr) = self.value.dyn_ref::() { self.deserialize_from_array(visitor, arr) } else { self.invalid_type(visitor) } } fn is_human_readable(&self) -> bool { true } } impl<'de> de::VariantAccess<'de> for Deserializer { type Error = Error; fn unit_variant(self) -> Result<()> { de::Deserialize::deserialize(self) } fn newtype_variant_seed>(self, seed: T) -> Result { seed.deserialize(self) } fn tuple_variant>(self, len: usize, visitor: V) -> Result { de::Deserializer::deserialize_tuple(self, len, visitor) } fn struct_variant>( self, fields: &'static [&'static str], visitor: V, ) -> Result { de::Deserializer::deserialize_struct(self, "", fields, visitor) } } serde-wasm-bindgen-0.6.5/src/error.rs000064400000000000000000000026600072674642500156310ustar 00000000000000use wasm_bindgen::prelude::*; /// A newtype that represents Serde errors as JavaScript exceptions. #[derive(Debug)] pub struct Error(JsValue); impl std::fmt::Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { #[wasm_bindgen] extern "C" { #[wasm_bindgen(js_name = String)] pub fn to_string(value: &JsValue) -> String; } to_string(&self.0).fmt(f) } } impl std::error::Error for Error {} impl Error { /// Creates a JavaScript `Error` with a given message. pub fn new(msg: T) -> Self { Error(JsError::new(&msg.to_string()).into()) } } impl serde::ser::Error for Error { fn custom(msg: T) -> Self { Error::new(msg) } } impl serde::de::Error for Error { fn custom(msg: T) -> Self { Error::new(msg) } } /// This conversion is needed for `?` to just work when using wasm-bindgen /// imports that return JavaScript exceptions as `Result`. impl From for Error { fn from(error: JsValue) -> Error { Error(error) } } // This conversion is needed for `?` to just work in wasm-bindgen exports // that return `Result` to throw JavaScript exceptions. impl From for JsValue { fn from(error: Error) -> JsValue { error.0 } } serde-wasm-bindgen-0.6.5/src/lib.rs000064400000000000000000000150170072674642500152460ustar 00000000000000#![doc = include_str!("../README.md")] #![warn(missing_docs)] #![warn(clippy::missing_const_for_fn)] use js_sys::JsString; use wasm_bindgen::prelude::*; mod de; mod error; mod ser; pub use de::Deserializer; pub use error::Error; pub use ser::Serializer; type Result = std::result::Result; fn static_str_to_js(s: &'static str) -> JsString { use std::cell::RefCell; use std::collections::HashMap; #[derive(Default)] struct PtrHasher { addr: usize, } impl std::hash::Hasher for PtrHasher { fn write(&mut self, _bytes: &[u8]) { unreachable!(); } fn write_usize(&mut self, addr_or_len: usize) { if self.addr == 0 { self.addr = addr_or_len; } } fn finish(&self) -> u64 { self.addr as _ } } type PtrBuildHasher = std::hash::BuildHasherDefault; thread_local! { // Since we're mainly optimising for converting the exact same string literal over and over again, // which will always have the same pointer, we can speed things up by indexing by the string's pointer // instead of its value. static CACHE: RefCell> = Default::default(); } CACHE.with(|cache| { cache .borrow_mut() .entry(s) .or_insert_with(|| s.into()) .clone() }) } /// Custom bindings to avoid using fallible `Reflect` for plain objects. #[wasm_bindgen] extern "C" { type ObjectExt; #[wasm_bindgen(method, indexing_getter)] fn get_with_ref_key(this: &ObjectExt, key: &JsString) -> JsValue; #[wasm_bindgen(method, indexing_setter)] fn set(this: &ObjectExt, key: JsString, value: JsValue); } /// Converts [`JsValue`] into a Rust type. pub fn from_value(value: JsValue) -> Result { T::deserialize(Deserializer::from(value)) } /// Converts a Rust value into a [`JsValue`]. pub fn to_value(value: &T) -> Result { value.serialize(&Serializer::new()) } /// Serialization and deserialization functions that pass JavaScript objects through unchanged. /// /// This module is compatible with the `serde(with)` annotation, so for example if you create /// the struct /// /// ```rust /// #[derive(serde::Serialize)] /// struct MyStruct { /// int_field: i32, /// #[serde(with = "serde_wasm_bindgen::preserve")] /// js_field: js_sys::Int8Array, /// } /// /// let s = MyStruct { /// int_field: 5, /// js_field: js_sys::Int8Array::new_with_length(1000), /// }; /// ``` /// /// then `serde_wasm_bindgen::to_value(&s)` /// will return a JsValue representing an object with two fields (`int_field` and `js_field`), where /// `js_field` will be an `Int8Array` pointing to the same underlying JavaScript object as `s.js_field` does. pub mod preserve { use serde::{de::Error, Deserialize, Serialize}; use wasm_bindgen::{ convert::{FromWasmAbi, IntoWasmAbi}, JsCast, JsValue, }; // Some arbitrary string that no one will collide with unless they try. pub(crate) const PRESERVED_VALUE_MAGIC: &str = "1fc430ca-5b7f-4295-92de-33cf2b145d38"; struct Magic; impl<'de> serde::de::Deserialize<'de> for Magic { fn deserialize>(de: D) -> Result { struct Visitor; impl<'de> serde::de::Visitor<'de> for Visitor { type Value = Magic; fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { formatter.write_str("serde-wasm-bindgen's magic string") } fn visit_str(self, s: &str) -> Result { if s == PRESERVED_VALUE_MAGIC { Ok(Magic) } else { Err(E::invalid_value(serde::de::Unexpected::Str(s), &self)) } } } de.deserialize_str(Visitor) } } #[derive(Serialize)] #[serde(rename = "1fc430ca-5b7f-4295-92de-33cf2b145d38")] struct PreservedValueSerWrapper(u32); // Intentionally asymmetrical wrapper to ensure that only serde-wasm-bindgen preserves roundtrip. #[derive(Deserialize)] #[serde(rename = "1fc430ca-5b7f-4295-92de-33cf2b145d38")] struct PreservedValueDeWrapper(Magic, u32); /// Serialize any `JsCast` value. /// /// When used with the `Serializer` in `serde_wasm_bindgen`, this serializes the value by /// passing it through as a `JsValue`. /// /// This function is compatible with the `serde(serialize_with)` derive annotation. pub fn serialize(val: &T, ser: S) -> Result { // It's responsibility of serde-wasm-bindgen's Serializer to clone the value. // For all other serializers, using reference instead of cloning here will ensure that we don't // create accidental leaks. PreservedValueSerWrapper(val.as_ref().into_abi()).serialize(ser) } /// Deserialize any `JsCast` value. /// /// When used with the `Derializer` in `serde_wasm_bindgen`, this serializes the value by /// passing it through as a `JsValue` and casting it. /// /// This function is compatible with the `serde(deserialize_with)` derive annotation. pub fn deserialize<'de, D: serde::Deserializer<'de>, T: JsCast>(de: D) -> Result { let wrap = PreservedValueDeWrapper::deserialize(de)?; // When used with our deserializer this unsafe is correct, because the // deserializer just converted a JsValue into_abi. // // Other deserializers are unlikely to end up here, thanks // to the asymmetry between PreservedValueSerWrapper and // PreservedValueDeWrapper. Even if some other deserializer ends up // here, this may be incorrect but it shouldn't be UB because JsValues // are represented using indices into a JS-side (i.e. bounds-checked) // array. let val: JsValue = unsafe { FromWasmAbi::from_abi(wrap.1) }; val.dyn_into().map_err(|e| { D::Error::custom(format_args!( "incompatible JS value {e:?} for type {}", std::any::type_name::() )) }) } } serde-wasm-bindgen-0.6.5/src/ser.rs000064400000000000000000000402500072674642500152660ustar 00000000000000use js_sys::{Array, JsString, Map, Number, Object, Uint8Array}; use serde::ser::{self, Error as _, Serialize}; use wasm_bindgen::convert::RefFromWasmAbi; use wasm_bindgen::prelude::*; use wasm_bindgen::JsCast; use crate::preserve::PRESERVED_VALUE_MAGIC; use crate::{static_str_to_js, Error, ObjectExt}; type Result = super::Result; /// Wraps other serializers into an enum tagged variant form. /// /// Results in `{"Variant": ...payload...}` for compatibility with serde-json. pub struct VariantSerializer { variant: &'static str, inner: S, } impl VariantSerializer { const fn new(variant: &'static str, inner: S) -> Self { Self { variant, inner } } fn end(self, inner: impl FnOnce(S) -> Result) -> Result { let value = inner(self.inner)?; let obj = Object::new(); obj.unchecked_ref::() .set(static_str_to_js(self.variant), value); Ok(obj.into()) } } impl> ser::SerializeTupleVariant for VariantSerializer { type Ok = JsValue; type Error = Error; fn serialize_field(&mut self, value: &T) -> Result<()> { self.inner.serialize_field(value) } fn end(self) -> Result { self.end(S::end) } } impl> ser::SerializeStructVariant for VariantSerializer { type Ok = JsValue; type Error = Error; fn serialize_field( &mut self, key: &'static str, value: &T, ) -> Result<()> { self.inner.serialize_field(key, value) } fn end(self) -> Result { self.end(S::end) } } /// Serializes Rust iterables and tuples into JS arrays. pub struct ArraySerializer<'s> { serializer: &'s Serializer, target: Array, idx: u32, } impl<'s> ArraySerializer<'s> { fn new(serializer: &'s Serializer) -> Self { Self { serializer, target: Array::new(), idx: 0, } } } impl ser::SerializeSeq for ArraySerializer<'_> { type Ok = JsValue; type Error = Error; fn serialize_element(&mut self, value: &T) -> Result<()> { self.target.set(self.idx, value.serialize(self.serializer)?); self.idx += 1; Ok(()) } fn end(self) -> Result { Ok(self.target.into()) } } impl ser::SerializeTuple for ArraySerializer<'_> { type Ok = JsValue; type Error = Error; fn serialize_element(&mut self, value: &T) -> Result<()> { ser::SerializeSeq::serialize_element(self, value) } fn end(self) -> Result { ser::SerializeSeq::end(self) } } impl ser::SerializeTupleStruct for ArraySerializer<'_> { type Ok = JsValue; type Error = Error; fn serialize_field(&mut self, value: &T) -> Result<()> { ser::SerializeTuple::serialize_element(self, value) } fn end(self) -> Result { ser::SerializeTuple::end(self) } } pub enum MapResult { Map(Map), Object(Object), } /// Serializes Rust maps into JS `Map` or plain JS objects. /// /// Plain JS objects are used if `serialize_maps_as_objects` is set to `true`, /// but then only string keys are supported. pub struct MapSerializer<'s> { serializer: &'s Serializer, target: MapResult, next_key: Option, } impl<'s> MapSerializer<'s> { pub fn new(serializer: &'s Serializer, as_object: bool) -> Self { Self { serializer, target: if as_object { MapResult::Object(Object::new()) } else { MapResult::Map(Map::new()) }, next_key: None, } } } impl ser::SerializeMap for MapSerializer<'_> { type Ok = JsValue; type Error = Error; fn serialize_key(&mut self, key: &T) -> Result<()> { debug_assert!(self.next_key.is_none()); self.next_key = Some(key.serialize(self.serializer)?); Ok(()) } fn serialize_value(&mut self, value: &T) -> Result<()> { let key = self.next_key.take().unwrap_throw(); let value_ser = value.serialize(self.serializer)?; match &self.target { MapResult::Map(map) => { map.set(&key, &value_ser); } MapResult::Object(object) => { let key = key.dyn_into::().map_err(|_| { Error::custom("Map key is not a string and cannot be an object key") })?; object.unchecked_ref::().set(key, value_ser); } } Ok(()) } fn end(self) -> Result { debug_assert!(self.next_key.is_none()); match self.target { MapResult::Map(map) => Ok(map.into()), MapResult::Object(object) => Ok(object.into()), } } } /// Serializes Rust structs into plain JS objects. pub struct ObjectSerializer<'s> { serializer: &'s Serializer, target: ObjectExt, } impl<'s> ObjectSerializer<'s> { pub fn new(serializer: &'s Serializer) -> Self { Self { serializer, target: Object::new().unchecked_into::(), } } } impl ser::SerializeStruct for ObjectSerializer<'_> { type Ok = JsValue; type Error = Error; fn serialize_field( &mut self, key: &'static str, value: &T, ) -> Result<()> { let value = value.serialize(self.serializer)?; self.target.set(static_str_to_js(key), value); Ok(()) } fn end(self) -> Result { Ok(self.target.into()) } } /// A [`serde::Serializer`] that converts supported Rust values into a [`JsValue`]. #[derive(Default)] pub struct Serializer { serialize_missing_as_null: bool, serialize_maps_as_objects: bool, serialize_large_number_types_as_bigints: bool, serialize_bytes_as_arrays: bool, } impl Serializer { /// Creates a new default [`Serializer`]. pub const fn new() -> Self { Self { serialize_missing_as_null: false, serialize_maps_as_objects: false, serialize_large_number_types_as_bigints: false, serialize_bytes_as_arrays: false, } } /// Creates a JSON compatible serializer. This uses null instead of undefined, and /// uses plain objects instead of ES maps. So you will get the same result of /// `JsValue::from_serde`, and you can stringify results to JSON and store /// it without data loss. pub const fn json_compatible() -> Self { Self { serialize_missing_as_null: true, serialize_maps_as_objects: true, serialize_large_number_types_as_bigints: false, serialize_bytes_as_arrays: true, } } /// Set to `true` to serialize `()`, unit structs and `Option::None` to `null` /// instead of `undefined` in JS. `false` by default. pub const fn serialize_missing_as_null(mut self, value: bool) -> Self { self.serialize_missing_as_null = value; self } /// Set to `true` to serialize maps into plain JavaScript objects instead of /// ES2015 `Map`s. `false` by default. pub const fn serialize_maps_as_objects(mut self, value: bool) -> Self { self.serialize_maps_as_objects = value; self } /// Set to `true` to serialize 64-bit numbers to JavaScript `BigInt` instead of /// plain numbers. `false` by default. pub const fn serialize_large_number_types_as_bigints(mut self, value: bool) -> Self { self.serialize_large_number_types_as_bigints = value; self } /// Set to `true` to serialize bytes into plain JavaScript arrays instead of /// ES2015 `Uint8Array`s. `false` by default. pub const fn serialize_bytes_as_arrays(mut self, value: bool) -> Self { self.serialize_bytes_as_arrays = value; self } } macro_rules! forward_to_into { ($($name:ident($ty:ty);)*) => { $(fn $name(self, v: $ty) -> Result { Ok(v.into()) })* }; } impl<'s> ser::Serializer for &'s Serializer { type Ok = JsValue; type Error = Error; type SerializeSeq = ArraySerializer<'s>; type SerializeTuple = ArraySerializer<'s>; type SerializeTupleStruct = ArraySerializer<'s>; type SerializeTupleVariant = VariantSerializer>; type SerializeMap = MapSerializer<'s>; type SerializeStruct = ObjectSerializer<'s>; type SerializeStructVariant = VariantSerializer>; forward_to_into! { serialize_bool(bool); serialize_i8(i8); serialize_i16(i16); serialize_i32(i32); serialize_u8(u8); serialize_u16(u16); serialize_u32(u32); serialize_f32(f32); serialize_f64(f64); serialize_str(&str); } /// Serializes `i64` into a `BigInt` or a JS number. /// /// If `serialize_large_number_types_as_bigints` is set to `false`, /// `i64` is serialized as a JS number. But in this mode only numbers /// within the safe integer range are supported. fn serialize_i64(self, v: i64) -> Result { if self.serialize_large_number_types_as_bigints { return Ok(v.into()); } // Note: don't try to "simplify" by using `.abs()` as it can overflow, // but range check can't. const MIN_SAFE_INTEGER: i64 = Number::MIN_SAFE_INTEGER as i64; const MAX_SAFE_INTEGER: i64 = Number::MAX_SAFE_INTEGER as i64; if (MIN_SAFE_INTEGER..=MAX_SAFE_INTEGER).contains(&v) { self.serialize_f64(v as _) } else { Err(Error::custom(format_args!( "{} can't be represented as a JavaScript number", v ))) } } /// Serializes `u64` into a `BigInt` or a JS number. /// /// If `serialize_large_number_types_as_bigints` is set to `false`, /// `u64` is serialized as a JS number. But in this mode only numbers /// within the safe integer range are supported. fn serialize_u64(self, v: u64) -> Result { if self.serialize_large_number_types_as_bigints { return Ok(v.into()); } if v <= Number::MAX_SAFE_INTEGER as u64 { self.serialize_f64(v as _) } else { Err(Error::custom(format_args!( "{} can't be represented as a JavaScript number", v ))) } } /// Serializes `i128` into a `BigInt`. fn serialize_i128(self, v: i128) -> Result { Ok(JsValue::from(v)) } /// Serializes `u128` into a `BigInt`. fn serialize_u128(self, v: u128) -> Result { Ok(JsValue::from(v)) } /// Serializes `char` into a JS string. fn serialize_char(self, v: char) -> Result { Ok(JsString::from(v).into()) } /// Serializes `bytes` into a JS `Uint8Array` or a plain JS array. /// /// If `serialize_bytes_as_arrays` is set to `true`, bytes are serialized as plain JS arrays. fn serialize_bytes(self, v: &[u8]) -> Result { // Create a `Uint8Array` view into a Rust slice, and immediately copy it to the JS memory. // // This is necessary because any allocation in WebAssembly can require reallocation of the // backing memory, which will invalidate existing views (including `Uint8Array`). let view = unsafe { Uint8Array::view(v) }; if self.serialize_bytes_as_arrays { Ok(JsValue::from(Array::from(view.as_ref()))) } else { Ok(JsValue::from(Uint8Array::new(view.as_ref()))) } } /// Serializes `None` into `undefined` or `null`. /// /// If `serialize_missing_as_null` is set to `true`, `None` is serialized as `null`. fn serialize_none(self) -> Result { self.serialize_unit() } /// Serializes `Some(T)` as `T`. fn serialize_some(self, value: &T) -> Result { value.serialize(self) } /// Serializes `()` into `undefined` or `null`. /// /// If `serialize_missing_as_null` is set to `true`, `()` is serialized as `null`. fn serialize_unit(self) -> Result { Ok(if self.serialize_missing_as_null { JsValue::NULL } else { JsValue::UNDEFINED }) } /// Serializes unit structs into `undefined` or `null`. fn serialize_unit_struct(self, _name: &'static str) -> Result { self.serialize_unit() } /// For compatibility with serde-json, serializes unit variants as "Variant" strings. fn serialize_unit_variant( self, _name: &'static str, _variant_index: u32, variant: &'static str, ) -> Result { Ok(static_str_to_js(variant).into()) } /// Serializes newtype structs as their inner values. fn serialize_newtype_struct( self, name: &'static str, value: &T, ) -> Result { if name == PRESERVED_VALUE_MAGIC { let abi = value.serialize(self)?.unchecked_into_f64() as u32; // `PreservedValueSerWrapper` gives us ABI of a reference to a `JsValue` that is // guaranteed to be alive only during this call. // We must clone it before giving away the value to the caller. return Ok(unsafe { JsValue::ref_from_abi(abi) }.as_ref().clone()); } value.serialize(self) } /// Serializes newtype variants as their inner values. fn serialize_newtype_variant( self, _name: &'static str, _variant_index: u32, variant: &'static str, value: &T, ) -> Result { VariantSerializer::new(variant, self.serialize_newtype_struct(variant, value)?).end(Ok) } /// Serializes any Rust iterable as a JS Array. // TODO: Figure out if there is a way to detect and serialize `Set` differently. fn serialize_seq(self, _len: Option) -> Result { Ok(ArraySerializer::new(self)) } /// Serializes Rust tuples as JS arrays. fn serialize_tuple(self, len: usize) -> Result { self.serialize_seq(Some(len)) } /// Serializes Rust tuple structs as JS arrays. fn serialize_tuple_struct( self, _name: &'static str, len: usize, ) -> Result { self.serialize_tuple(len) } /// Serializes Rust tuple variants as `{"Variant": [ ...tuple... ]}`. fn serialize_tuple_variant( self, _name: &'static str, _variant_index: u32, variant: &'static str, len: usize, ) -> Result { Ok(VariantSerializer::new( variant, self.serialize_tuple_struct(variant, len)?, )) } /// Serializes Rust maps into JS `Map` or plain JS objects. /// /// See [`MapSerializer`] for more details. fn serialize_map(self, _len: Option) -> Result { Ok(MapSerializer::new(self, self.serialize_maps_as_objects)) } /// Serializes Rust typed structs into plain JS objects. fn serialize_struct(self, _name: &'static str, _len: usize) -> Result { Ok(ObjectSerializer::new(self)) } /// Serializes Rust struct-like variants into `{"Variant": { ...fields... }}`. fn serialize_struct_variant( self, _name: &'static str, _variant_index: u32, variant: &'static str, len: usize, ) -> Result { Ok(VariantSerializer::new( variant, self.serialize_struct(variant, len)?, )) } } serde-wasm-bindgen-0.6.5/tests/browser.rs000064400000000000000000000001210072674642500165240ustar 00000000000000wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); mod common; serde-wasm-bindgen-0.6.5/tests/common/mod.rs000064400000000000000000000652310072674642500171250ustar 00000000000000use js_sys::{BigInt, JsString, Number, Object}; use maplit::{btreemap, hashmap, hashset}; use proptest::prelude::*; use serde::de::DeserializeOwned; use serde::ser::Error as SerError; use serde::{Deserialize, Serialize}; use serde_wasm_bindgen::{from_value, to_value, Error, Serializer}; use std::collections::{BTreeMap, HashMap}; use std::fmt::Debug; use std::hash::Hash; use wasm_bindgen::{JsCast, JsValue}; use wasm_bindgen_test::wasm_bindgen_test; const SERIALIZER: Serializer = Serializer::new(); const JSON_SERIALIZER: Serializer = Serializer::json_compatible(); const BIGINT_SERIALIZER: Serializer = Serializer::new().serialize_large_number_types_as_bigints(true); const MAP_OBJECT_SERIALIZER: Serializer = Serializer::new().serialize_maps_as_objects(true); fn test_via_round_trip_with_config(value: T, serializer: &Serializer) -> JsValue where T: Serialize + DeserializeOwned + PartialEq + Debug, { let serialized = value.serialize(serializer).unwrap(); let round_trip = from_value(serialized.clone()).unwrap(); assert_eq!( value, round_trip, "{:?} == from_value({:?})", value, serialized ); serialized } fn test_via_into_with_config(lhs: L, rhs: R, serializer: &Serializer) where L: Serialize + DeserializeOwned + PartialEq + Clone + Debug, R: Into + Clone + Debug, { let serialized = test_via_round_trip_with_config(lhs.clone(), serializer); assert_eq!( serialized, rhs.clone().into(), "to_value({:?}) == JsValue::from({:?})", lhs, rhs ); } fn test_via_into(lhs: L, rhs: R) where L: Serialize + DeserializeOwned + PartialEq + Clone + Debug, R: Into + Clone + Debug, { test_via_into_with_config(lhs, rhs, &SERIALIZER) } fn test_primitive_with_config(value: T, serializer: &Serializer) where T: Copy + Serialize + Into + DeserializeOwned + PartialEq + Debug, { test_via_into_with_config(value, value, serializer) } fn test_primitive(value: T) where T: Copy + Serialize + Into + DeserializeOwned + PartialEq + Debug, { test_via_into(value, value) } #[derive(PartialEq)] enum ValueKind { Null, Undefined, Boolean, PosFloat, NegFloat, NaN, PosInfinity, NegInfinity, PosInt, NegInt, PosBigInt, NegBigInt, String, Object, } fn sample_js_values() -> Vec<(ValueKind, JsValue)> { vec![ (ValueKind::Null, JsValue::NULL), (ValueKind::Undefined, JsValue::UNDEFINED), (ValueKind::Boolean, JsValue::TRUE), (ValueKind::PosFloat, JsValue::from(0.5)), (ValueKind::NegFloat, JsValue::from(-0.5)), (ValueKind::NaN, JsValue::from(std::f64::NAN)), (ValueKind::PosInfinity, JsValue::from(std::f64::INFINITY)), (ValueKind::NegInfinity, JsValue::from(-std::f64::INFINITY)), (ValueKind::PosInt, JsValue::from(1)), (ValueKind::NegInt, JsValue::from(-1)), (ValueKind::PosBigInt, JsValue::from(BigInt::from(1_i64))), (ValueKind::NegBigInt, JsValue::from(BigInt::from(-1_i64))), (ValueKind::String, JsValue::from("1")), (ValueKind::Object, Object::new().into()), ] } macro_rules! test_value_compatibility { ($ty:ty { $(ValueKind::$kind:ident $delim:tt $rust_value:expr,)* }) => { for (kind, js_value) in sample_js_values() { match kind { $(ValueKind::$kind => { let rust_value: $ty = $rust_value; test_value_compatibility!(@compare $ty, $kind $delim rust_value, js_value); })* _ => { from_value::<$ty>(js_value).unwrap_err(); } } } }; (@compare $ty:ty, NaN => $rust_value:ident, $js_value:ident) => {{ let mut rust_value: $ty = $rust_value; assert!(rust_value.is_nan(), "{:?} is not NaN", rust_value); let js_value = to_value(&rust_value).unwrap(); assert!(Number::is_nan(&js_value), "{:?} is not NaN", js_value); rust_value = from_value(js_value).unwrap(); assert!(rust_value.is_nan(), "{:?} is not NaN", rust_value); }}; (@compare $ty:ty, $kind:ident -> $rust_value:ident, $js_value:ident) => {{ assert_ne!(to_value(&$rust_value).ok().as_ref(), Some(&$js_value), "to_value({:?}) != Ok({:?})", $rust_value, $js_value); let rust_value: $ty = from_value($js_value.clone()).unwrap(); assert_eq!(rust_value, $rust_value, "from_value from {:?}", $js_value); }}; (@compare $ty:ty, $kind:ident => $rust_value:ident, $js_value:ident) => ( test_via_into::<$ty, JsValue>($rust_value, $js_value) ); } #[wasm_bindgen_test] fn unit() { test_via_into((), JsValue::UNDEFINED); test_value_compatibility!(() { ValueKind::Undefined => (), // Special case: one-way only conversion. ValueKind::Null -> (), }); } mod proptests { use super::*; proptest! { #[wasm_bindgen_test] fn bool(value: bool) { test_primitive(value); } #[wasm_bindgen_test] fn i8(value: i8) { test_primitive(value); } #[wasm_bindgen_test] fn i16(value: i16) { test_primitive(value); } #[wasm_bindgen_test] fn i32(value: i32) { test_primitive(value); } #[wasm_bindgen_test] fn u8(value: u8) { test_primitive(value); } #[wasm_bindgen_test] fn u16(value: u16) { test_primitive(value); } #[wasm_bindgen_test] fn u32(value: u32) { test_primitive(value); } #[wasm_bindgen_test] fn f32(value: f32) { test_primitive(value); } #[wasm_bindgen_test] fn f64(value: f64) { test_primitive(value); } #[wasm_bindgen_test] fn i64_bigints(value: i64) { test_primitive_with_config(value, &BIGINT_SERIALIZER); } #[wasm_bindgen_test] fn u64_bigints(value: i64) { test_primitive_with_config(value, &BIGINT_SERIALIZER); } #[wasm_bindgen_test] fn i64_numbers(value in Number::MIN_SAFE_INTEGER as i64..=Number::MAX_SAFE_INTEGER as i64) { test_via_into(value, value as f64); } #[wasm_bindgen_test] fn u64_numbers(value in 0..=Number::MAX_SAFE_INTEGER as u64) { test_via_into(value, value as f64); } #[wasm_bindgen_test] fn isize(value: isize) { test_via_into(value, value as f64); test_via_into_with_config(value, value as i64, &BIGINT_SERIALIZER); } #[wasm_bindgen_test] fn usize(value: usize) { test_via_into(value, value as f64); test_via_into_with_config(value, value as u64, &BIGINT_SERIALIZER); } #[wasm_bindgen_test] fn i128(value: i128) { test_primitive(value); } #[wasm_bindgen_test] fn u128(value: u128) { test_primitive(value); } #[wasm_bindgen_test] fn char(c: char) { test_via_into(c, String::from(c)); } #[wasm_bindgen_test] fn string(s: String) { test_via_into(s.clone(), s); } } } mod compat { use super::*; macro_rules! test_int_boundaries { ($ty:ident) => { test_primitive::<$ty>($ty::MIN); test_primitive::<$ty>($ty::MAX); let too_small = f64::from($ty::MIN) - 1.0; from_value::<$ty>(too_small.into()).unwrap_err(); let too_large = f64::from($ty::MAX) + 1.0; from_value::<$ty>(too_large.into()).unwrap_err(); }; } macro_rules! test_bigint_boundaries { ($ty:ident $(as $as:ident)?) => { test_via_into_with_config($ty::MIN, $ty::MIN $(as $as)?, &BIGINT_SERIALIZER); test_via_into_with_config($ty::MAX, $ty::MAX $(as $as)?, &BIGINT_SERIALIZER); let too_small = BigInt::from($ty::MIN $(as $as)?) - BigInt::from(1); from_value::<$ty>(too_small.into()).unwrap_err(); let too_large = BigInt::from($ty::MAX $(as $as)?) + BigInt::from(1); from_value::<$ty>(too_large.into()).unwrap_err(); }; } macro_rules! test_safe_int_boundaries { (signed $ty:ident) => { test_via_into(Number::MIN_SAFE_INTEGER as $ty, Number::MIN_SAFE_INTEGER); from_value::<$ty>(JsValue::from(Number::MIN_SAFE_INTEGER - 1.)).unwrap_err(); test_primitive_with_config((Number::MIN_SAFE_INTEGER - 1.) as $ty, &BIGINT_SERIALIZER); test_safe_int_boundaries!(unsigned $ty); }; (unsigned $ty:ident) => { test_via_into(Number::MAX_SAFE_INTEGER as $ty, Number::MAX_SAFE_INTEGER); from_value::<$ty>(JsValue::from(Number::MAX_SAFE_INTEGER + 1.)).unwrap_err(); test_primitive_with_config((Number::MAX_SAFE_INTEGER + 1.) as $ty, &BIGINT_SERIALIZER); }; } #[wasm_bindgen_test] fn bool() { test_value_compatibility!(bool { ValueKind::Boolean => true, }); } #[wasm_bindgen_test] fn i8() { test_int_boundaries!(i8); test_value_compatibility!(i8 { ValueKind::PosInt => 1, ValueKind::NegInt => -1, }); } #[wasm_bindgen_test] fn i16() { test_int_boundaries!(i16); test_value_compatibility!(i16 { ValueKind::PosInt => 1, ValueKind::NegInt => -1, }); } #[wasm_bindgen_test] fn i32() { test_int_boundaries!(i32); test_value_compatibility!(i32 { ValueKind::PosInt => 1, ValueKind::NegInt => -1, }); } #[wasm_bindgen_test] fn u8() { test_int_boundaries!(u8); test_value_compatibility!(u8 { ValueKind::PosInt => 1, }); } #[wasm_bindgen_test] fn u16() { test_int_boundaries!(u16); test_value_compatibility!(u16 { ValueKind::PosInt => 1, }); } #[wasm_bindgen_test] fn u32() { test_int_boundaries!(u32); test_value_compatibility!(u32 { ValueKind::PosInt => 1, }); } #[wasm_bindgen_test] fn i64() { test_bigint_boundaries!(i64); test_safe_int_boundaries!(signed i64); test_value_compatibility!(i64 { ValueKind::PosInt => 1, ValueKind::NegInt => -1, ValueKind::PosBigInt -> 1, ValueKind::NegBigInt -> -1, }); } #[wasm_bindgen_test] fn u64() { test_bigint_boundaries!(u64); test_safe_int_boundaries!(unsigned u64); test_value_compatibility!(u64 { ValueKind::PosInt => 1, ValueKind::PosBigInt -> 1, }); } #[wasm_bindgen_test] fn i128() { test_bigint_boundaries!(i128); test_value_compatibility!(i128 { ValueKind::PosBigInt => 1, ValueKind::NegBigInt => -1, }); } #[wasm_bindgen_test] fn u128() { test_bigint_boundaries!(u128); test_value_compatibility!(u128 { ValueKind::PosBigInt => 1, }); } #[wasm_bindgen_test] fn isize() { test_bigint_boundaries!(isize as i64); test_value_compatibility!(isize { ValueKind::PosInt => 1, ValueKind::NegInt => -1, ValueKind::PosBigInt -> 1, ValueKind::NegBigInt -> -1, }); } #[wasm_bindgen_test] fn usize() { test_bigint_boundaries!(usize as u64); test_value_compatibility!(usize { ValueKind::PosInt => 1, ValueKind::PosBigInt -> 1, }); } #[wasm_bindgen_test] fn f32() { test_value_compatibility!(f32 { ValueKind::PosFloat => 0.5, ValueKind::NegFloat => -0.5, ValueKind::NaN => f32::NAN, ValueKind::PosInfinity => f32::INFINITY, ValueKind::NegInfinity => f32::NEG_INFINITY, ValueKind::PosInt => 1.0, ValueKind::NegInt => -1.0, }); } #[wasm_bindgen_test] fn f64() { test_value_compatibility!(f64 { ValueKind::PosFloat => 0.5, ValueKind::NegFloat => -0.5, ValueKind::NaN => f64::NAN, ValueKind::PosInfinity => f64::INFINITY, ValueKind::NegInfinity => f64::NEG_INFINITY, ValueKind::PosInt => 1.0, ValueKind::NegInt => -1.0, }); } #[wasm_bindgen_test] fn string() { let lone_surrogate = JsString::from_char_code(&[0xDC00]); // Lone surrogates currently become a replacement character. assert_eq!( from_value::(lone_surrogate.into()).unwrap(), "\u{FFFD}" ); } } #[wasm_bindgen_test] fn bytes() { // Create a backing storage. let mut src = [1, 2, 3]; // Store the original separately for the mutation test let orig_src = src; // Convert to a JS value let value = to_value(&serde_bytes::Bytes::new(&src)).unwrap(); // Modify the original storage to make sure that JS value is a copy. src[0] = 10; // Make sure the JS value is a Uint8Array let res = value.dyn_ref::().unwrap(); // Copy it into another Rust storage let mut dst = [0; 3]; res.copy_to(&mut dst); // Finally, compare that resulting storage with the original. assert_eq!(orig_src, dst); // Now, try to deserialize back. let deserialized: serde_bytes::ByteBuf = from_value(value).unwrap(); assert_eq!(deserialized.as_ref(), orig_src); } #[wasm_bindgen_test] fn bytes_as_array() { let src = [1, 2, 3]; // Convert to a JS value let serializer = Serializer::new().serialize_bytes_as_arrays(true); let bytes = &serde_bytes::Bytes::new(&src); let value = bytes.serialize(&serializer).unwrap(); // Make sure the JS value is an Array. value.dyn_ref::().unwrap(); // Now, try to deserialize back. let deserialized: serde_bytes::ByteBuf = from_value(value).unwrap(); assert_eq!(deserialized.as_ref(), src); } #[wasm_bindgen_test] fn bytes_from_mixed_array() { // The string "xyz" cannot convert to a u8 let value = (100, "xyz".to_string(), true) .serialize(&SERIALIZER) .unwrap(); from_value::(value).unwrap_err(); // The number 256 cannot convert to a u8 let value = (100, 256, 100).serialize(&SERIALIZER).unwrap(); from_value::(value).unwrap_err(); } #[wasm_bindgen_test] fn options() { test_via_into(Some(0_u32), 0_u32); test_via_into(Some(32_u32), 32_u32); test_via_into(None::, JsValue::UNDEFINED); test_via_into(Some("".to_string()), ""); test_via_into(Some("abc".to_string()), "abc"); test_via_into(None::, JsValue::UNDEFINED); // This one is an unfortunate edge case that won't roundtrip, // but it's pretty unlikely in real-world code. assert_eq!(to_value(&Some(())).unwrap(), JsValue::UNDEFINED); assert_eq!(to_value(&None::<()>).unwrap(), JsValue::UNDEFINED); assert_eq!(to_value(&Some(Some(()))).unwrap(), JsValue::UNDEFINED); assert_eq!(to_value(&Some(None::<()>)).unwrap(), JsValue::UNDEFINED); } fn assert_json(lhs_value: JsValue, rhs: R) where R: Serialize + DeserializeOwned + PartialEq + Debug, { if lhs_value.is_undefined() { assert_eq!("null", serde_json::to_string(&rhs).unwrap()) } else { assert_eq!( js_sys::JSON::stringify(&lhs_value).unwrap(), serde_json::to_string(&rhs).unwrap(), ); } let restored_lhs: R = from_value(lhs_value.clone()).unwrap(); assert_eq!(restored_lhs, rhs, "from_value from {:?}", lhs_value); } fn test_via_json_with_config(value: T, serializer: &Serializer) where T: Serialize + DeserializeOwned + PartialEq + Debug, { assert_json(value.serialize(serializer).unwrap(), value); } fn test_via_json(value: T) where T: Serialize + DeserializeOwned + PartialEq + Debug, { test_via_json_with_config(value, &SERIALIZER); } #[wasm_bindgen_test] fn enums() { macro_rules! test_enum { ($(# $attr:tt)* $name:ident) => {{ #[derive(Debug, PartialEq, Serialize, Deserialize)] $(# $attr)* enum $name where A: Debug + Ord + Eq { Unit, Newtype(A), Tuple(A, B), Struct { a: A, b: B }, Map(BTreeMap), Seq { seq: Vec } // internal tags cannot be directly embedded in arrays } test_via_json($name::Unit::); test_via_json($name::Newtype::<_, i32>("newtype content".to_string())); test_via_json($name::Tuple("tuple content".to_string(), 42)); test_via_json($name::Struct { a: "struct content".to_string(), b: 42, }); test_via_json_with_config($name::Map::( btreemap!{ "a".to_string() => 12, "abc".to_string() => -1161, "b".to_string() => 64, } ), &MAP_OBJECT_SERIALIZER); test_via_json($name::Seq:: { seq: vec![5, 63, 0, -62, 6] }); }}; } test_enum! { ExternallyTagged } #[derive(Debug, PartialEq, Serialize, Deserialize, Clone)] #[serde(tag = "tag")] enum InternallyTagged where A: Ord, { Unit, Struct { a: A, b: B, }, Sequence { seq: Vec, }, Map(BTreeMap), Bytes { #[serde(with = "serde_bytes")] serde_bytes: Vec, raw: Vec, }, } test_via_json(InternallyTagged::Unit::<(), ()>); test_via_json(InternallyTagged::Struct { a: "struct content".to_string(), b: 42, }); test_via_json(InternallyTagged::Struct { a: "struct content".to_string(), b: 42.2, }); test_via_json(InternallyTagged::::Sequence { seq: vec![12, 41, -11, -65, 961], }); // Internal tags with maps are not properly deserialized from Map values due to the exclusion // of Iterables during deserialize_any(). They can be deserialized properly from plain objects // so we can test that. test_via_json_with_config( InternallyTagged::Map(btreemap! { "a".to_string() => 12, "abc".to_string() => -1161, "b".to_string() => 64, }), &MAP_OBJECT_SERIALIZER, ); test_via_round_trip_with_config( InternallyTagged::Struct { a: 10_u64, b: -10_i64, }, &BIGINT_SERIALIZER, ); test_via_round_trip_with_config( InternallyTagged::<(), ()>::Bytes { serde_bytes: vec![0, 1, 2], raw: vec![3, 4, 5], }, &SERIALIZER, ); test_enum! { #[serde(tag = "tag", content = "content")] AdjacentlyTagged } test_enum! { #[serde(untagged)] Untagged } } #[wasm_bindgen_test] fn serde_json_value_with_json() { test_via_round_trip_with_config( serde_json::from_str::("[0, \"foo\"]").unwrap(), &JSON_SERIALIZER, ); test_via_round_trip_with_config( serde_json::from_str::(r#"{"foo": "bar"}"#).unwrap(), &JSON_SERIALIZER, ); } #[wasm_bindgen_test] fn serde_json_value_with_default() { test_via_round_trip_with_config( serde_json::from_str::("[0, \"foo\"]").unwrap(), &SERIALIZER, ); test_via_round_trip_with_config( serde_json::from_str::(r#"{"foo": "bar"}"#).unwrap(), &SERIALIZER, ); } #[wasm_bindgen_test] fn preserved_value() { #[derive(serde::Deserialize, serde::Serialize, PartialEq, Clone, Debug)] #[serde(bound = "T: JsCast")] struct PreservedValue(#[serde(with = "serde_wasm_bindgen::preserve")] T); test_via_into(PreservedValue(JsValue::from_f64(42.0)), 42); test_via_into(PreservedValue(JsValue::from_str("hello")), "hello"); let res: PreservedValue = from_value(JsValue::from_f64(42.0)).unwrap(); assert_eq!(res.0.as_f64(), Some(42.0)); // Check that object identity is preserved. let big_array = js_sys::Int8Array::new_with_length(64); let val = PreservedValue(big_array); let res = to_value(&val).unwrap(); assert_eq!(res, JsValue::from(val.0)); // The JsCasts are checked on deserialization. let bool = js_sys::Boolean::from(true); let serialized = to_value(&PreservedValue(bool)).unwrap(); let res: Result, _> = from_value(serialized); assert_eq!( res.unwrap_err().to_string(), Error::custom("incompatible JS value JsValue(true) for type js_sys::Number").to_string() ); // serde_json must fail to round-trip our special wrapper let s = serde_json::to_string(&PreservedValue(JsValue::from_f64(42.0))).unwrap(); serde_json::from_str::>(&s).unwrap_err(); // bincode must fail to round-trip our special wrapper let s = bincode::serialize(&PreservedValue(JsValue::from_f64(42.0))).unwrap(); bincode::deserialize::>(&s).unwrap_err(); } #[wasm_bindgen_test] fn structs() { #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] struct Unit; test_via_into(Unit, JsValue::UNDEFINED); #[derive(Debug, PartialEq, Serialize, Deserialize)] struct Newtype(A); test_via_json(Newtype("newtype content".to_string())); #[derive(Debug, PartialEq, Serialize, Deserialize)] struct Tuple(A, B); test_via_json(Tuple("tuple content".to_string(), 42)); #[derive(Debug, PartialEq, Serialize, Deserialize)] struct Struct { a: A, b: B, } test_via_json(Struct { a: "struct content".to_string(), b: 42, }); } #[wasm_bindgen_test] fn sequences() { test_via_json([1, 2]); test_via_json(["".to_string(), "x".to_string(), "xyz".to_string()]); test_via_json((100, "xyz".to_string(), true)); // Sets are currently indistinguishable from other sequences for // Serde serializers, so this will become an array on the JS side. test_via_json(hashset! {false, true}); } #[wasm_bindgen_test] fn maps() { #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] struct Struct { a: A, b: B, } // Create a Rust HashMap with non-string keys to make sure // that we support real arbitrary maps. let src = hashmap! { Struct { a: 1, b: "smth".to_string(), } => Struct { a: 2, b: "SMTH".to_string(), }, Struct { a: 42, b: "something".to_string(), } => Struct { a: 84, b: "SOMETHING".to_string(), }, }; // Convert to a JS value let res = to_value(&src).unwrap(); // Make sure that the result is an ES6 Map. let res = res.dyn_into::().unwrap(); assert_eq!(res.size() as usize, src.len()); // Compare values one by one (it's ok to use JSON for invidivual structs). res.entries() .into_iter() .map(|kv| kv.unwrap()) .zip(src) .for_each(|(lhs_kv, rhs_kv)| { assert_json(lhs_kv, rhs_kv); }); } #[wasm_bindgen_test] fn maps_objects_string_key() { #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] struct Struct { a: A, b: B, } let src = hashmap! { "a".to_string() => Struct { a: 2, b: "S".to_string(), }, "b".to_string() => Struct { a: 3, b: "T".to_string(), }, }; test_via_json_with_config(src, &MAP_OBJECT_SERIALIZER); } #[wasm_bindgen_test] fn serialize_json_compatible() { #[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] struct Struct { a: HashMap, b: Option, } let x = Struct { a: hashmap! { "foo".to_string() => (), "bar".to_string() => (), }, b: None, }; test_via_json_with_config(x, &Serializer::json_compatible()); } #[wasm_bindgen_test] fn maps_objects_object_key() { #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] struct Struct { a: A, b: B, } let src = hashmap! { Struct { a: 1, b: "smth".to_string(), } => Struct { a: 2, b: "SMTH".to_string(), }, Struct { a: 42, b: "something".to_string(), } => Struct { a: 84, b: "SOMETHING".to_string(), }, }; let res = src.serialize(&MAP_OBJECT_SERIALIZER).unwrap_err(); assert_eq!( res.to_string(), Error::custom("Map key is not a string and cannot be an object key").to_string() ); } #[wasm_bindgen_test] fn serde_default_fields() { #[derive(Deserialize)] #[allow(dead_code)] struct Struct { data: String, #[serde(default)] missing: bool, opt_field: Option, unit_field: (), } let json = r#"{"data": "testing", "unit_field": null}"#; let obj = js_sys::JSON::parse(json).unwrap(); // Check that it parses successfully despite the missing field. let _struct: Struct = from_value(obj).unwrap(); } #[wasm_bindgen_test] fn field_aliases() { #[derive(Debug, Serialize, Deserialize, PartialEq)] struct Struct { #[serde(alias = "b")] a: i32, c: i32, } test_via_round_trip_with_config(Struct { a: 42, c: 84 }, &SERIALIZER); } serde-wasm-bindgen-0.6.5/tests/node.rs000064400000000000000000000000150072674642500157700ustar 00000000000000mod common;