diff --git a/serde_with/src/lib.rs b/serde_with/src/lib.rs index 12d180a9..068b5e06 100644 --- a/serde_with/src/lib.rs +++ b/serde_with/src/lib.rs @@ -331,7 +331,7 @@ pub mod json; mod key_value_map; pub mod rust; #[cfg(feature = "schemars_0_8")] -mod schemars_0_8; +pub mod schemars_0_8; pub mod ser; #[cfg(feature = "std")] mod serde_conv; diff --git a/serde_with/src/schemars_0_8.rs b/serde_with/src/schemars_0_8.rs index ecafb294..a175edc6 100644 --- a/serde_with/src/schemars_0_8.rs +++ b/serde_with/src/schemars_0_8.rs @@ -1,6 +1,9 @@ //! Integration with [schemars v0.8](schemars_0_8). //! //! This module is only available if using the `schemars_0_8` feature of the crate. +//! +//! If you would like to add support for schemars to your own serde_with helpers +//! see [`JsonSchemaAs`]. use crate::{ formats::Separator, @@ -13,6 +16,139 @@ use ::schemars_0_8::{ }; use std::borrow::Cow; +//=================================================================== +// Trait Definition + +/// A type which can be described as a JSON schema document. +/// +/// This trait is as [`SerializeAs`] is to [`Serialize`] but for [`JsonSchema`]. +/// You can use it to make your custom [`SerializeAs`] and [`DeserializeAs`] +/// types also support being described via JSON schemas. +/// +/// It is used by the [`Schema`][1] type in order to implement [`JsonSchema`] +/// for the relevant types. [`Schema`][1] is used implicitly by the [`serde_as`] +/// macro to instruct `schemars` on how to generate JSON schemas for fields +/// annotated with `#[serde_as(as = "...")]` attributes. +/// +/// # Examples +/// Suppose we have our very own `PositiveInt` type. Then we could add support +/// for generating a schema from it like this +/// +/// ``` +/// # extern crate schemars_0_8 as schemars; +/// # use serde::{Serialize, Serializer, Deserialize, Deserializer}; +/// # use serde_with::{SerializeAs, DeserializeAs}; +/// use serde_with::schemars_0_8::JsonSchemaAs; +/// use schemars::gen::SchemaGenerator; +/// use schemars::schema::Schema; +/// use schemars::JsonSchema; +/// +/// struct PositiveInt; +/// +/// impl SerializeAs for PositiveInt { +/// // ... +/// # fn serialize_as(&value: &i32, ser: S) -> Result +/// # where +/// # S: Serializer +/// # { +/// # if value < 0 { +/// # return Err(serde::ser::Error::custom( +/// # "expected a positive integer value, got a negative one" +/// # )); +/// # } +/// # +/// # value.serialize(ser) +/// # } +/// } +/// +/// impl<'de> DeserializeAs<'de, i32> for PositiveInt { +/// // ... +/// # fn deserialize_as(de: D) -> Result +/// # where +/// # D: Deserializer<'de>, +/// # { +/// # match i32::deserialize(de) { +/// # Ok(value) if value < 0 => Err(serde::de::Error::custom( +/// # "expected a positive integer value, got a negative one" +/// # )), +/// # value => value +/// # } +/// # } +/// } +/// +/// impl JsonSchemaAs for PositiveInt { +/// fn schema_name() -> String { +/// "PositiveInt".into() +/// } +/// +/// fn json_schema(gen: &mut SchemaGenerator) -> Schema { +/// let mut schema = ::json_schema(gen).into_object(); +/// schema.number().minimum = Some(0.0); +/// schema.into() +/// } +/// } +/// ``` +/// +/// [0]: crate::serde_as +/// [1]: crate::Schema +pub trait JsonSchemaAs { + /// Whether JSON Schemas generated for this type should be re-used where possible using the `$ref` keyword. + /// + /// For trivial types (such as primitives), this should return `false`. For more complex types, it should return `true`. + /// For recursive types, this **must** return `true` to prevent infinite cycles when generating schemas. + /// + /// By default, this returns `true`. + fn is_referenceable() -> bool { + true + } + + /// The name of the generated JSON Schema. + /// + /// This is used as the title for root schemas, and the key within the root's `definitions` property for subschemas. + fn schema_name() -> String; + + /// Returns a string that uniquely identifies the schema produced by this type. + /// + /// This does not have to be a human-readable string, and the value will not itself be included in generated schemas. + /// If two types produce different schemas, then they **must** have different `schema_id()`s, + /// but two types that produce identical schemas should *ideally* have the same `schema_id()`. + /// + /// The default implementation returns the same value as `schema_name()`. + fn schema_id() -> Cow<'static, str> { + Cow::Owned(Self::schema_name()) + } + + /// Generates a JSON Schema for this type. + /// + /// If the returned schema depends on any [referenceable](JsonSchema::is_referenceable) schemas, then this method will + /// add them to the [`SchemaGenerator`](gen::SchemaGenerator)'s schema definitions. + /// + /// This should not return a `$ref` schema. + fn json_schema(gen: &mut SchemaGenerator) -> Schema; +} + +impl JsonSchema for WrapSchema +where + T: ?Sized, + TA: JsonSchemaAs, +{ + fn schema_name() -> String { + TA::schema_name() + } + + fn schema_id() -> Cow<'static, str> { + TA::schema_id() + } + + fn json_schema(gen: &mut SchemaGenerator) -> Schema { + TA::json_schema(gen) + } + + fn is_referenceable() -> bool { + TA::is_referenceable() + } +} + //=================================================================== // Macro helpers @@ -39,100 +175,100 @@ macro_rules! forward_schema { //=================================================================== // Common definitions for various std types -impl<'a, T: 'a, TA: 'a> JsonSchema for WrapSchema<&'a T, &'a TA> +impl<'a, T: 'a, TA: 'a> JsonSchemaAs<&'a T> for &'a TA where T: ?Sized, - WrapSchema: JsonSchema, + TA: JsonSchemaAs, { forward_schema!(&'a WrapSchema); } -impl<'a, T: 'a, TA: 'a> JsonSchema for WrapSchema<&'a mut T, &'a mut TA> +impl<'a, T: 'a, TA: 'a> JsonSchemaAs<&'a mut T> for &'a mut TA where T: ?Sized, - WrapSchema: JsonSchema, + TA: JsonSchemaAs, { forward_schema!(&'a mut WrapSchema); } -impl JsonSchema for WrapSchema, Option> +impl JsonSchemaAs> for Option where - WrapSchema: JsonSchema, + TA: JsonSchemaAs, { forward_schema!(Option>); } -impl JsonSchema for WrapSchema, Box> +impl JsonSchemaAs> for Box where T: ?Sized, - WrapSchema: JsonSchema, + TA: JsonSchemaAs, { forward_schema!(Box>); } -impl JsonSchema for WrapSchema, Rc> +impl JsonSchemaAs> for Rc where T: ?Sized, - WrapSchema: JsonSchema, + TA: JsonSchemaAs, { forward_schema!(Rc>); } -impl JsonSchema for WrapSchema, Arc> +impl JsonSchemaAs> for Arc where T: ?Sized, - WrapSchema: JsonSchema, + TA: JsonSchemaAs, { forward_schema!(Arc>); } -impl JsonSchema for WrapSchema, Vec> +impl JsonSchemaAs> for Vec where - WrapSchema: JsonSchema, + TA: JsonSchemaAs, { forward_schema!(Vec>); } -impl JsonSchema for WrapSchema, VecDeque> +impl JsonSchemaAs> for VecDeque where - WrapSchema: JsonSchema, + TA: JsonSchemaAs, { forward_schema!(VecDeque>); } // schemars only requires that V implement JsonSchema for BTreeMap -impl JsonSchema for WrapSchema, BTreeMap> +impl JsonSchemaAs> for BTreeMap where - WrapSchema: JsonSchema, + VA: JsonSchemaAs, { forward_schema!(BTreeMap, WrapSchema>); } // schemars only requires that V implement JsonSchema for HashMap -impl JsonSchema for WrapSchema, HashMap> +impl JsonSchemaAs> for HashMap where - WrapSchema: JsonSchema, + VA: JsonSchemaAs, { forward_schema!(HashMap, WrapSchema, S>); } -impl JsonSchema for WrapSchema, BTreeSet> +impl JsonSchemaAs> for BTreeSet where - WrapSchema: JsonSchema, + TA: JsonSchemaAs, { forward_schema!(BTreeSet>); } -impl JsonSchema for WrapSchema, HashSet> +impl JsonSchemaAs for HashSet where - WrapSchema: JsonSchema, + TA: JsonSchemaAs, { - forward_schema!(HashSet, H>); + forward_schema!(HashSet, S>); } -impl JsonSchema for WrapSchema<[T; N], [TA; N]> +impl JsonSchemaAs<[T; N]> for [TA; N] where - WrapSchema: JsonSchema, + TA: JsonSchemaAs, { fn schema_name() -> String { std::format!("[{}; {}]", >::schema_name(), N) @@ -171,16 +307,16 @@ macro_rules! schema_for_tuple { ( $( $ts:ident )+ ) ( $( $as:ident )+ ) ) => { - impl<$($ts,)+ $($as,)+> JsonSchema for WrapSchema<($($ts,)+), ($($as,)+)> + impl<$($ts,)+ $($as,)+> JsonSchemaAs<($($ts,)+)> for ($($as,)+) where - $( WrapSchema<$ts, $as>: JsonSchema, )+ + $( $as: JsonSchemaAs<$ts>, )+ { forward_schema!(( $( WrapSchema<$ts, $as>, )+ )); } } } -impl JsonSchema for WrapSchema<(), ()> { +impl JsonSchemaAs<()> for () { forward_schema!(()); } @@ -218,15 +354,15 @@ schema_for_tuple!( //=================================================================== // Impls for serde_with types. -impl JsonSchema for WrapSchema { +impl JsonSchemaAs for Same { forward_schema!(T); } -impl JsonSchema for WrapSchema { +impl JsonSchemaAs for DisplayFromStr { forward_schema!(String); } -impl<'a, T: 'a> JsonSchema for WrapSchema, BorrowCow> +impl<'a, T: 'a> JsonSchemaAs> for BorrowCow where T: ?Sized + ToOwned, Cow<'a, T>: JsonSchema, @@ -234,45 +370,45 @@ where forward_schema!(Cow<'a, T>); } -impl JsonSchema for WrapSchema { +impl JsonSchemaAs for Bytes { forward_schema!(Vec); } -impl JsonSchema for WrapSchema> +impl JsonSchemaAs for DefaultOnError where - WrapSchema: JsonSchema, + TA: JsonSchemaAs, { forward_schema!(WrapSchema); } -impl JsonSchema for WrapSchema> +impl JsonSchemaAs for DefaultOnNull where - WrapSchema: JsonSchema, + TA: JsonSchemaAs, { forward_schema!(Option>); } -impl JsonSchema for WrapSchema> { +impl JsonSchemaAs for FromInto { forward_schema!(T); } -impl JsonSchema for WrapSchema> { +impl JsonSchemaAs for FromIntoRef { forward_schema!(T); } -impl JsonSchema for WrapSchema> { +impl JsonSchemaAs for TryFromInto { forward_schema!(U); } -impl JsonSchema for WrapSchema> { +impl JsonSchemaAs for TryFromIntoRef { forward_schema!(U); } macro_rules! schema_for_map { ($type:ty) => { - impl JsonSchema for WrapSchema<$type, Map> + impl JsonSchemaAs<$type> for Map where - WrapSchema: JsonSchema, + VA: JsonSchemaAs, { forward_schema!(WrapSchema, BTreeMap>); } @@ -287,32 +423,32 @@ schema_for_map!(LinkedList<(K, V)>); schema_for_map!(Vec<(K, V)>); schema_for_map!(VecDeque<(K, V)>); -impl JsonSchema for WrapSchema, Map> +impl JsonSchemaAs> for Map where - WrapSchema: JsonSchema, + VA: JsonSchemaAs, { forward_schema!(WrapSchema, BTreeMap>); } -impl JsonSchema for WrapSchema<[(K, V); N], Map> +impl JsonSchemaAs<[(K, V); N]> for Map where - WrapSchema: JsonSchema, + VA: JsonSchemaAs, { forward_schema!(WrapSchema, BTreeMap>); } macro_rules! map_first_last_wins_schema { ($(=> $extra:ident)? $type:ty) => { - impl JsonSchema for WrapSchema<$type, MapFirstKeyWins> + impl JsonSchemaAs<$type> for MapFirstKeyWins where - WrapSchema: JsonSchema + VA: JsonSchemaAs, { forward_schema!(BTreeMap, WrapSchema>); } - impl JsonSchema for WrapSchema<$type, MapPreventDuplicates> + impl JsonSchemaAs<$type> for MapPreventDuplicates where - WrapSchema: JsonSchema + VA: JsonSchemaAs, { forward_schema!(BTreeMap, WrapSchema>); } @@ -328,9 +464,9 @@ map_first_last_wins_schema!(=> S indexmap_1::IndexMap); #[cfg(feature = "indexmap_2")] map_first_last_wins_schema!(=> S indexmap_2::IndexMap); -impl JsonSchema for WrapSchema> +impl JsonSchemaAs for SetLastValueWins where - WrapSchema: JsonSchema, + TA: JsonSchemaAs, { fn schema_id() -> Cow<'static, str> { std::format!( @@ -365,23 +501,23 @@ where } } -impl JsonSchema for WrapSchema> +impl JsonSchemaAs for SetPreventDuplicates where - WrapSchema: JsonSchema, + TA: JsonSchemaAs, { forward_schema!(WrapSchema); } -impl JsonSchema for WrapSchema> +impl JsonSchemaAs for StringWithSeparator where SEP: Separator, { forward_schema!(String); } -impl JsonSchema for WrapSchema, VecSkipError> +impl JsonSchemaAs> for VecSkipError where - WrapSchema: JsonSchema, + TA: JsonSchemaAs, { forward_schema!(Vec>); }