use std::{
borrow::Cow,
collections::{BTreeMap, BTreeSet, HashMap, HashSet, VecDeque},
rc::Rc,
sync::Arc,
};
use chrono::{DateTime, TimeZone};
use serde_json::{Value, json};
#[cfg(test)]
mod tests;
#[diagnostic::on_unimplemented(
message = "`{Self}` does not describe its JSON schema",
note = "derive `Schema` for it, or implement `Schema` by hand"
)]
pub trait Schema {
#[must_use]
fn schema() -> Value;
#[must_use]
fn schema_without_null() -> Value {
Self::schema()
}
}
macro_rules! described_as {
($schema:tt => $($described:ty),+ $(,)?) => {
$(
impl Schema for $described {
fn schema() -> Value {
json!($schema)
}
}
)+
};
}
described_as!({ "type": "boolean" } => bool);
described_as!({ "type": "string" } => String, str, char);
described_as!({ "type": "integer" } => i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize);
described_as!({ "type": "number" } => f32, f64, serde_json::Number);
described_as!({} => Value);
impl<Tz: TimeZone> Schema for DateTime<Tz> {
fn schema() -> Value {
json!({ "type": "string", "format": "date-time" })
}
}
impl<T: Schema> Schema for Option<T> {
fn schema() -> Value {
json!({ "anyOf": [T::schema(), { "type": "null" }] })
}
fn schema_without_null() -> Value {
T::schema()
}
}
fn array_of<T: Schema>() -> Value {
json!({ "type": "array", "items": T::schema() })
}
macro_rules! described_as_array {
($($collection:ident),+) => {
$(
impl<T: Schema> Schema for $collection<T> {
fn schema() -> Value {
array_of::<T>()
}
}
)+
};
}
described_as_array!(Vec, VecDeque, BTreeSet);
impl<T: Schema, S> Schema for HashSet<T, S> {
fn schema() -> Value {
array_of::<T>()
}
}
impl<T: Schema> Schema for [T] {
fn schema() -> Value {
array_of::<T>()
}
}
impl<T: Schema, const N: usize> Schema for [T; N] {
fn schema() -> Value {
json!({ "type": "array", "items": T::schema(), "minItems": N, "maxItems": N })
}
}
fn map_of<V: Schema>() -> Value {
json!({ "type": "object", "additionalProperties": V::schema() })
}
impl<K, V: Schema, S> Schema for HashMap<K, V, S> {
fn schema() -> Value {
map_of::<V>()
}
}
impl<K, V: Schema> Schema for BTreeMap<K, V> {
fn schema() -> Value {
map_of::<V>()
}
}
impl Schema for serde_json::Map<String, Value> {
fn schema() -> Value {
json!({ "type": "object", "additionalProperties": {} })
}
}
macro_rules! described_like_what_it_holds {
($($holder:ident),+) => {
$(
impl<T: Schema + ?Sized> Schema for $holder<T> {
fn schema() -> Value {
T::schema()
}
fn schema_without_null() -> Value {
T::schema_without_null()
}
}
)+
};
}
described_like_what_it_holds!(Box, Rc, Arc);
impl<T: Schema + ?Sized> Schema for &T {
fn schema() -> Value {
T::schema()
}
fn schema_without_null() -> Value {
T::schema_without_null()
}
}
impl<T: Schema + ToOwned + ?Sized> Schema for Cow<'_, T> {
fn schema() -> Value {
T::schema()
}
fn schema_without_null() -> Value {
T::schema_without_null()
}
}