Skip to main content

rquickjs_serde/
lib.rs

1#![cfg_attr(not(test), no_std)]
2
3extern crate alloc;
4
5use rquickjs::{Ctx, Value};
6use serde::Serialize;
7use serde::de::DeserializeOwned;
8
9#[doc(inline)]
10pub use crate::de::Deserializer;
11#[doc(inline)]
12pub use crate::err::{Error, Result};
13#[doc(inline)]
14pub use crate::ser::Serializer;
15
16pub mod de;
17pub mod err;
18pub mod ser;
19#[cfg(test)]
20mod test;
21mod utils;
22
23// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER#description
24pub const MAX_SAFE_INTEGER: i64 = 2_i64.pow(53) - 1;
25
26// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MIN_SAFE_INTEGER#description
27pub const MIN_SAFE_INTEGER: i64 = -MAX_SAFE_INTEGER;
28
29/// Convert a `T` into `rquickjs::Value`
30///
31/// # Example
32///
33/// ```
34/// use std::error::Error;
35///
36/// use serde::Serialize;
37/// use rquickjs::{Runtime, Context};
38///
39/// #[derive(Serialize)]
40/// struct User {
41///     fingerprint: String,
42///     location: String,
43/// }
44///
45/// fn serialize_to_value() -> Result<(), Box<dyn Error>> {
46///     let u = User {
47///         fingerprint: "0xF9BA143B95FF6D82".to_owned(),
48///         location: "Menlo Park, CA".to_owned(),
49///     };
50///
51///     let rt = Runtime::new().unwrap();
52///     let ctx = Context::full(&rt).unwrap();
53///
54///     ctx.with(|ctx| {
55///         let v = rquickjs_serde::to_value(ctx, u).unwrap();
56///         let obj = v.into_object().unwrap();
57///
58///         let fingerprint: String = obj.get("fingerprint").unwrap();
59///         assert_eq!(fingerprint, "0xF9BA143B95FF6D82");
60///
61///         let location: String = obj.get("location").unwrap();
62///         assert_eq!(location, "Menlo Park, CA");
63///     });
64///
65///     Ok(())
66/// }
67/// #
68/// # serialize_to_value().unwrap();
69/// ```
70#[inline]
71pub fn to_value<T>(context: Ctx<'_>, value: T) -> Result<Value<'_>>
72where
73    T: Serialize,
74{
75    let mut serializer = Serializer::from_context(context)?;
76    value.serialize(&mut serializer)?;
77    Ok(serializer.value)
78}
79
80/// Interpret a `rquickjs::Value` as an instance of type `T`.
81///
82/// # Example
83///
84/// ```
85/// use std::error::Error;
86///
87/// use serde::Deserialize;
88/// use rquickjs::{Runtime, Context, Value};
89///
90/// #[derive(Deserialize, Debug)]
91/// struct User {
92///     fingerprint: String,
93///     location: String,
94/// }
95///
96/// fn deserialize_from_value() -> Result<(), Box<dyn Error>> {
97///     let rt = Runtime::new().unwrap();
98///     let ctx = Context::full(&rt).unwrap();
99///
100///     let v = ctx.with(|ctx| {
101///          ctx.eval::<Value<'_>, _>("var a = {fingerprint: '0xF9BA143B95FF6D82', location: 'Menlo Park, CA'};").unwrap();
102///          let val = ctx.globals().get("a").unwrap();
103///          let u: User = rquickjs_serde::from_value(val).unwrap();
104///          u
105///     });
106///
107///     assert_eq!(v.fingerprint, "0xF9BA143B95FF6D82");
108///     assert_eq!(v.location, "Menlo Park, CA");
109///
110///     Ok(())
111/// }
112/// #
113/// # deserialize_from_value().unwrap();
114/// ```
115///
116/// # Errors
117///
118/// This conversion can fail if the structure of the Value does not match the
119/// structure expected by `T`, for example if `T` is a struct type but the Value
120/// contains something other than a JS Object. It can also fail if the structure
121/// is correct but `T`'s implementation of `Deserialize` decides that something
122/// is wrong with the data, for example required struct fields are missing from
123/// the JS Object or some number is too big to fit in the expected primitive
124/// type.
125#[inline]
126pub fn from_value<T>(value: Value) -> Result<T>
127where
128    T: DeserializeOwned,
129{
130    let mut deserializer = Deserializer::from(value);
131    T::deserialize(&mut deserializer)
132}
133
134/// Interpret a `rquickjs::Value` as an instance of type `T` in strict mode.
135///
136/// Strict mode will stick to the behaviour of JSON.stringify.
137///
138/// See [from_value] for more information about the deserialization.
139pub fn from_value_strict<T>(value: Value) -> Result<T>
140where
141    T: DeserializeOwned,
142{
143    let mut deserializer = Deserializer::from(value).with_strict();
144    T::deserialize(&mut deserializer)
145}
146
147#[cfg(test)]
148mod tests {
149    use std::collections::BTreeMap;
150
151    use quickcheck::quickcheck;
152    use serde::de::DeserializeOwned;
153    use serde::{Deserialize, Serialize};
154
155    use crate::de::Deserializer as ValueDeserializer;
156    use crate::err::Result;
157    use crate::ser::Serializer as ValueSerializer;
158    use crate::test::Runtime;
159    use crate::{MAX_SAFE_INTEGER, MIN_SAFE_INTEGER};
160
161    quickcheck! {
162        fn test_str(expected: String) -> Result<bool> {
163            let actual = do_roundtrip::<_, String>(&expected);
164            Ok(expected == actual)
165        }
166
167        fn test_u8(expected: u8) -> Result<bool> {
168            let actual = do_roundtrip::<_, u8>(&expected);
169            Ok(expected == actual)
170        }
171
172        fn test_u16(expected: u16) -> Result<bool> {
173            let actual = do_roundtrip::<_, u16>(&expected);
174            Ok(expected == actual)
175        }
176
177        fn test_f32(expected: f32) -> quickcheck::TestResult {
178            if expected.is_nan() {
179                return quickcheck::TestResult::discard();
180            }
181
182            let actual = do_roundtrip::<_, f32>(&expected);
183            quickcheck::TestResult::from_bool(expected == actual)
184        }
185
186        fn test_i32(expected: i32) -> Result<bool> {
187            let actual = do_roundtrip::<_, i32>(&expected);
188            Ok(expected == actual)
189        }
190
191        // This test is not representative of what is happening in the real world. Since we are transcoding
192        // from msgpack, only values greather than or equal to u32::MAX would be serialized as `BigInt`. Any other values would
193        // be serialized as a `number`.
194        //
195        // See https://github.com/3Hren/msgpack-rust/blob/aa3c4a77b2b901fe73a555c615b92773b40905fc/rmp/src/encode/sint.rs#L170.
196        //
197        // This test works here since we are explicitly calling serialize_i64 and deserialize_i64.
198        fn test_i64(expected: i64) -> Result<bool> {
199            if (MIN_SAFE_INTEGER..=MAX_SAFE_INTEGER).contains(&expected) {
200                let actual = do_roundtrip::<_, i64>(&expected);
201                Ok(expected == actual)
202            } else {
203                let expected_f64 = expected as f64;
204                let actual = do_roundtrip::<_, f64>(&expected_f64);
205                Ok(expected_f64 == actual)
206            }
207        }
208
209        fn test_u32(expected: u32) -> Result<bool> {
210            let actual = do_roundtrip::<_, u32>(&expected);
211            Ok(expected == actual)
212        }
213
214        // This test is not representative of what is happening in the real world. Since we are transcoding
215        // from msgpack, only values larger than i64::MAX would be serialized as BigInt. Any other values would
216        // be serialized as a number.
217        //
218        // See https://github.com/3Hren/msgpack-rust/blob/aa3c4a77b2b901fe73a555c615b92773b40905fc/rmp/src/encode/sint.rs#L170.
219        //
220        // This test works here since we are explicitly calling serialize_u64 and deserialize_u64.
221        fn test_u64(expected: u64) -> Result<bool> {
222            if expected <= MAX_SAFE_INTEGER as u64 {
223                let actual = do_roundtrip::<_, u64>(&expected);
224                Ok(expected == actual)
225            } else {
226                let expected_f64 = expected as f64;
227                let actual = do_roundtrip::<_, f64>(&expected_f64);
228                Ok(expected_f64 == actual)
229            }
230        }
231
232        fn test_bool(expected: bool) -> Result<bool> {
233            let actual = do_roundtrip::<_, bool>(&expected);
234            Ok(expected == actual)
235        }
236    }
237
238    #[test]
239    fn test_map() {
240        let mut expected = BTreeMap::<String, String>::new();
241        expected.insert("foo".to_string(), "bar".to_string());
242        expected.insert("hello".to_string(), "world".to_string());
243
244        let actual = do_roundtrip::<_, BTreeMap<String, String>>(&expected);
245
246        assert_eq!(expected, actual);
247    }
248
249    #[test]
250    fn test_struct_into_map() {
251        #[derive(Debug, Serialize, Deserialize, PartialEq)]
252        struct MyObject {
253            foo: String,
254            bar: u32,
255        }
256        let expected = MyObject {
257            foo: "hello".to_string(),
258            bar: 1337,
259        };
260
261        let actual = do_roundtrip::<_, MyObject>(&expected);
262
263        assert_eq!(expected, actual);
264    }
265
266    #[test]
267    fn test_nested_maps() {
268        let mut expected = BTreeMap::<String, BTreeMap<String, String>>::new();
269        let mut a = BTreeMap::new();
270        a.insert("foo".to_string(), "bar".to_string());
271        a.insert("hello".to_string(), "world".to_string());
272        let mut b = BTreeMap::new();
273        b.insert("toto".to_string(), "titi".to_string());
274        expected.insert("aaa".to_string(), a);
275        expected.insert("bbb".to_string(), b);
276
277        let actual = do_roundtrip::<_, BTreeMap<String, BTreeMap<String, String>>>(&expected);
278
279        assert_eq!(expected, actual);
280    }
281
282    #[test]
283    fn test_nested_structs_into_maps() {
284        #[derive(Debug, Serialize, Deserialize, PartialEq)]
285        struct MyObjectB {
286            toto: String,
287            titi: i32,
288        }
289
290        #[derive(Debug, Serialize, Deserialize, PartialEq)]
291        struct MyObjectA {
292            foo: String,
293            bar: u32,
294            b: MyObjectB,
295        }
296        let expected = MyObjectA {
297            foo: "hello".to_string(),
298            bar: 1337,
299            b: MyObjectB {
300                toto: "world".to_string(),
301                titi: -42,
302            },
303        };
304
305        let actual = do_roundtrip::<_, MyObjectA>(&expected);
306
307        assert_eq!(expected, actual);
308    }
309
310    #[test]
311    fn test_sequence() {
312        let expected = vec!["hello".to_string(), "world".to_string()];
313
314        let actual = do_roundtrip::<_, Vec<String>>(&expected);
315
316        assert_eq!(expected, actual);
317    }
318
319    #[test]
320    fn test_nested_sequences() {
321        let mut expected = Vec::new();
322        let a = vec!["foo".to_string(), "bar".to_string()];
323        let b = vec!["toto".to_string(), "tata".to_string()];
324        expected.push(a);
325        expected.push(b);
326
327        let actual = do_roundtrip::<_, Vec<Vec<String>>>(&expected);
328
329        assert_eq!(expected, actual);
330    }
331
332    #[test]
333    fn test_sanity() {
334        #[derive(Debug, Serialize, Deserialize, PartialEq)]
335        struct MyObject {
336            a: u8,
337            b: u16,
338            c: u32,
339            d: u64,
340            e: i8,
341            f: i16,
342            g: i32,
343            h: i64,
344            i: f32,
345            j: f64,
346            k: String,
347            l: bool,
348            m: BTreeMap<String, u32>,
349            n: Vec<u32>,
350            o: BTreeMap<String, BTreeMap<String, u32>>,
351            p: Vec<Vec<u32>>,
352            bb: MyObjectB,
353        }
354
355        #[derive(Debug, Serialize, Deserialize, PartialEq)]
356        struct MyObjectB {
357            a: u32,
358            cc: MyObjectC,
359        }
360
361        #[derive(Debug, Serialize, Deserialize, PartialEq)]
362        struct MyObjectC {
363            a: Vec<u32>,
364            b: BTreeMap<String, u32>,
365        }
366
367        let mut cc_b = BTreeMap::new();
368        cc_b.insert("a".to_string(), 123);
369        cc_b.insert("b".to_string(), 456);
370        let cc = MyObjectC {
371            a: vec![1337, 42],
372            b: cc_b,
373        };
374
375        let bb = MyObjectB { a: 789, cc };
376
377        let mut m = BTreeMap::new();
378        m.insert("a".to_string(), 123);
379        m.insert("b".to_string(), 456);
380        m.insert("c".to_string(), 789);
381
382        let mut oo = BTreeMap::new();
383        oo.insert("e".to_string(), 123);
384
385        let mut o = BTreeMap::new();
386        o.insert("d".to_string(), oo);
387
388        let expected = MyObject {
389            a: u8::MAX,
390            b: u16::MAX,
391            c: u32::MAX,
392            d: MAX_SAFE_INTEGER as u64,
393            e: i8::MAX,
394            f: i16::MAX,
395            g: i32::MAX,
396            h: MIN_SAFE_INTEGER,
397            i: f32::MAX,
398            j: f64::MAX,
399            k: "hello world".to_string(),
400            l: true,
401            m,
402            n: vec![1, 2, 3, 4, 5],
403            o,
404            p: vec![vec![1, 2], vec![3, 4, 5]],
405            bb,
406        };
407
408        let actual = do_roundtrip::<_, MyObject>(&expected);
409
410        assert_eq!(expected, actual);
411    }
412
413    fn do_roundtrip<E, A>(expected: &E) -> A
414    where
415        E: Serialize,
416        A: DeserializeOwned,
417    {
418        let rt = Runtime::default();
419        rt.context().with(|cx| {
420            let mut serializer = ValueSerializer::from_context(cx).unwrap();
421            expected.serialize(&mut serializer).unwrap();
422            let mut deserializer = ValueDeserializer::from(serializer.value);
423            A::deserialize(&mut deserializer).unwrap()
424        })
425    }
426}