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