Skip to content

Commit

Permalink
Merge pull request #666 from swlynch99/schemars
Browse files Browse the repository at this point in the history
  • Loading branch information
jonasbb committed Jan 10, 2024
2 parents 40b1bda + ff07e46 commit d2fc3a4
Show file tree
Hide file tree
Showing 16 changed files with 1,438 additions and 223 deletions.
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

0 comments on commit d2fc3a4

Please sign in to comment.