use serde::{de::DeserializeOwned, Deserialize, Deserializer, Serialize, Serializer};
use serde_json::Value;
use std::collections::BTreeMap;
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub enum Presence<T> {
#[default]
Missing,
Present(T),
}
impl<T> Presence<T> {
pub fn is_missing(&self) -> bool {
matches!(self, Self::Missing)
}
pub fn as_ref(&self) -> Presence<&T> {
match self {
Self::Missing => Presence::Missing,
Self::Present(value) => Presence::Present(value),
}
}
}
impl<T: Serialize> Serialize for Presence<T> {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
match self {
Self::Present(value) => value.serialize(serializer),
Self::Missing => Err(serde::ser::Error::custom(
"a missing field must be omitted by its containing object",
)),
}
}
}
impl<'de, T: Deserialize<'de>> Deserialize<'de> for Presence<T> {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
T::deserialize(deserializer).map(Self::Present)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Nullable<T> {
Null,
Value(T),
}
impl<T: Serialize> Serialize for Nullable<T> {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
match self {
Self::Null => serializer.serialize_none(),
Self::Value(value) => value.serialize(serializer),
}
}
}
impl<'de, T: DeserializeOwned> Deserialize<'de> for Nullable<T> {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let value = Value::deserialize(deserializer)?;
if value.is_null() {
Ok(Self::Null)
} else {
serde_json::from_value(value)
.map(Self::Value)
.map_err(serde::de::Error::custom)
}
}
}
pub fn required<'de, T: Deserialize<'de>, D: Deserializer<'de>>(
deserializer: D,
) -> Result<T, D::Error> {
T::deserialize(deserializer)
}
pub trait Shape {
fn matches(value: &Value) -> bool;
}
impl Shape for Value {
fn matches(_: &Value) -> bool {
true
}
}
impl Shape for String {
fn matches(value: &Value) -> bool {
value.is_string()
}
}
impl Shape for bool {
fn matches(value: &Value) -> bool {
value.is_boolean()
}
}
impl<T: Shape> Shape for Vec<T> {
fn matches(value: &Value) -> bool {
value
.as_array()
.is_some_and(|items| items.iter().all(T::matches))
}
}
impl<T: Shape> Shape for BTreeMap<String, T> {
fn matches(value: &Value) -> bool {
value
.as_object()
.is_some_and(|items| items.values().all(T::matches))
}
}
impl<T: Shape> Shape for Box<T> {
fn matches(value: &Value) -> bool {
T::matches(value)
}
}
impl<T: Shape> Shape for Nullable<T> {
fn matches(value: &Value) -> bool {
value.is_null() || T::matches(value)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Null;
impl Serialize for Null {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_none()
}
}
impl<'de> Deserialize<'de> for Null {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
<()>::deserialize(deserializer).map(|()| Self)
}
}
impl Shape for Null {
fn matches(value: &Value) -> bool {
value.is_null()
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct Integer(i64);
impl Integer {
pub const MAX: i64 = 9_007_199_254_740_991;
pub fn new(value: i64) -> Result<Self, &'static str> {
if (-Self::MAX..=Self::MAX).contains(&value) {
Ok(Self(value))
} else {
Err("integer exceeds the SuperDoc host's exact JSON integer range")
}
}
pub fn get(self) -> i64 {
self.0
}
}
impl TryFrom<i64> for Integer {
type Error = &'static str;
fn try_from(value: i64) -> Result<Self, Self::Error> {
Self::new(value)
}
}
impl Serialize for Integer {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_i64(self.0)
}
}
impl<'de> Deserialize<'de> for Integer {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let value = Value::deserialize(deserializer)?;
let number = value
.as_f64()
.filter(|n| n.is_finite() && n.fract() == 0.0 && n.abs() <= Self::MAX as f64);
number.map(|n| Self(n as i64)).ok_or_else(|| {
serde::de::Error::custom("expected an exactly representable JSON integer")
})
}
}
impl Shape for Integer {
fn matches(value: &Value) -> bool {
serde_json::from_value::<Self>(value.clone()).is_ok()
}
}
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
pub struct Number(f64);
impl Number {
pub fn new(value: f64) -> Result<Self, &'static str> {
if value.is_finite() {
Ok(Self(value))
} else {
Err("JSON numbers must be finite")
}
}
pub fn get(self) -> f64 {
self.0
}
}
impl Serialize for Number {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_f64(self.0)
}
}
impl<'de> Deserialize<'de> for Number {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
Self::new(f64::deserialize(deserializer)?).map_err(serde::de::Error::custom)
}
}
impl Shape for Number {
fn matches(value: &Value) -> bool {
value.as_f64().is_some_and(f64::is_finite)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Never {}
impl Serialize for Never {
fn serialize<S: Serializer>(&self, _: S) -> Result<S::Ok, S::Error> {
match *self {}
}
}
impl<'de> Deserialize<'de> for Never {
fn deserialize<D: Deserializer<'de>>(_: D) -> Result<Self, D::Error> {
Err(serde::de::Error::custom("the schema accepts no values"))
}
}
impl Shape for Never {
fn matches(_: &Value) -> bool {
false
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::{from_value, json, to_value};
#[derive(Debug, Deserialize, Serialize)]
struct Fields {
#[serde(deserialize_with = "required")]
required_nullable: Nullable<String>,
#[serde(default, skip_serializing_if = "Presence::is_missing")]
optional_nullable: Presence<Nullable<String>>,
#[serde(default, skip_serializing_if = "Presence::is_missing")]
optional_text: Presence<String>,
#[serde(deserialize_with = "required")]
json: Value,
}
#[test]
fn preserves_missing_null_and_value() {
for value in [
json!({"required_nullable":null,"json":null}),
json!({"required_nullable":"é 😀","optional_nullable":null,"json":{}}),
json!({"required_nullable":null,"optional_nullable":"","optional_text":"","json":false}),
] {
let decoded: Fields = from_value(value.clone()).unwrap();
assert_eq!(to_value(decoded).unwrap(), value);
}
}
#[test]
fn required_null_fields_reject_absence() {
assert!(from_value::<Fields>(json!({"json":null})).is_err());
assert!(from_value::<Fields>(json!({"required_nullable":null})).is_err());
}
#[test]
fn optional_nonnullable_rejects_null() {
assert!(from_value::<Fields>(
json!({"required_nullable":null,"optional_text":null,"json":null})
)
.is_err());
}
#[test]
fn exact_integer_boundaries_and_fraction_rejection() {
for value in [-Integer::MAX, -1, 0, 1, Integer::MAX] {
assert_eq!(Integer::new(value).unwrap().get(), value);
assert_eq!(from_value::<Integer>(json!(value)).unwrap().get(), value);
}
for value in [
json!(Integer::MAX + 1),
json!(1.5),
json!(true),
Value::Null,
] {
assert!(from_value::<Integer>(value).is_err());
}
assert!(Integer::new(i64::MIN).is_err());
assert_eq!(from_value::<Integer>(json!(1.0)).unwrap().get(), 1);
}
#[test]
fn nonfinite_numbers_are_rejected() {
assert!(Number::new(f64::NAN).is_err());
assert!(Number::new(f64::INFINITY).is_err());
assert!(Number::new(f64::NEG_INFINITY).is_err());
assert_eq!(to_value(Number::new(0.5).unwrap()).unwrap(), json!(0.5));
}
#[test]
fn missing_values_cannot_leak_into_arrays_as_null() {
assert!(to_value(vec![Presence::<String>::Missing]).is_err());
}
}