Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Include support for schemars #666

Merged
merged 18 commits into from
Jan 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
759 changes: 540 additions & 219 deletions Cargo.lock

Large diffs are not rendered by default.

15 changes: 15 additions & 0 deletions serde_with/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,13 @@ macros = ["dep:serde_with_macros"]
## This pulls in `time` v0.3 as a dependency.
## Some functionality is only available when `alloc` or `std` is enabled too.
time_0_3 = ["dep:time_0_3"]
## This feature enables integration with `schemars` 0.8.
## This makes `#[derive(JsonSchema)]` pick up the correct schema for the type
## used within `#[serde_as(as = ...)]`.
##
## This pulls in `schemars` v0.8 as a dependency. It will also implicitly enable
## the `std` feature as `schemars` is not `#[no_std]`.
schemars_0_8 = ["dep:schemars_0_8", "std", "serde_with_macros?/schemars_0_8"]

# When adding new optional dependencies update the documentation in feature-flags.md
[dependencies]
Expand All @@ -128,17 +135,20 @@ serde = {version = "1.0.152", default-features = false, features = ["derive"] }
serde_json = {version = "1.0.45", optional = true, default-features = false}
serde_with_macros = {path = "../serde_with_macros", version = "=3.4.0", optional = true}
time_0_3 = {package = "time", version = "~0.3.11", optional = true, default-features = false}
schemars_0_8 = {package = "schemars", version = "0.8.16", optional = true, default-features = false}

[dev-dependencies]
expect-test = "1.3.0"
fnv = "1.0.6"
glob = "0.3.0"
jsonschema = { version = "0.17.1", default-features = false, features = ["resolve-file"] }
mime = "0.3.16"
pretty_assertions = "1.4.0"
regex = {version = "1.9.1", default-features = false, features = ["std"]}
rmp-serde = "1.1.0"
ron = "0.8"
rustversion = "1.0.0"
schemars_0_8 = {package = "schemars", version = "0.8.16"}
serde_json = {version = "1.0.25", features = ["preserve_order"]}
serde_test = "1.0.124"
serde_yaml = "0.9.2"
Expand Down Expand Up @@ -210,6 +220,11 @@ name = "rust"
path = "tests/rust.rs"
required-features = ["alloc"]

[[test]]
name = "schemars_0_8"
path = "tests/schemars_0_8.rs"
required-features = ["schemars_0_8"]

[package.metadata.docs.rs]
all-features = true
rustdoc-args = [
Expand Down
13 changes: 13 additions & 0 deletions serde_with/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,8 @@ pub mod json;
#[cfg(feature = "alloc")]
mod key_value_map;
pub mod rust;
#[cfg(feature = "schemars_0_8")]
mod schemars_0_8;
pub mod ser;
#[cfg(feature = "std")]
mod serde_conv;
Expand Down Expand Up @@ -2512,3 +2514,14 @@ pub struct SetPreventDuplicates<T>(PhantomData<T>);
/// [`BTreeSet`]: std::collections::HashSet
#[cfg(feature = "alloc")]
pub struct SetLastValueWins<T>(PhantomData<T>);

/// Helper for implementing [`JsonSchema`] on serializers whose output depends
/// on the type of the concrete field.
///
/// It is added implicitly by the [`#[serde_as]`] macro when any `schemars`
/// feature is enabled.
///
/// [`JsonSchema`]: ::schemars_0_8::JsonSchema
/// [`#[serde_as]`]: crate::serde_as
#[cfg(feature = "schemars_0_8")]
pub struct Schema<T: ?Sized, TA>(PhantomData<T>, PhantomData<TA>);
224 changes: 224 additions & 0 deletions serde_with/src/schemars_0_8.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
//! Integration with [schemars v0.8](schemars_0_8).
//!
//! This module is only available if using the `schemars_0_8` feature of the crate.

use crate::prelude::{Schema as WrapSchema, *};
use ::schemars_0_8::{
gen::SchemaGenerator,
schema::{ArrayValidation, InstanceType, Schema, SchemaObject},
JsonSchema,
};
use std::borrow::Cow;

//===================================================================
// Macro helpers

macro_rules! forward_schema {
($fwd:ty) => {
fn schema_name() -> String {
<$fwd as JsonSchema>::schema_name()
}

fn schema_id() -> Cow<'static, str> {
<$fwd as JsonSchema>::schema_id()
}

fn json_schema(gen: &mut SchemaGenerator) -> Schema {
<$fwd as JsonSchema>::json_schema(gen)
}

fn is_referenceable() -> bool {
<$fwd as JsonSchema>::is_referenceable()
}
};
}

//===================================================================
// Common definitions for various std types

impl<'a, T: 'a, TA: 'a> JsonSchema for WrapSchema<&'a T, &'a TA>
where
T: ?Sized,
WrapSchema<T, TA>: JsonSchema,
{
forward_schema!(&'a WrapSchema<T, TA>);
}

impl<'a, T: 'a, TA: 'a> JsonSchema for WrapSchema<&'a mut T, &'a mut TA>
where
T: ?Sized,
WrapSchema<T, TA>: JsonSchema,
{
forward_schema!(&'a mut WrapSchema<T, TA>);
}

impl<T, TA> JsonSchema for WrapSchema<Option<T>, Option<TA>>
where
WrapSchema<T, TA>: JsonSchema,
{
forward_schema!(Option<WrapSchema<T, TA>>);
}

impl<T, TA> JsonSchema for WrapSchema<Box<T>, Box<TA>>
where
T: ?Sized,
WrapSchema<T, TA>: JsonSchema,
{
forward_schema!(Box<WrapSchema<T, TA>>);
}

impl<T, TA> JsonSchema for WrapSchema<Rc<T>, Rc<TA>>
where
T: ?Sized,
WrapSchema<T, TA>: JsonSchema,
{
forward_schema!(Rc<WrapSchema<T, TA>>);
}

impl<T, TA> JsonSchema for WrapSchema<Arc<T>, Arc<TA>>
where
T: ?Sized,
WrapSchema<T, TA>: JsonSchema,
{
forward_schema!(Arc<WrapSchema<T, TA>>);
}

impl<T, TA> JsonSchema for WrapSchema<Vec<T>, Vec<TA>>
where
WrapSchema<T, TA>: JsonSchema,
{
forward_schema!(Vec<WrapSchema<T, TA>>);
}

impl<T, TA> JsonSchema for WrapSchema<VecDeque<T>, VecDeque<TA>>
where
WrapSchema<T, TA>: JsonSchema,
{
forward_schema!(VecDeque<WrapSchema<T, TA>>);
}

// schemars only requires that V implement JsonSchema for BTreeMap<K, V>
impl<K, V, KA, VA> JsonSchema for WrapSchema<BTreeMap<K, V>, BTreeMap<KA, VA>>
where
WrapSchema<V, VA>: JsonSchema,
{
forward_schema!(BTreeMap<WrapSchema<K, KA>, WrapSchema<V, VA>>);
}

// schemars only requires that V implement JsonSchema for HashMap<K, V>
impl<K, V, S, KA, VA> JsonSchema for WrapSchema<HashMap<K, V, S>, HashMap<KA, VA, S>>
where
WrapSchema<V, VA>: JsonSchema,
{
forward_schema!(HashMap<WrapSchema<K, KA>, WrapSchema<V, VA>, S>);
}

impl<T, TA> JsonSchema for WrapSchema<BTreeSet<T>, BTreeSet<TA>>
where
WrapSchema<T, TA>: JsonSchema,
{
forward_schema!(BTreeSet<WrapSchema<T, TA>>);
}

impl<T, TA, H> JsonSchema for WrapSchema<HashSet<T, H>, HashSet<TA, H>>
where
WrapSchema<T, TA>: JsonSchema,
{
forward_schema!(HashSet<WrapSchema<T, TA>, H>);
}

impl<T, TA, const N: usize> JsonSchema for WrapSchema<[T; N], [TA; N]>
where
WrapSchema<T, TA>: JsonSchema,
{
fn schema_name() -> String {
std::format!("[{}; {}]", <WrapSchema<T, TA>>::schema_name(), N)
}

fn schema_id() -> Cow<'static, str> {
std::format!("[{}; {}]", <WrapSchema<T, TA>>::schema_id(), N).into()
}

fn json_schema(gen: &mut SchemaGenerator) -> Schema {
let (max, min) = match N.try_into() {
Ok(len) => (Some(len), Some(len)),
Err(_) => (None, Some(u32::MAX)),
};

SchemaObject {
instance_type: Some(InstanceType::Array.into()),
array: Some(Box::new(ArrayValidation {
items: Some(gen.subschema_for::<WrapSchema<T, TA>>().into()),
max_items: max,
min_items: min,
..Default::default()
})),
..Default::default()
}
.into()
}

fn is_referenceable() -> bool {
false
}
}

macro_rules! schema_for_tuple {
(
( $( $ts:ident )+ )
( $( $as:ident )+ )
) => {
impl<$($ts,)+ $($as,)+> JsonSchema for WrapSchema<($($ts,)+), ($($as,)+)>
where
$( WrapSchema<$ts, $as>: JsonSchema, )+
{
forward_schema!(( $( WrapSchema<$ts, $as>, )+ ));
}
}
}

impl JsonSchema for WrapSchema<(), ()> {
forward_schema!(());
}

// schemars only implements JsonSchema for tuples up to 15 elements so we do
// the same here.
schema_for_tuple!((T0)(A0));
schema_for_tuple!((T0 T1) (A0 A1));
schema_for_tuple!((T0 T1 T2) (A0 A1 A2));
schema_for_tuple!((T0 T1 T2 T3) (A0 A1 A2 A3));
schema_for_tuple!((T0 T1 T2 T3 T4) (A0 A1 A2 A3 A4));
schema_for_tuple!((T0 T1 T2 T3 T4 T5) (A0 A1 A2 A3 A4 A5));
schema_for_tuple!((T0 T1 T2 T3 T4 T5 T6) (A0 A1 A2 A3 A4 A5 A6));
schema_for_tuple!((T0 T1 T2 T3 T4 T5 T6 T7) (A0 A1 A2 A3 A4 A5 A6 A7));
schema_for_tuple!((T0 T1 T2 T3 T4 T5 T6 T7 T8) (A0 A1 A2 A3 A4 A5 A6 A7 A8));
schema_for_tuple!((T0 T1 T2 T3 T4 T5 T6 T7 T8 T9) (A0 A1 A2 A3 A4 A5 A6 A7 A8 A9));
schema_for_tuple!((T0 T1 T2 T3 T4 T5 T6 T7 T8 T9 T10) (A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 A10));
schema_for_tuple!((T0 T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11) (A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 A10 A11));
schema_for_tuple!(
(T0 T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12)
(A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 A10 A11 A12)
);
schema_for_tuple!(
(T0 T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12 T13)
(A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 A10 A11 A12 A13)
);
schema_for_tuple!(
(T0 T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12 T13 T14)
(A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 A10 A11 A12 A13 A14)
);
schema_for_tuple!(
(T0 T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12 T13 T14 T15)
(A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 A10 A11 A12 A13 A14 A15)
);

//===================================================================
// Impls for serde_with types.

impl<T: JsonSchema> JsonSchema for WrapSchema<T, Same> {
forward_schema!(T);
}

impl<T> JsonSchema for WrapSchema<T, DisplayFromStr> {
forward_schema!(String);
}
Loading