use super::{missing, unexpected};
use crate::codes;
use crate::decoder::Decoder;
use crate::issue::{Issue, Issues};
use crate::path::Path;
use crate::presence::Presence;
use serde_json::{Map, Value};
use std::borrow::Cow;
mod sealed {
pub trait Sealed {}
}
pub fn object<F: FieldSet>(fields: F) -> Object<F> {
Object(fields)
}
#[derive(Clone, Copy, Debug)]
pub struct Object<F>(F);
impl<F: FieldSet> Decoder<Value> for Object<F> {
type Output = F::Output;
fn decode_at(&self, input: &Value, path: &Path<'_>) -> Result<F::Output, Issues> {
match input {
Value::Object(members) => self.0.decode_fields(members, path),
other => Err(unexpected(path, "object", other).into()),
}
}
}
impl<F: FieldSet> Object<F> {
pub fn strict(self) -> Strict<F> {
Strict(self.0)
}
}
#[derive(Clone, Copy, Debug)]
pub struct Strict<F>(F);
impl<F: FieldSet> Decoder<Value> for Strict<F> {
type Output = F::Output;
fn decode_at(&self, input: &Value, path: &Path<'_>) -> Result<F::Output, Issues> {
let Value::Object(members) = input else {
return Err(unexpected(path, "object", input).into());
};
let result = self.0.decode_fields(members, path);
let mut declared = Vec::new();
self.0.field_names(&mut declared);
let unknown: Issues = members
.keys()
.filter(|name| !declared.contains(&name.as_str()))
.map(|name| {
Issue::at_path(&path.key(name), codes::UNKNOWN_FIELD)
.with_meta("field", name.clone())
})
.collect();
match result {
Ok(value) if unknown.is_empty() => Ok(value),
Ok(_) => Err(unknown),
Err(mut issues) => {
issues.merge(unknown);
Err(issues)
}
}
}
}
pub trait FieldSet: sealed::Sealed {
type Output;
#[doc(hidden)]
fn decode_fields(
&self,
members: &Map<String, Value>,
path: &Path<'_>,
) -> Result<Self::Output, Issues>;
#[doc(hidden)]
fn field_names<'a>(&'a self, names: &mut Vec<&'a str>);
}
pub fn field<D>(name: impl Into<Cow<'static, str>>, decoder: D) -> Field<D> {
Field {
name: name.into(),
decoder,
}
}
#[derive(Clone, Debug)]
pub struct Field<D> {
name: Cow<'static, str>,
decoder: D,
}
impl<D> sealed::Sealed for Field<D> {}
impl<D: Decoder<Value>> FieldSet for Field<D> {
type Output = D::Output;
fn decode_fields(
&self,
members: &Map<String, Value>,
path: &Path<'_>,
) -> Result<D::Output, Issues> {
let member = members.get(self.name.as_ref()).unwrap_or(missing());
self.decoder.decode_at(member, &path.key(&self.name))
}
fn field_names<'a>(&'a self, names: &mut Vec<&'a str>) {
names.push(&self.name);
}
}
pub fn optional_field<D>(name: impl Into<Cow<'static, str>>, decoder: D) -> OptionalField<D> {
OptionalField {
name: name.into(),
decoder,
}
}
#[derive(Clone, Debug)]
pub struct OptionalField<D> {
name: Cow<'static, str>,
decoder: D,
}
impl<D> sealed::Sealed for OptionalField<D> {}
impl<D: Decoder<Value>> FieldSet for OptionalField<D> {
type Output = Option<D::Output>;
fn decode_fields(
&self,
members: &Map<String, Value>,
path: &Path<'_>,
) -> Result<Self::Output, Issues> {
members
.get(self.name.as_ref())
.map(|member| self.decoder.decode_at(member, &path.key(&self.name)))
.transpose()
}
fn field_names<'a>(&'a self, names: &mut Vec<&'a str>) {
names.push(&self.name);
}
}
pub fn presence_field<D>(name: impl Into<Cow<'static, str>>, decoder: D) -> PresenceField<D> {
PresenceField {
name: name.into(),
decoder,
}
}
#[derive(Clone, Debug)]
pub struct PresenceField<D> {
name: Cow<'static, str>,
decoder: D,
}
impl<D> sealed::Sealed for PresenceField<D> {}
impl<D: Decoder<Value>> FieldSet for PresenceField<D> {
type Output = Presence<D::Output>;
fn decode_fields(
&self,
members: &Map<String, Value>,
path: &Path<'_>,
) -> Result<Self::Output, Issues> {
match members.get(self.name.as_ref()) {
None => Ok(Presence::Absent),
Some(Value::Null) => Ok(Presence::Null),
Some(member) => self
.decoder
.decode_at(member, &path.key(&self.name))
.map(Presence::Present),
}
}
fn field_names<'a>(&'a self, names: &mut Vec<&'a str>) {
names.push(&self.name);
}
}
macro_rules! tuple_field_set {
($($T:ident $v:ident $idx:tt),+) => {
impl<$($T: FieldSet),+> sealed::Sealed for ($($T,)+) {}
impl<$($T: FieldSet),+> FieldSet for ($($T,)+) {
type Output = ($($T::Output,)+);
fn decode_fields(
&self,
members: &Map<String, Value>,
path: &Path<'_>,
) -> Result<Self::Output, Issues> {
let mut issues = Issues::new();
$(
let $v = match self.$idx.decode_fields(members, path) {
Ok(value) => Some(value),
Err(found) => {
issues.merge(found);
None
}
};
)+
match ($($v,)+) {
($(Some($v),)+) => Ok(($($v,)+)),
_ => Err(issues),
}
}
fn field_names<'a>(&'a self, names: &mut Vec<&'a str>) {
$( self.$idx.field_names(names); )+
}
}
};
}
tuple_field_set!(A a 0);
tuple_field_set!(A a 0, B b 1);
tuple_field_set!(A a 0, B b 1, C c 2);
tuple_field_set!(A a 0, B b 1, C c 2, D d 3);
tuple_field_set!(A a 0, B b 1, C c 2, D d 3, E e 4);
tuple_field_set!(A a 0, B b 1, C c 2, D d 3, E e 4, F f 5);
tuple_field_set!(A a 0, B b 1, C c 2, D d 3, E e 4, F f 5, G g 6);
tuple_field_set!(A a 0, B b 1, C c 2, D d 3, E e 4, F f 5, G g 6, H h 7);
tuple_field_set!(A a 0, B b 1, C c 2, D d 3, E e 4, F f 5, G g 6, H h 7, J j 8);
tuple_field_set!(A a 0, B b 1, C c 2, D d 3, E e 4, F f 5, G g 6, H h 7, J j 8, K k 9);
tuple_field_set!(A a 0, B b 1, C c 2, D d 3, E e 4, F f 5, G g 6, H h 7, J j 8, K k 9, L l 10);
tuple_field_set!(A a 0, B b 1, C c 2, D d 3, E e 4, F f 5, G g 6, H h 7, J j 8, K k 9, L l 10, M m 11);
tuple_field_set!(A a 0, B b 1, C c 2, D d 3, E e 4, F f 5, G g 6, H h 7, J j 8, K k 9, L l 10, M m 11, N n 12);
tuple_field_set!(A a 0, B b 1, C c 2, D d 3, E e 4, F f 5, G g 6, H h 7, J j 8, K k 9, L l 10, M m 11, N n 12, O o 13);
tuple_field_set!(A a 0, B b 1, C c 2, D d 3, E e 4, F f 5, G g 6, H h 7, J j 8, K k 9, L l 10, M m 11, N n 12, O o 13, P p 14);
tuple_field_set!(A a 0, B b 1, C c 2, D d 3, E e 4, F f 5, G g 6, H h 7, J j 8, K k 9, L l 10, M m 11, N n 12, O o 13, P p 14, Q q 15);
#[cfg(test)]
mod tests {
use super::*;
use crate::json::{JsonDecoderExt, i64, string};
use serde_json::json;
fn paths(issues: &Issues) -> Vec<String> {
issues.iter().map(|i| i.path().to_string()).collect()
}
#[test]
fn a_missing_field_is_required_at_its_path() {
let issues = object((field("name", string()),))
.decode(&json!({}))
.unwrap_err();
let issue = issues.iter().next().unwrap();
assert_eq!(issue.code(), "required");
assert_eq!(issue.path().to_string(), "/name");
}
#[test]
fn a_non_object_is_one_issue_at_the_object_path() {
let decoder = object((field("a", i64()), field("b", i64())));
let issues = decoder.decode(&json!([1])).unwrap_err();
assert_eq!(paths(&issues), [""]);
let issue = issues.iter().next().unwrap();
assert_eq!(issue.code(), "type_mismatch");
assert_eq!(issue.meta()["expected"], "object");
assert_eq!(issue.meta()["actual"], "array");
let issues = decoder.decode(&Value::Null).unwrap_err();
assert_eq!(issues.iter().next().unwrap().code(), "required");
}
#[test]
fn optional_and_presence_fields_do_not_accept_a_non_object() {
for input in [json!("x"), json!(1), json!([]), Value::Null] {
assert!(
object((optional_field("n", string()),))
.decode(&input)
.is_err(),
"{input}"
);
assert!(
object((presence_field("n", string()),))
.decode(&input)
.is_err(),
"{input}"
);
assert!(
object((optional_field("n", string()),))
.strict()
.decode(&input)
.is_err(),
"{input}"
);
}
}
#[test]
fn nested_paths_join() {
let item = object((field("name", string()),));
let decoder = object((field("items", item.list()),));
let issues = decoder
.decode(&json!({"items": [{"name": "a"}, {}]}))
.unwrap_err();
assert_eq!(paths(&issues), ["/items/1/name"]);
}
#[test]
fn keys_holding_slash_and_tilde_are_escaped() {
let decoder = object((field("a/b", i64()), field("", i64())));
let issues = decoder.decode(&json!({})).unwrap_err();
assert_eq!(paths(&issues), ["/a~1b", "/"]);
}
#[test]
fn optional_field_gives_none_when_missing_and_requires_non_null() {
let decoder = object((optional_field("nick", string()),));
assert_eq!(decoder.decode(&json!({})).unwrap(), (None,));
let issues = decoder.decode(&json!({"nick": null})).unwrap_err();
assert_eq!(issues.iter().next().unwrap().code(), "required");
}
#[test]
fn presence_field_tells_the_three_apart() {
let decoder = object((presence_field("n", i64()),));
assert_eq!(decoder.decode(&json!({})).unwrap(), (Presence::Absent,));
assert_eq!(
decoder.decode(&json!({"n": null})).unwrap(),
(Presence::Null,)
);
assert_eq!(
decoder.decode(&json!({"n": 1})).unwrap(),
(Presence::Present(1),)
);
}
#[test]
fn strict_reports_every_unknown_member_after_the_fields_issues() {
let decoder = object((field("a", i64()),)).strict();
let issues = decoder
.decode(&json!({"a": "x", "b": 2, "c": 3}))
.unwrap_err();
assert_eq!(paths(&issues), ["/a", "/b", "/c"]);
assert_eq!(issues.iter().nth(1).unwrap().meta()["field"], "b");
}
}