Skip to main content

platform_value/value_serialization/
mod.rs

1use crate::value_serialization::ser::Serializer;
2use crate::{Error, Value};
3use serde::Deserialize;
4use serde::Serialize;
5
6pub mod de;
7pub mod ser;
8
9/// Convert a `T` into `platform_value::Value` which is an enum that can represent
10/// data.
11///
12/// # Example
13///
14/// ```
15/// use serde::Serialize;
16/// use platform_value::platform_value;
17///
18/// use std::error::Error;
19///
20/// #[derive(Serialize)]
21/// struct User {
22///     fingerprint: String,
23///     location: String,
24/// }
25///
26/// fn compare_platform_values() -> Result<(), Box<dyn Error>> {
27///     let u = User {
28///         fingerprint: "0xF9BA143B95FF6D82".to_owned(),
29///         location: "Menlo Park, CA".to_owned(),
30///     };
31///
32///     // The type of `expected` is `serde_json::Value`
33///     let expected = platform_value!({
34///         "fingerprint": "0xF9BA143B95FF6D82",
35///         "location": "Menlo Park, CA",
36///     });
37///
38///     let v = platform_value::to_value(u).unwrap();
39///     assert_eq!(v, expected);
40///
41///     Ok(())
42/// }
43/// #
44/// # compare_platform_values().unwrap();
45/// ```
46///
47/// # Errors
48///
49/// This conversion can fail if `T`'s implementation of `Serialize` decides to
50/// fail. Unlike `serde_json::to_value`, `platform_value::Value::Map` accepts
51/// non-string keys (any `Value` is a valid map key), so maps with vector or
52/// numeric keys serialize without error:
53///
54/// ```
55/// use std::collections::BTreeMap;
56///
57/// let mut map = BTreeMap::new();
58/// map.insert(vec![32u8, 64], "x86");
59///
60/// let v = platform_value::to_value(map).unwrap();
61/// assert!(v.is_map());
62/// ```
63pub fn to_value<T>(value: T) -> Result<Value, Error>
64where
65    T: Serialize,
66{
67    value.serialize(Serializer)
68}
69
70/// Interpret a `serde_json::Value` as an instance of type `T`.
71///
72/// # Example
73///
74/// ```
75/// use serde::Deserialize;
76/// use platform_value::platform_value;
77///
78/// #[derive(Deserialize, Debug)]
79/// struct User {
80///     fingerprint: String,
81///     location: String,
82/// }
83///
84/// // The type of `j` is `serde_json::Value`
85/// let j = platform_value!({
86///     "fingerprint": "0xF9BA143B95FF6D82",
87///     "location": "Menlo Park, CA"
88/// });
89///
90/// let u: User = platform_value::from_value(j).unwrap();
91/// println!("{:#?}", u);
92/// ```
93///
94/// # Errors
95///
96/// This conversion can fail if the structure of the Value does not match the
97/// structure expected by `T`, for example if `T` is a struct type but the Value
98/// contains something other than a JSON map. It can also fail if the structure
99/// is correct but `T`'s implementation of `Deserialize` decides that something
100/// is wrong with the data, for example required struct fields are missing from
101/// the JSON map or some number is too big to fit in the expected primitive
102/// type.
103pub fn from_value<'de, T>(value: Value) -> Result<T, Error>
104where
105    T: Deserialize<'de>,
106{
107    T::deserialize(de::Deserializer(value))
108}
109
110#[cfg(test)]
111#[allow(clippy::needless_borrows_for_generic_args)]
112mod tests {
113    use serde::{Deserialize, Serialize};
114    use std::collections::HashMap;
115
116    use super::*;
117
118    #[test]
119    fn yeet() {
120        #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
121        struct Yeet {
122            arr: Vec<String>,
123            map: HashMap<String, char>,
124            number: i32,
125            //todo: manage static strings
126            //static_string: &'static str,
127        }
128
129        let mut hm = HashMap::new();
130        hm.insert("wow".to_owned(), 'a');
131        hm.insert("lol".to_owned(), 'd');
132
133        let yeet = Yeet {
134            arr: vec!["kek".to_owned(), "top".to_owned()],
135            map: hm,
136            number: 420,
137            //static_string: "pizza",
138        };
139
140        let platform_value = to_value(yeet.clone()).expect("please");
141        let yeet_back: Yeet = from_value(platform_value).expect("please once again");
142
143        assert_eq!(yeet, yeet_back);
144    }
145
146    #[test]
147    fn test_externally_tagged_unit_variant() {
148        #[derive(Serialize, Deserialize, Debug, PartialEq)]
149        #[serde(rename_all = "camelCase")]
150        enum Choice {
151            Abstain,
152            Lock,
153            TowardsIdentity(String),
154        }
155
156        let v = to_value(&Choice::Abstain).unwrap();
157        assert_eq!(v, Value::Text("abstain".to_string()));
158        let back: Choice = from_value(v).unwrap();
159        assert_eq!(back, Choice::Abstain);
160
161        let v = to_value(&Choice::Lock).unwrap();
162        assert_eq!(v, Value::Text("lock".to_string()));
163        let back: Choice = from_value(v).unwrap();
164        assert_eq!(back, Choice::Lock);
165    }
166
167    #[test]
168    fn test_externally_tagged_newtype_variant() {
169        #[derive(Serialize, Deserialize, Debug, PartialEq)]
170        #[serde(rename_all = "camelCase")]
171        enum Choice {
172            Abstain,
173            Lock,
174            TowardsIdentity(String),
175        }
176
177        let v = to_value(&Choice::TowardsIdentity("abc".into())).unwrap();
178        let back: Choice = from_value(v).unwrap();
179        assert_eq!(back, Choice::TowardsIdentity("abc".into()));
180    }
181
182    #[test]
183    fn test_internally_tagged_enum() {
184        #[derive(Serialize, Deserialize, Debug, PartialEq)]
185        #[serde(tag = "$formatVersion")]
186        enum Info {
187            #[serde(rename = "0")]
188            V0 { name: String },
189        }
190
191        let v = to_value(&Info::V0 {
192            name: "test".into(),
193        })
194        .unwrap();
195        let back: Info = from_value(v).unwrap();
196        assert_eq!(
197            back,
198            Info::V0 {
199                name: "test".into()
200            }
201        );
202    }
203
204    #[test]
205    fn test_externally_tagged_struct_variant() {
206        #[derive(Serialize, Deserialize, Debug, PartialEq)]
207        enum Shape {
208            Circle { radius: f64 },
209            Rectangle { width: f64, height: f64 },
210        }
211
212        let v = to_value(&Shape::Circle { radius: 5.0 }).unwrap();
213        let back: Shape = from_value(v).unwrap();
214        assert_eq!(back, Shape::Circle { radius: 5.0 });
215
216        let v = to_value(&Shape::Rectangle {
217            width: 3.0,
218            height: 4.0,
219        })
220        .unwrap();
221        let back: Shape = from_value(v).unwrap();
222        assert_eq!(
223            back,
224            Shape::Rectangle {
225                width: 3.0,
226                height: 4.0
227            }
228        );
229    }
230
231    #[test]
232    fn test_externally_tagged_tuple_variant() {
233        #[derive(Serialize, Deserialize, Debug, PartialEq)]
234        enum Point {
235            TwoD(f64, f64),
236            ThreeD(f64, f64, f64),
237        }
238
239        let v = to_value(&Point::TwoD(1.0, 2.0)).unwrap();
240        let back: Point = from_value(v).unwrap();
241        assert_eq!(back, Point::TwoD(1.0, 2.0));
242
243        let v = to_value(&Point::ThreeD(1.0, 2.0, 3.0)).unwrap();
244        let back: Point = from_value(v).unwrap();
245        assert_eq!(back, Point::ThreeD(1.0, 2.0, 3.0));
246    }
247
248    #[test]
249    fn test_externally_tagged_newtype_wrapping_struct() {
250        #[derive(Serialize, Deserialize, Debug, PartialEq)]
251        #[serde(rename_all = "camelCase")]
252        enum Vote {
253            ResourceVote(InnerVote),
254        }
255
256        #[derive(Serialize, Deserialize, Debug, PartialEq)]
257        #[serde(rename_all = "camelCase")]
258        struct InnerVote {
259            poll_name: String,
260            choice: u32,
261        }
262
263        let v = to_value(&Vote::ResourceVote(InnerVote {
264            poll_name: "test".into(),
265            choice: 42,
266        }))
267        .unwrap();
268        let back: Vote = from_value(v).unwrap();
269        assert_eq!(
270            back,
271            Vote::ResourceVote(InnerVote {
272                poll_name: "test".into(),
273                choice: 42,
274            })
275        );
276    }
277}