use std::fmt;
use serde_json::{Map, Number, Value};
pub type Object = Map<String, Value>;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DecodeErrorKind {
UnknownProperty,
MissingProperty,
WrongType {
expected: &'static str,
},
WrongCardinality,
Empty,
BadNumber,
BadValue,
DuplicateChoice,
ResourceType,
}
impl fmt::Display for DecodeErrorKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::UnknownProperty => f.write_str("unknown property"),
Self::MissingProperty => f.write_str("required property is missing"),
Self::WrongType { expected } => write!(f, "expected {expected}"),
Self::WrongCardinality => f.write_str("cardinality mismatch"),
Self::Empty => f.write_str("empty object or array"),
Self::BadNumber => f.write_str("number out of range"),
Self::BadValue => f.write_str("invalid value"),
Self::DuplicateChoice => f.write_str("more than one form of a choice element"),
Self::ResourceType => f.write_str("resourceType is missing or does not match"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DecodeError {
pub path: String,
pub kind: DecodeErrorKind,
}
impl fmt::Display for DecodeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}: {}", self.path, self.kind)
}
}
impl std::error::Error for DecodeError {}
#[derive(Debug, Default, Clone)]
pub struct Path {
segments: Vec<String>,
}
impl Path {
#[must_use]
pub fn root(root: &str) -> Self {
Self {
segments: vec![root.to_owned()],
}
}
pub fn with<T>(
&mut self,
name: &str,
f: impl FnOnce(&mut Self) -> Result<T, DecodeError>,
) -> Result<T, DecodeError> {
self.segments.push(name.to_owned());
let out = f(self);
self.segments.pop();
out
}
pub fn with_index<T>(
&mut self,
name: &str,
index: usize,
f: impl FnOnce(&mut Self) -> Result<T, DecodeError>,
) -> Result<T, DecodeError> {
self.segments.push(format!("{name}[{index}]"));
let out = f(self);
self.segments.pop();
out
}
#[must_use]
pub fn error(&self, kind: DecodeErrorKind) -> DecodeError {
DecodeError {
path: self.segments.join("."),
kind,
}
}
}
pub trait Json: Sized {
fn to_json(&self) -> Result<Object, EncodeError>;
fn from_json(object: &Object, path: &mut Path) -> Result<Self, DecodeError>;
}
pub trait Primitive: Sized {
fn value_json(&self) -> Result<Option<Value>, EncodeError>;
fn element_json(&self) -> Result<Option<Value>, EncodeError>;
fn from_json_parts(
value: Option<&Value>,
element: Option<&Value>,
path: &mut Path,
) -> Result<Self, DecodeError>;
}
pub fn expect_object<'a>(value: &'a Value, path: &Path) -> Result<&'a Object, DecodeError> {
match value {
Value::Object(object) if object.is_empty() => Err(path.error(DecodeErrorKind::Empty)),
Value::Object(object) => Ok(object),
_ => Err(path.error(DecodeErrorKind::WrongType {
expected: "an object",
})),
}
}
pub fn expect_array<'a>(value: &'a Value, path: &Path) -> Result<&'a [Value], DecodeError> {
match value {
Value::Array(items) if items.is_empty() => Err(path.error(DecodeErrorKind::Empty)),
Value::Array(items) => Ok(items),
_ => Err(path.error(DecodeErrorKind::WrongType {
expected: "an array",
})),
}
}
pub fn expect_single<'a>(value: &'a Value, path: &Path) -> Result<&'a Value, DecodeError> {
match value {
Value::Array(_) => Err(path.error(DecodeErrorKind::WrongCardinality)),
other => Ok(other),
}
}
pub fn expect_string(value: &Value, path: &Path) -> Result<String, DecodeError> {
match value {
Value::String(s) => Ok(s.clone()),
_ => Err(path.error(DecodeErrorKind::WrongType {
expected: "a string",
})),
}
}
pub fn expect_bool(value: &Value, path: &Path) -> Result<bool, DecodeError> {
match value {
Value::Bool(b) => Ok(*b),
_ => Err(path.error(DecodeErrorKind::WrongType {
expected: "a boolean",
})),
}
}
pub fn expect_i32(value: &Value, path: &Path) -> Result<i32, DecodeError> {
match value {
Value::Number(n) => n
.as_i64()
.and_then(|i| i32::try_from(i).ok())
.ok_or_else(|| path.error(DecodeErrorKind::BadNumber)),
_ => Err(path.error(DecodeErrorKind::WrongType {
expected: "a number",
})),
}
}
pub fn expect_u32(value: &Value, min: u32, path: &Path) -> Result<u32, DecodeError> {
match value {
Value::Number(n) => n
.as_u64()
.and_then(|u| u32::try_from(u).ok())
.filter(|u| *u >= min)
.ok_or_else(|| path.error(DecodeErrorKind::BadNumber)),
_ => Err(path.error(DecodeErrorKind::WrongType {
expected: "a number",
})),
}
}
pub fn expect_i64_string(value: &Value, path: &Path) -> Result<i64, DecodeError> {
match value {
Value::String(s) => s
.parse()
.map_err(|_| path.error(DecodeErrorKind::BadNumber)),
_ => Err(path.error(DecodeErrorKind::WrongType {
expected: "a string holding an integer",
})),
}
}
pub fn expect_decimal(value: &Value, path: &Path) -> Result<String, DecodeError> {
match value {
Value::Number(n) => Ok(n.to_string()),
_ => Err(path.error(DecodeErrorKind::WrongType {
expected: "a number",
})),
}
}
pub fn decimal_value(text: &str, path: &Path) -> Result<Value, DecodeError> {
text.parse::<Number>()
.map(Value::Number)
.map_err(|_| path.error(DecodeErrorKind::BadValue))
}
pub type ValuePair<'a> = (Option<&'a Value>, Option<&'a Value>);
pub fn pair_arrays<'a>(
values: Option<&'a Value>,
elements: Option<&'a Value>,
path: &Path,
) -> Result<Vec<ValuePair<'a>>, DecodeError> {
let values = values.map(|v| expect_array(v, path)).transpose()?;
let elements = elements.map(|v| expect_array(v, path)).transpose()?;
let len = match (values, elements) {
(Some(v), Some(e)) if v.len() != e.len() => {
return Err(path.error(DecodeErrorKind::WrongCardinality));
}
(Some(v), _) => v.len(),
(None, Some(e)) => e.len(),
(None, None) => 0,
};
Ok((0..len)
.map(|i| {
let value = values.and_then(|v| v.get(i)).filter(|v| !v.is_null());
let element = elements.and_then(|e| e.get(i)).filter(|e| !e.is_null());
(value, element)
})
.collect())
}
pub fn primitive_arrays<P: Primitive>(items: &[P]) -> Result<(Value, Option<Value>), EncodeError> {
let mut values = Vec::with_capacity(items.len());
let mut elements = Vec::with_capacity(items.len());
for item in items {
values.push(item.value_json()?.unwrap_or(Value::Null));
elements.push(item.element_json()?.unwrap_or(Value::Null));
}
let any_element = elements.iter().any(|e| !e.is_null());
Ok((
Value::Array(values),
any_element.then_some(Value::Array(elements)),
))
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum EncodeError {
BadDecimal {
text: String,
},
UnknownResourceBody,
}
impl fmt::Display for EncodeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::BadDecimal { text } => write!(f, "decimal {text:?} is not a JSON number"),
Self::UnknownResourceBody => f.write_str("an unknown resource's body is not an object"),
}
}
}
impl std::error::Error for EncodeError {}
#[derive(Debug, Default)]
pub struct ChoiceSlot<'a> {
pub suffix: Option<&'static str>,
pub value: Option<&'a Value>,
pub element: Option<&'a Value>,
}
impl<'a> ChoiceSlot<'a> {
fn claim(&mut self, suffix: &'static str, path: &Path) -> Result<(), DecodeError> {
match self.suffix {
Some(seen) if seen != suffix => Err(path.error(DecodeErrorKind::DuplicateChoice)),
_ => {
self.suffix = Some(suffix);
Ok(())
}
}
}
pub fn value(
&mut self,
suffix: &'static str,
value: &'a Value,
path: &Path,
) -> Result<(), DecodeError> {
self.claim(suffix, path)?;
self.value = Some(value);
Ok(())
}
pub fn element(
&mut self,
suffix: &'static str,
element: &'a Value,
path: &Path,
) -> Result<(), DecodeError> {
self.claim(suffix, path)?;
self.element = Some(element);
Ok(())
}
}
pub fn resource_type<'a>(object: &'a Object, path: &Path) -> Result<&'a str, DecodeError> {
match object.get("resourceType") {
Some(Value::String(name)) => Ok(name),
_ => Err(path.error(DecodeErrorKind::ResourceType)),
}
}