1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
#![allow(deprecated)]
//! Decoding of tagged unions: read the JSON value, look at the tag, decode only what it selects. A union
//! calls these from the `dispatch` function next to its `Deserialize`.
//!
//! Decoding goes through `serde_json::Value`, like `serde(untagged)`, whose buffer holds numbers the same
//! way: an integer up to 64 bits stays that integer, anything larger and every float is an `f64`.
//! A key that appears twice in an object is the last one, where a struct alone would reject it.
use serde::de::DeserializeOwned;
use serde_json::Value;
/// What a union finds where it looks for its tag.
pub(crate) enum Tag<'a> {
/// The tag's value.
Value(&'a str),
/// An object without the tag, or with `null` for it.
Missing,
/// Not an object at all.
NotObject,
}
/// Looks at the tag. A tag of another kind than a string is an error here, whatever the variants say.
pub(crate) fn tag<'a>(value: &'a Value, union: &str, tag: &str) -> Result<Tag<'a>, String> {
let Value::Object(fields) = value else {
return Ok(Tag::NotObject);
};
match fields.get(tag) {
None | Some(Value::Null) => Ok(Tag::Missing),
Some(Value::String(found)) => Ok(Tag::Value(found)),
Some(other) => Err(format!(
"{union}: tag `{tag}` must be a string, got {}",
kind(other)
)),
}
}
/// Decodes the one variant selected, and keeps its error.
pub(crate) fn decode<T: DeserializeOwned>(
value: Value,
union: &str,
variant: &str,
) -> Result<T, String> {
serde_json::from_value(value).map_err(|e| format!("{union}::{variant}: {e}"))
}
/// The error of a tag no variant has.
#[allow(dead_code)]
pub(crate) fn unknown_tag(union: &str, tag: &str, value: &str) -> String {
format!("{union}: unknown `{tag}` value {value:?}")
}
/// The error of no tag, when every variant requires it.
pub(crate) fn missing_tag(union: &str, tag: &str) -> String {
format!("{union}: tag `{tag}` is missing or null")
}
/// The error of something other than an object, when every variant is one.
pub(crate) fn not_an_object(value: &Value, union: &str, tag: &str) -> String {
format!(
"{union}: expected an object with tag `{tag}`, got {}",
kind(value)
)
}
fn kind(value: &Value) -> &'static str {
match value {
Value::Null => "null",
Value::Bool(_) => "a boolean",
Value::Number(_) => "a number",
Value::String(_) => "a string",
Value::Array(_) => "an array",
Value::Object(_) => "an object",
}
}