1use std::{collections::BTreeMap, fmt};
26
27use super::*;
28
29use ::cid::multibase;
30use ipld_core::{ipld, ipld::Ipld};
31use serde::de;
32
33#[derive(Serialize, Deserialize, JsonSchema)]
34#[schemars(rename = "Ipld")]
35pub struct IpldLotusJson(
36 #[serde(with = "self")]
37 #[schemars(with = "serde_json::Value")] Ipld,
39);
40
41impl HasLotusJson for Ipld {
42 type LotusJson = IpldLotusJson;
43 #[cfg(test)]
44 fn snapshots() -> Vec<(serde_json::Value, Self)> {
45 vec![
46 (
47 json!({
48 "my_link": {
49 "/": "QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n"
50 },
51 "my_bytes": {
52 "/": { "bytes": "mVGhlIHF1aQ" }
53 },
54 "my_string": "Some data",
55 "my_float": {
56 "/": { "float": "10.5" }
57 },
58 "my_int": {
59 "/": { "int": "8" }
60 },
61 "my_neg_int": {
62 "/": { "int": "-20" }
63 },
64 "my_null": null,
65 "my_list": [
66 null,
67 { "/": "bafy2bzaceaa466o2jfc4g4ggrmtf55ygigvkmxvkr5mvhy4qbwlxetbmlkqjk" },
68 {"/": { "int": "1" }},
69 ]
70 }),
71 ipld!({
72 "my_link": Ipld::Link("QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n".parse().unwrap()),
73 "my_bytes": Ipld::Bytes(vec![0x54, 0x68, 0x65, 0x20, 0x71, 0x75, 0x69]),
74 "my_string": "Some data",
75 "my_float": 10.5,
76 "my_int": 8,
77 "my_neg_int": -20,
78 "my_null": null,
79 "my_list": [
80 null,
81 Ipld::Link("bafy2bzaceaa466o2jfc4g4ggrmtf55ygigvkmxvkr5mvhy4qbwlxetbmlkqjk".parse().unwrap()),
82 1,
83 ],
84 }),
85 ),
86 (
88 json!({"/":{"/":"QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n"}}),
89 ipld!({"/": Ipld::Link("QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n".parse().unwrap())}),
90 ),
91 ]
92 }
93 fn into_lotus_json(self) -> Self::LotusJson {
94 IpldLotusJson(self)
95 }
96 fn from_lotus_json(IpldLotusJson(it): Self::LotusJson) -> Self {
97 it
98 }
99}
100
101const BYTES_JSON_KEY: &str = "bytes";
102const INT_JSON_KEY: &str = "int";
103const FLOAT_JSON_KEY: &str = "float";
104
105#[derive(Serialize)]
107#[serde(transparent)]
108struct Ref<'a>(#[serde(with = "self")] pub &'a Ipld);
109
110fn serialize<S>(ipld: &Ipld, serializer: S) -> Result<S::Ok, S::Error>
111where
112 S: Serializer,
113{
114 match &ipld {
115 Ipld::Null => serializer.serialize_none(),
116 Ipld::Bool(bool) => serializer.serialize_bool(*bool),
117 Ipld::Integer(i128) => serialize(
118 &ipld!({ "/": { INT_JSON_KEY: i128.to_string() } }),
119 serializer,
120 ),
121 Ipld::Float(f64) => serialize(
122 &ipld!({ "/": { FLOAT_JSON_KEY: f64.to_string() } }),
123 serializer,
124 ),
125 Ipld::String(string) => serializer.serialize_str(string),
126 Ipld::Bytes(bytes) => serialize(
127 &ipld!({ "/": { BYTES_JSON_KEY: multibase::encode(multibase::Base::Base64, bytes) } }),
128 serializer,
129 ),
130 Ipld::List(list) => {
131 let wrapped = list.iter().map(Ref);
132 serializer.collect_seq(wrapped)
133 }
134 Ipld::Map(map) => {
135 let wrapped = map.iter().map(|(key, ipld)| (key, Ref(ipld)));
136 serializer.collect_map(wrapped)
137 }
138 Ipld::Link(cid) => serialize(&ipld!({ "/": cid.to_string() }), serializer),
139 }
140}
141
142fn deserialize<'de, D>(deserializer: D) -> Result<Ipld, D::Error>
143where
144 D: Deserializer<'de>,
145{
146 deserializer.deserialize_any(JSONVisitor)
147}
148
149struct JSONVisitor;
151impl<'de> de::Visitor<'de> for JSONVisitor {
152 type Value = Ipld;
153
154 fn expecting(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
155 fmt.write_str("any valid JSON value")
156 }
157
158 #[inline]
159 fn visit_str<E: de::Error>(self, value: &str) -> Result<Self::Value, E> {
160 self.visit_string(String::from(value))
161 }
162
163 #[inline]
164 fn visit_string<E: de::Error>(self, value: String) -> Result<Self::Value, E> {
165 Ok(Ipld::String(value))
166 }
167 #[inline]
168 fn visit_bytes<E: de::Error>(self, v: &[u8]) -> Result<Self::Value, E> {
169 self.visit_byte_buf(v.to_owned())
170 }
171
172 #[inline]
173 fn visit_byte_buf<E: de::Error>(self, v: Vec<u8>) -> Result<Self::Value, E> {
174 Ok(Ipld::Bytes(v))
175 }
176
177 #[inline]
178 fn visit_u64<E: de::Error>(self, v: u64) -> Result<Self::Value, E> {
179 Ok(Ipld::Integer(v.into()))
180 }
181
182 #[inline]
183 fn visit_i64<E: de::Error>(self, v: i64) -> Result<Self::Value, E> {
184 Ok(Ipld::Integer(v.into()))
185 }
186
187 #[inline]
188 fn visit_i128<E: de::Error>(self, v: i128) -> Result<Self::Value, E> {
189 Ok(Ipld::Integer(v))
190 }
191
192 #[inline]
193 fn visit_bool<E: de::Error>(self, v: bool) -> Result<Self::Value, E> {
194 Ok(Ipld::Bool(v))
195 }
196
197 #[inline]
198 fn visit_none<E: de::Error>(self) -> Result<Self::Value, E> {
199 self.visit_unit()
200 }
201
202 #[inline]
203 fn visit_unit<E: de::Error>(self) -> Result<Self::Value, E> {
204 Ok(Ipld::Null)
205 }
206
207 #[inline]
208 fn visit_seq<V: de::SeqAccess<'de>>(self, mut visitor: V) -> Result<Self::Value, V::Error> {
209 let mut vec = Vec::new();
210
211 while let Some(IpldLotusJson(elem)) = visitor.next_element()? {
212 vec.push(elem);
213 }
214
215 Ok(Ipld::List(vec))
216 }
217
218 #[inline]
219 fn visit_map<V>(self, mut visitor: V) -> Result<Self::Value, V::Error>
220 where
221 V: de::MapAccess<'de>,
222 {
223 let mut map = BTreeMap::new();
224
225 while let Some((key, IpldLotusJson(value))) = visitor.next_entry()? {
226 map.insert(key, value);
227 }
228
229 if map.len() == 1
230 && let Some(v) = map.get("/")
231 {
232 match v {
233 Ipld::String(s) => {
234 return Ok(Ipld::Link(s.parse().map_err(de::Error::custom)?));
236 }
237 Ipld::Map(obj) => {
238 if let Some(Ipld::String(s)) = obj.get(BYTES_JSON_KEY) {
239 let (_, bz) =
241 multibase::decode(s).map_err(|e| de::Error::custom(e.to_string()))?;
242 return Ok(Ipld::Bytes(bz));
243 }
244 if let Some(Ipld::String(s)) = obj.get(INT_JSON_KEY) {
245 let s = s
247 .parse::<i128>()
248 .map_err(|e| de::Error::custom(e.to_string()))?;
249 return Ok(Ipld::Integer(s));
250 }
251 if let Some(Ipld::String(s)) = obj.get(FLOAT_JSON_KEY) {
252 let s = s
254 .parse::<f64>()
255 .map_err(|e| de::Error::custom(e.to_string()))?;
256 return Ok(Ipld::Float(s));
257 }
258 }
259 _ => (),
260 }
261 }
262
263 Ok(Ipld::Map(map))
264 }
265
266 #[inline]
267 fn visit_f64<E: de::Error>(self, v: f64) -> Result<Self::Value, E> {
268 Ok(Ipld::Float(v))
269 }
270}
271
272#[test]
273fn snapshots() {
274 assert_all_snapshots::<Ipld>()
275}
276
277#[cfg(test)]
278quickcheck::quickcheck! {
279 fn quickcheck(val: Ipld) -> () {
280 let mut val = val;
281 fn fixup_floats(ipld: &mut Ipld) {
284 match ipld {
285 Ipld::Float(v) => {
286 if v.is_nan() {
287 *ipld = Ipld::Float(0.0);
288 }
289 }
290 Ipld::List(list) => {
291 for item in list {
292 fixup_floats(item);
293 }
294 }
295 Ipld::Map(map) => {
296 for item in map.values_mut() {
297 fixup_floats(item);
298 }
299 }
300 _ => {}
301 }
302 }
303 fixup_floats(&mut val);
304 assert_unchanged_via_json(val)
305 }
306}
307
308#[test]
324#[should_panic = "Input too short"]
325fn issue_3383() {
326 let poison = Ipld::Map(BTreeMap::from_iter([(
327 String::from("/"),
328 Ipld::String(String::from("")),
329 )]));
330 let serialized = serde_json::to_value(Ref(&poison)).unwrap();
331
332 let IpldLotusJson(round_tripped) = serde_json::from_value(serialized).unwrap();
334
335 pretty_assertions::assert_eq!(round_tripped, poison); }