use serde::de::DeserializeOwned;
use serde_json::Value;
pub(crate) enum Tag<'a> {
Value(&'a str),
Missing,
NotObject,
}
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)
)),
}
}
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}"))
}
pub(crate) type Attempt<U> = (&'static str, fn(Value) -> Result<U, serde_json::Error>);
pub(crate) fn first_of<U>(
mut value: Value,
union: &str,
tag: &str,
attempts: &[Attempt<U>],
) -> Result<U, String> {
let found = match value.get(tag) {
Some(Value::String(found)) => format!("`{tag}` {found:?}"),
_ if value.is_object() => format!("no `{tag}`"),
_ => kind(&value).to_owned(),
};
let mut errors = Vec::with_capacity(attempts.len());
for (index, (variant, decode)) in attempts.iter().enumerate() {
let input = if index + 1 == attempts.len() {
std::mem::take(&mut value)
} else {
value.clone()
};
match decode(input) {
Ok(decoded) => return Ok(decoded),
Err(e) => errors.push(format!("{variant}: {e}")),
}
}
Err(format!(
"{union}: no variant decodes {found}: {}",
errors.join("; ")
))
}
#[allow(dead_code)]
pub(crate) fn unknown_tag(union: &str, tag: &str, value: &str) -> String {
format!("{union}: unknown `{tag}` value {value:?}")
}
pub(crate) fn missing_tag(union: &str, tag: &str) -> String {
format!("{union}: tag `{tag}` is missing or null")
}
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",
}
}