1use std::str::FromStr;
13
14use base64::engine::{DecodePaddingMode, GeneralPurpose, GeneralPurposeConfig};
15use base64::Engine;
16use num_bigint::BigInt;
17use serde::de::{self, Deserializer, MapAccess, SeqAccess, Visitor};
18use serde::Deserialize;
19
20use crate::descriptor::{Strategy, TypeBody, TypeShape};
21use crate::error::{PanicKind, SoilError};
22use crate::value::{MapOrder, MapVal, Record, Ref, SumVal, Value};
23use crate::Runtime;
24
25const BASE64: GeneralPurpose = GeneralPurpose::new(
28 &base64::alphabet::STANDARD,
29 GeneralPurposeConfig::new().with_decode_padding_mode(DecodePaddingMode::RequireCanonical),
30);
31
32pub fn decode(runtime: &Runtime, shape: &TypeShape, json: &str) -> Result<Value, SoilError> {
35 let tree: Tree = serde_json::from_str(json)
36 .map_err(|e| SoilError::new(PanicKind::DecodeError, format!("invalid JSON: {e}")))?;
37 decode_tree(runtime, shape, &tree, "$")
38}
39
40enum Tree {
43 Null,
44 Bool(bool),
45 Num(serde_json::Number),
46 Str(String),
47 Arr(Vec<Tree>),
48 Obj(Vec<(String, Tree)>),
49}
50
51impl Tree {
52 fn kind(&self) -> &'static str {
53 match self {
54 Tree::Null => "null",
55 Tree::Bool(_) => "boolean",
56 Tree::Num(_) => "number",
57 Tree::Str(_) => "string",
58 Tree::Arr(_) => "array",
59 Tree::Obj(_) => "object",
60 }
61 }
62}
63
64impl<'de> Deserialize<'de> for Tree {
65 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Tree, D::Error> {
66 struct TreeVisitor;
67
68 impl<'de> Visitor<'de> for TreeVisitor {
69 type Value = Tree;
70
71 fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
72 f.write_str("any JSON value")
73 }
74
75 fn visit_unit<E>(self) -> Result<Tree, E> {
76 Ok(Tree::Null)
77 }
78
79 fn visit_bool<E>(self, b: bool) -> Result<Tree, E> {
80 Ok(Tree::Bool(b))
81 }
82
83 fn visit_i64<E>(self, n: i64) -> Result<Tree, E> {
84 Ok(Tree::Num(n.into()))
85 }
86
87 fn visit_u64<E>(self, n: u64) -> Result<Tree, E> {
88 Ok(Tree::Num(n.into()))
89 }
90
91 fn visit_f64<E: de::Error>(self, n: f64) -> Result<Tree, E> {
92 serde_json::Number::from_f64(n)
93 .map(Tree::Num)
94 .ok_or_else(|| E::custom("non-finite number"))
95 }
96
97 fn visit_str<E>(self, s: &str) -> Result<Tree, E> {
98 Ok(Tree::Str(s.to_string()))
99 }
100
101 fn visit_string<E>(self, s: String) -> Result<Tree, E> {
102 Ok(Tree::Str(s))
103 }
104
105 fn visit_seq<A: SeqAccess<'de>>(self, mut seq: A) -> Result<Tree, A::Error> {
106 let mut items = Vec::new();
107 while let Some(item) = seq.next_element()? {
108 items.push(item);
109 }
110 Ok(Tree::Arr(items))
111 }
112
113 fn visit_map<A: MapAccess<'de>>(self, mut map: A) -> Result<Tree, A::Error> {
114 let mut entries: Vec<(String, Tree)> = Vec::new();
115 while let Some((key, value)) = map.next_entry::<String, Tree>()? {
116 if entries.iter().any(|(k, _)| *k == key) {
117 return Err(de::Error::custom(format!("duplicate object key {key:?}")));
118 }
119 entries.push((key, value));
120 }
121 Ok(Tree::Obj(entries))
122 }
123 }
124
125 deserializer.deserialize_any(TreeVisitor)
126 }
127}
128
129fn err(path: &str, message: impl std::fmt::Display) -> SoilError {
130 SoilError::new(PanicKind::DecodeError, format!("at {path}: {message}"))
131}
132
133fn decode_tree(
134 runtime: &Runtime,
135 shape: &TypeShape,
136 tree: &Tree,
137 path: &str,
138) -> Result<Value, SoilError> {
139 match shape {
140 TypeShape::I64 => Ok(Value::I64(decode_int(tree, path, "I64")?)),
141 TypeShape::U64 => Ok(Value::U64(decode_int(tree, path, "U64")?)),
142 TypeShape::I32 => Ok(Value::I32(decode_int(tree, path, "I32")?)),
143 TypeShape::U32 => Ok(Value::U32(decode_int(tree, path, "U32")?)),
144 TypeShape::I16 => Ok(Value::I16(decode_int(tree, path, "I16")?)),
145 TypeShape::U16 => Ok(Value::U16(decode_int(tree, path, "U16")?)),
146 TypeShape::I8 => Ok(Value::I8(decode_int(tree, path, "I8")?)),
147 TypeShape::U8 => Ok(Value::U8(decode_int(tree, path, "U8")?)),
148 TypeShape::BigInt => match tree {
149 Tree::Num(n) => {
150 if let Some(i) = n.as_i64() {
151 Ok(Value::BigInt(Ref::new(BigInt::from(i))))
152 } else if let Some(u) = n.as_u64() {
153 Ok(Value::BigInt(Ref::new(BigInt::from(u))))
154 } else {
155 Err(err(path, "BigInt number must be an exact integer"))
156 }
157 }
158 Tree::Str(s) => BigInt::from_str(s)
159 .map(|b| Value::BigInt(Ref::new(b)))
160 .map_err(|_| err(path, format!("invalid BigInt string {s:?}"))),
161 other => Err(err(
162 path,
163 format!("expected BigInt, found {}", other.kind()),
164 )),
165 },
166 TypeShape::F64 => match tree {
167 Tree::Num(n) => Ok(Value::F64(n.as_f64().expect("finite by construction"))),
168 Tree::Str(s) => match s.as_str() {
169 "NaN" => Ok(Value::F64(f64::NAN)),
170 "Inf" => Ok(Value::F64(f64::INFINITY)),
171 "-Inf" => Ok(Value::F64(f64::NEG_INFINITY)),
172 _ => Err(err(path, format!("invalid F64 string {s:?}"))),
173 },
174 other => Err(err(path, format!("expected F64, found {}", other.kind()))),
175 },
176 TypeShape::Utf8 => match tree {
177 Tree::Str(s) => Ok(Value::Utf8(Ref::from(s.as_str()))),
178 other => Err(err(
179 path,
180 format!("expected string, found {}", other.kind()),
181 )),
182 },
183 TypeShape::Bytes => match tree {
184 Tree::Str(s) => BASE64
185 .decode(s)
186 .map(|b| Value::Bytes(Ref::from(b)))
187 .map_err(|e| err(path, format!("invalid base64: {e}"))),
188 other => Err(err(
189 path,
190 format!("expected base64 string, found {}", other.kind()),
191 )),
192 },
193 TypeShape::Unit => match tree {
194 Tree::Null => Ok(Value::Unit),
195 other => Err(err(path, format!("expected null, found {}", other.kind()))),
196 },
197 TypeShape::List(elem) => match tree {
198 Tree::Arr(items) => {
199 let mut values = Vec::with_capacity(items.len());
200 for (i, item) in items.iter().enumerate() {
201 values.push(decode_tree(runtime, elem, item, &format!("{path}[{i}]"))?);
202 }
203 Ok(Value::List(Ref::new(values)))
204 }
205 other => Err(err(path, format!("expected array, found {}", other.kind()))),
206 },
207 TypeShape::Map(key_shape, value_shape) => match tree {
208 Tree::Arr(items) => {
209 let mut map = MapVal::new(MapOrder::Structural);
212 for (i, item) in items.iter().enumerate() {
213 let entry_path = format!("{path}[{i}]");
214 let Tree::Obj(entries) = item else {
215 return Err(err(&entry_path, "map entry must be an object"));
216 };
217 let mut key = None;
218 let mut value = None;
219 for (k, v) in entries {
220 match k.as_str() {
221 "key" => key = Some(v),
222 "value" => value = Some(v),
223 other => {
224 return Err(err(
225 &entry_path,
226 format!("unexpected map-entry key {other:?}"),
227 ));
228 }
229 }
230 }
231 let (Some(key), Some(value)) = (key, value) else {
232 return Err(err(&entry_path, "map entry needs \"key\" and \"value\""));
233 };
234 let key = decode_tree(runtime, key_shape, key, &format!("{entry_path}.key"))?;
235 let value =
236 decode_tree(runtime, value_shape, value, &format!("{entry_path}.value"))?;
237 if map.get(runtime, &key)?.is_some() {
238 return Err(err(&entry_path, "duplicate map key"));
239 }
240 map.insert(runtime, key, value)?;
241 }
242 Ok(Value::Map(Ref::new(map)))
243 }
244 other => Err(err(path, format!("expected array, found {}", other.kind()))),
245 },
246 TypeShape::Closure => Err(err(path, "functions have no JSON encoding")),
247 TypeShape::Named(id) => {
248 if *id == runtime.registry.bool_id() {
249 return match tree {
250 Tree::Bool(b) => Ok(Value::Sum(Ref::new(SumVal {
251 type_id: *id,
252 variant: if *b { 0 } else { 1 },
253 payload: None,
254 }))),
255 other => Err(err(
256 path,
257 format!("expected boolean, found {}", other.kind()),
258 )),
259 };
260 }
261 let desc = runtime.registry.get(*id)?;
262 if desc.strategy == Strategy::Opaque {
263 return Err(err(
264 path,
265 format!("type {} is opaque and cannot be decoded", desc.name),
266 ));
267 }
268 match &desc.body {
269 TypeBody::Record(fields) => {
270 let Tree::Obj(entries) = tree else {
271 return Err(err(path, format!("expected object, found {}", tree.kind())));
272 };
273 for (key, _) in entries {
274 match fields.iter().find(|f| f.name == *key) {
275 None => {
276 return Err(err(
277 path,
278 format!("unknown field {key:?} on {}", desc.name),
279 ));
280 }
281 Some(f) if f.ignored.is_some() => {
282 return Err(err(
286 path,
287 format!(
288 "field {key:?} on {} is ignored and must be omitted",
289 desc.name
290 ),
291 ));
292 }
293 Some(_) => {}
294 }
295 }
296 let mut decoded = Vec::with_capacity(fields.len());
297 for field in fields {
298 if field.ignored.is_some() {
299 decoded.push(None);
300 continue;
301 }
302 let Some((_, subtree)) = entries.iter().find(|(k, _)| *k == field.name)
303 else {
304 return Err(err(
305 path,
306 format!("missing field {:?} on {}", field.name, desc.name),
307 ));
308 };
309 decoded.push(Some(decode_tree(
310 runtime,
311 &field.shape,
312 subtree,
313 &format!("{path}.{}", field.name),
314 )?));
315 }
316 let thunk_args: Vec<Value> = decoded.iter().flatten().cloned().collect();
320 let mut values = Vec::with_capacity(fields.len());
321 for (field, slot) in fields.iter().zip(decoded) {
322 match slot {
323 Some(value) => values.push(value),
324 None => {
325 let default = field
326 .ignored
327 .as_ref()
328 .expect("slot is empty only for ignored fields");
329 values.push(default.call(&thunk_args)?);
330 }
331 }
332 }
333 Ok(Value::Record(Ref::new(Record {
334 type_id: *id,
335 fields: values.into_boxed_slice(),
336 })))
337 }
338 TypeBody::Sum(variants) => {
339 let Tree::Obj(entries) = tree else {
340 return Err(err(path, format!("expected object, found {}", tree.kind())));
341 };
342 let mut tag = None;
343 let mut payload_tree = None;
344 for (key, value) in entries {
345 match key.as_str() {
346 "tag" => tag = Some(value),
347 "value" => payload_tree = Some(value),
348 other => {
349 return Err(err(path, format!("unexpected key {other:?} in sum")));
350 }
351 }
352 }
353 let Some(Tree::Str(tag)) = tag else {
354 return Err(err(path, "sum needs a string \"tag\""));
355 };
356 let Some(variant_index) = variants.iter().position(|v| v.name == *tag) else {
357 return Err(err(
358 path,
359 format!("unknown variant {tag:?} of {}", desc.name),
360 ));
361 };
362 let variant = &variants[variant_index];
363 let payload = match (&variant.payload, payload_tree) {
364 (Some(shape), Some(subtree)) => Some(decode_tree(
365 runtime,
366 shape,
367 subtree,
368 &format!("{path}.value"),
369 )?),
370 (None, None) => None,
371 (Some(_), None) => {
372 return Err(err(
373 path,
374 format!("variant {tag} of {} needs a \"value\"", desc.name),
375 ));
376 }
377 (None, Some(_)) => {
378 return Err(err(
379 path,
380 format!("variant {tag} of {} takes no \"value\"", desc.name),
381 ));
382 }
383 };
384 Ok(Value::Sum(Ref::new(SumVal {
385 type_id: *id,
386 variant: u32::try_from(variant_index).expect("validated at registration"),
387 payload,
388 })))
389 }
390 TypeBody::Opaque => unreachable!("opaque body implies opaque strategy"),
391 }
392 }
393 }
394}
395
396fn decode_int<T: TryFrom<i128>>(tree: &Tree, path: &str, width: &str) -> Result<T, SoilError> {
399 let Tree::Num(n) = tree else {
400 return Err(err(
401 path,
402 format!("expected {width}, found {}", tree.kind()),
403 ));
404 };
405 let wide: i128 = if let Some(i) = n.as_i64() {
406 i128::from(i)
407 } else if let Some(u) = n.as_u64() {
408 i128::from(u)
409 } else {
410 return Err(err(path, format!("{width} must be an exact integer")));
411 };
412 T::try_from(wide).map_err(|_| err(path, format!("{n} out of range for {width}")))
413}
414
415#[cfg(test)]
416mod tests {
417 use super::*;
418 use crate::descriptor::{FieldDesc, TypeDesc, VariantDesc};
419 use crate::json::encode;
420 use crate::ops;
421 use crate::value::Closure;
422
423 fn doc_runtime() -> (Runtime, crate::descriptor::TypeId) {
424 let mut rt = Runtime::new();
425 let id = rt
426 .registry
427 .register(TypeDesc {
428 name: "Doc".to_string(),
429 strategy: Strategy::Structural,
430 body: TypeBody::Record(vec![
431 FieldDesc {
432 name: "text".to_string(),
433 shape: TypeShape::Utf8,
434 ignored: None,
435 },
436 FieldDesc {
437 name: "cached_len".to_string(),
438 shape: TypeShape::U64,
439 ignored: Some(crate::descriptor::IgnoredDefault::Native(Closure::native(
440 |args| match &args[0] {
441 Value::Utf8(s) => Ok(Value::U64(s.len() as u64)),
442 _ => unreachable!(),
443 },
444 ))),
445 },
446 FieldDesc {
447 name: "id".to_string(),
448 shape: TypeShape::I64,
449 ignored: None,
450 },
451 ]),
452 })
453 .unwrap();
454 (rt, id)
455 }
456
457 #[test]
458 fn scalar_round_trips_and_strictness() {
459 let rt = Runtime::new();
460 assert!(matches!(
461 decode(&rt, &TypeShape::I64, "-42").unwrap(),
462 Value::I64(-42)
463 ));
464 assert!(decode(&rt, &TypeShape::I64, "1.5").is_err());
465 assert!(decode(&rt, &TypeShape::I64, "1.0").is_err());
466 assert!(decode(&rt, &TypeShape::I64, "\"1\"").is_err());
467 assert!(decode(&rt, &TypeShape::U8, "256").is_err());
468 assert!(decode(&rt, &TypeShape::U8, "-1").is_err());
469 assert!(matches!(
470 decode(&rt, &TypeShape::Unit, "null").unwrap(),
471 Value::Unit
472 ));
473 assert!(decode(&rt, &TypeShape::Unit, "0").is_err());
474 }
475
476 #[test]
477 fn float_forms() {
478 let rt = Runtime::new();
479 assert!(matches!(
480 decode(&rt, &TypeShape::F64, "1.5").unwrap(),
481 Value::F64(x) if x == 1.5
482 ));
483 assert!(matches!(
484 decode(&rt, &TypeShape::F64, "\"NaN\"").unwrap(),
485 Value::F64(x) if x.is_nan()
486 ));
487 assert!(matches!(
488 decode(&rt, &TypeShape::F64, "\"-Inf\"").unwrap(),
489 Value::F64(x) if x == f64::NEG_INFINITY
490 ));
491 assert!(decode(&rt, &TypeShape::F64, "\"nan\"").is_err());
492 }
493
494 #[test]
495 fn bigint_hybrid() {
496 let rt = Runtime::new();
497 let big = decode(&rt, &TypeShape::BigInt, "\"9007199254740992\"").unwrap();
498 let reencoded = encode(&rt, &big).unwrap();
499 assert_eq!(reencoded, "\"9007199254740992\"");
500 let small = decode(&rt, &TypeShape::BigInt, "12").unwrap();
501 assert_eq!(encode(&rt, &small).unwrap(), "12");
502 assert!(decode(&rt, &TypeShape::BigInt, "9007199254740993").is_ok());
504 assert!(decode(&rt, &TypeShape::BigInt, "1.5").is_err());
505 assert!(decode(&rt, &TypeShape::BigInt, "\"twelve\"").is_err());
506 }
507
508 #[test]
509 fn bytes_reject_non_canonical_base64() {
510 let rt = Runtime::new();
511 assert!(matches!(
512 decode(&rt, &TypeShape::Bytes, "\"aGVsbG8=\"").unwrap(),
513 Value::Bytes(b) if &*b == b"hello"
514 ));
515 assert!(decode(&rt, &TypeShape::Bytes, "\"aGVsbG8\"").is_err());
516 assert!(decode(&rt, &TypeShape::Bytes, "\"a GVsbG8=\"").is_err());
517 }
518
519 #[test]
520 fn record_strictness_and_ignored_refill() {
521 let (rt, id) = doc_runtime();
522 let shape = TypeShape::Named(id);
523 let value = decode(&rt, &shape, "{\"text\":\"hello\",\"id\":7}").unwrap();
524 let Value::Record(record) = &value else {
526 panic!()
527 };
528 assert!(matches!(record.fields[1], Value::U64(5)));
529 assert_eq!(
531 encode(&rt, &value).unwrap(),
532 "{\"text\":\"hello\",\"id\":7}"
533 );
534 let reordered = decode(&rt, &shape, "{\"id\":7,\"text\":\"hello\"}").unwrap();
536 assert!(ops::eq(&rt, &value, &reordered).unwrap());
537 assert!(decode(&rt, &shape, "{\"text\":\"hello\"}").is_err());
539 assert!(decode(&rt, &shape, "{\"text\":\"hello\",\"id\":7,\"x\":0}").is_err());
540 assert!(decode(
541 &rt,
542 &shape,
543 "{\"text\":\"hello\",\"id\":7,\"cached_len\":5}"
544 )
545 .is_err());
546 assert!(decode(&rt, &shape, "{\"text\":\"hello\",\"id\":7,\"id\":8}").is_err());
547 }
548
549 #[test]
550 fn sum_and_bool() {
551 let mut rt = Runtime::new();
552 let id = rt
553 .registry
554 .register(TypeDesc {
555 name: "Shape".to_string(),
556 strategy: Strategy::Structural,
557 body: TypeBody::Sum(vec![
558 VariantDesc {
559 name: "Point".to_string(),
560 payload: None,
561 },
562 VariantDesc {
563 name: "Circle".to_string(),
564 payload: Some(TypeShape::F64),
565 },
566 ]),
567 })
568 .unwrap();
569 let shape = TypeShape::Named(id);
570 let circle = decode(&rt, &shape, "{\"tag\":\"Circle\",\"value\":2.5}").unwrap();
571 assert_eq!(
572 encode(&rt, &circle).unwrap(),
573 "{\"tag\":\"Circle\",\"value\":2.5}"
574 );
575 assert!(decode(&rt, &shape, "{\"tag\":\"Point\"}").is_ok());
576 assert!(decode(&rt, &shape, "{\"tag\":\"Square\"}").is_err());
577 assert!(decode(&rt, &shape, "{\"tag\":\"Point\",\"value\":1}").is_err());
578 assert!(decode(&rt, &shape, "{\"tag\":\"Circle\"}").is_err());
579 assert!(decode(&rt, &shape, "{\"tag\":\"Circle\",\"value\":2.5,\"x\":0}").is_err());
580
581 let bool_shape = TypeShape::Named(rt.registry.bool_id());
582 let t = decode(&rt, &bool_shape, "true").unwrap();
583 assert_eq!(encode(&rt, &t).unwrap(), "true");
584 assert!(decode(&rt, &bool_shape, "{\"tag\":\"True\"}").is_err());
585 }
586
587 #[test]
588 fn map_decode_sorts_structurally_and_rejects_duplicates() {
589 let rt = Runtime::new();
590 let shape = TypeShape::Map(Box::new(TypeShape::I64), Box::new(TypeShape::Utf8));
591 let value = decode(
592 &rt,
593 &shape,
594 "[{\"key\":2,\"value\":\"b\"},{\"key\":1,\"value\":\"a\"}]",
595 )
596 .unwrap();
597 assert_eq!(
598 encode(&rt, &value).unwrap(),
599 "[{\"key\":1,\"value\":\"a\"},{\"key\":2,\"value\":\"b\"}]"
600 );
601 assert!(decode(
602 &rt,
603 &shape,
604 "[{\"key\":1,\"value\":\"a\"},{\"key\":1,\"value\":\"b\"}]"
605 )
606 .is_err());
607 assert!(decode(&rt, &shape, "[{\"key\":1}]").is_err());
608 assert!(decode(&rt, &shape, "[{\"key\":1,\"value\":\"a\",\"z\":0}]").is_err());
609 }
610
611 #[test]
612 fn opaque_and_closures_do_not_decode() {
613 let mut rt = Runtime::new();
614 let id = rt
615 .registry
616 .register(TypeDesc {
617 name: "Handle".to_string(),
618 strategy: Strategy::Opaque,
619 body: TypeBody::Opaque,
620 })
621 .unwrap();
622 assert!(decode(&rt, &TypeShape::Named(id), "\"<handle>\"").is_err());
623 assert!(decode(&rt, &TypeShape::Closure, "null").is_err());
624 }
625
626 #[test]
627 fn duplicate_object_keys_rejected_at_parse() {
628 let (rt, id) = doc_runtime();
629 let result = decode(
630 &rt,
631 &TypeShape::Named(id),
632 "{\"text\":\"a\",\"text\":\"b\",\"id\":1}",
633 );
634 assert!(result.is_err());
635 }
636}