//! 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::{Flexible, Format, PreferMany, PreferOne, Separator, Strict}, prelude::{Schema as WrapSchema, *}, }; use ::schemars_0_8::{ gen::SchemaGenerator, schema::{ ArrayValidation, InstanceType, Metadata, NumberValidation, ObjectValidation, Schema, SchemaObject, SingleOrVec, SubschemaValidation, }, JsonSchema, }; use core::{ mem::ManuallyDrop, ops::{Deref, DerefMut}, }; //=================================================================== // 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; /// /// # #[allow(dead_code)] /// 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 sub-schemas. /// /// As the schema name is used as as part of `$ref` it has to be a valid URI path segment according to /// [RFC 3986 Section-3](https://datatracker.ietf.org/doc/html/rfc3986#section-3). 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`]'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 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> JsonSchemaAs<&'a T> for &'a TA where T: ?Sized, TA: JsonSchemaAs, { forward_schema!(&'a WrapSchema); } impl<'a, T: 'a, TA: 'a> JsonSchemaAs<&'a mut T> for &'a mut TA where T: ?Sized, TA: JsonSchemaAs, { forward_schema!(&'a mut WrapSchema); } impl JsonSchemaAs> for Option where TA: JsonSchemaAs, { forward_schema!(Option>); } impl JsonSchemaAs> for Box where T: ?Sized, TA: JsonSchemaAs, { forward_schema!(Box>); } impl JsonSchemaAs> for Rc where T: ?Sized, TA: JsonSchemaAs, { forward_schema!(Rc>); } impl JsonSchemaAs> for Arc where T: ?Sized, TA: JsonSchemaAs, { forward_schema!(Arc>); } impl JsonSchemaAs> for Vec where TA: JsonSchemaAs, { forward_schema!(Vec>); } impl JsonSchemaAs> for VecDeque where TA: JsonSchemaAs, { forward_schema!(VecDeque>); } // schemars only requires that V implement JsonSchema for BTreeMap impl JsonSchemaAs> for BTreeMap where VA: JsonSchemaAs, { forward_schema!(BTreeMap, WrapSchema>); } // schemars only requires that V implement JsonSchema for HashMap impl JsonSchemaAs> for HashMap where VA: JsonSchemaAs, { forward_schema!(HashMap, WrapSchema, S>); } impl JsonSchemaAs> for BTreeSet where TA: JsonSchemaAs, { forward_schema!(BTreeSet>); } impl JsonSchemaAs for HashSet where TA: JsonSchemaAs, { forward_schema!(HashSet, S>); } impl JsonSchemaAs<[T; N]> for [TA; N] where TA: JsonSchemaAs, { fn schema_name() -> String { std::format!("[{}; {}]", >::schema_name(), N) } fn schema_id() -> Cow<'static, str> { std::format!("[{}; {}]", >::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::>().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,)+> JsonSchemaAs<($($ts,)+)> for ($($as,)+) where $( $as: JsonSchemaAs<$ts>, )+ { forward_schema!(( $( WrapSchema<$ts, $as>, )+ )); } } } impl JsonSchemaAs<()> for () { 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 JsonSchemaAs for Same { forward_schema!(T); } impl JsonSchemaAs for DisplayFromStr { forward_schema!(String); } impl JsonSchemaAs for BoolFromInt { fn schema_name() -> String { "BoolFromInt".into() } fn schema_id() -> Cow<'static, str> { "serde_with::BoolFromInt".into() } fn json_schema(_: &mut SchemaGenerator) -> Schema { SchemaObject { instance_type: Some(InstanceType::Integer.into()), number: Some(Box::new(NumberValidation { minimum: Some(0.0), maximum: Some(1.0), ..Default::default() })), ..Default::default() } .into() } fn is_referenceable() -> bool { false } } impl JsonSchemaAs for BoolFromInt { fn schema_name() -> String { "BoolFromInt".into() } fn schema_id() -> Cow<'static, str> { "serde_with::BoolFromInt".into() } fn json_schema(_: &mut SchemaGenerator) -> Schema { SchemaObject { instance_type: Some(InstanceType::Integer.into()), ..Default::default() } .into() } fn is_referenceable() -> bool { false } } impl<'a, T: 'a> JsonSchemaAs> for BorrowCow where T: ?Sized + ToOwned, Cow<'a, T>: JsonSchema, { forward_schema!(Cow<'a, T>); } impl JsonSchemaAs for Bytes { forward_schema!(Vec); } impl JsonSchemaAs> for BytesOrString { fn schema_name() -> String { "BytesOrString".into() } fn schema_id() -> Cow<'static, str> { "serde_with::BytesOrString".into() } fn json_schema(gen: &mut SchemaGenerator) -> Schema { SchemaObject { subschemas: Some(Box::new(SubschemaValidation { any_of: Some(std::vec![ gen.subschema_for::>(), SchemaObject { instance_type: Some(InstanceType::String.into()), metadata: Some(Box::new(Metadata { write_only: true, ..Default::default() })), ..Default::default() } .into() ]), ..Default::default() })), ..Default::default() } .into() } fn is_referenceable() -> bool { false } } impl JsonSchemaAs for DefaultOnError where TA: JsonSchemaAs, { forward_schema!(WrapSchema); } impl JsonSchemaAs for DefaultOnNull where TA: JsonSchemaAs, { forward_schema!(Option>); } impl JsonSchemaAs for FromInto { forward_schema!(T); } impl JsonSchemaAs for FromIntoRef { forward_schema!(T); } impl JsonSchemaAs for TryFromInto { forward_schema!(U); } impl JsonSchemaAs for TryFromIntoRef { forward_schema!(U); } impl JsonSchemaAs for IfIsHumanReadable where TA: JsonSchemaAs, { // serde_json always has `is_human_readable` set to true so we just use the // schema for the human readable variant. forward_schema!(WrapSchema); } macro_rules! schema_for_map { ($type:ty) => { impl JsonSchemaAs<$type> for Map where VA: JsonSchemaAs, { forward_schema!(WrapSchema, BTreeMap>); } }; } schema_for_map!([(K, V)]); schema_for_map!(BTreeSet<(K, V)>); schema_for_map!(BinaryHeap<(K, V)>); schema_for_map!(Box<[(K, V)]>); schema_for_map!(LinkedList<(K, V)>); schema_for_map!(Vec<(K, V)>); schema_for_map!(VecDeque<(K, V)>); impl JsonSchemaAs> for Map where VA: JsonSchemaAs, { forward_schema!(WrapSchema, BTreeMap>); } impl JsonSchemaAs> for EnumMap where T: JsonSchema, { fn schema_name() -> String { std::format!("EnumMap({})", T::schema_name()) } fn schema_id() -> Cow<'static, str> { std::format!("serde_with::EnumMap({})", T::schema_id()).into() } // We generate the schema here by going through all the variants of the // enum (the oneOf property) and sticking all their properties onto an // object. // // This will be wrong if the object is not an externally tagged enum but in // that case serialization and deserialization will fail so it is probably // OK. fn json_schema(gen: &mut SchemaGenerator) -> Schema { let mut object = SchemaObject { instance_type: Some(InstanceType::Object.into()), ..Default::default() }; let inner = T::json_schema(gen).into_object(); let one_of = match inner.subschemas { Some(subschemas) => match subschemas.one_of { Some(one_of) => one_of, None => return object.into(), }, None => return object.into(), }; let properties = &mut object.object().properties; for schema in one_of { if let Some(object) = schema.into_object().object { properties.extend(object.properties.into_iter()); } } object.object().additional_properties = Some(Box::new(Schema::Bool(false))); object.into() } fn is_referenceable() -> bool { true } } impl WrapSchema, KeyValueMap> where TA: JsonSchemaAs, { /// Transform a schema from the entry type of a `KeyValueMap` to the /// resulting field type. /// /// This usually means doing one of two things: /// 1. removing the `$key$` property from an object, or, /// 2. removing the first item from an array. /// /// We also need to adjust any fields that control the number of items or /// properties allowed such as `(max|min)_properties` or `(max|min)_items`. /// /// This is mostly straightforward. Where things get hairy is when dealing /// with subschemas. JSON schemas allow you to build the schema for an /// object by combining multiple subschemas: /// - You can match exactly one of a set of subschemas (`one_of`). /// - You can match any of a set of subschemas (`any_of`). /// - You can match all of a set of subschemas (`all_of`). /// /// Unfortunately for us, we need to handle all of these options by recursing /// into the subschemas and applying the same transformations as above. fn kvmap_transform_schema(gen: &mut SchemaGenerator, schema: &mut Schema) { let mut parents = Vec::new(); Self::kvmap_transform_schema_impl(gen, schema, &mut parents, 0); } fn kvmap_transform_schema_impl( gen: &mut SchemaGenerator, schema: &mut Schema, parents: &mut Vec, depth: u32, ) { if depth > 8 { return; } let mut done = false; let schema = match schema { Schema::Object(schema) => schema, _ => return, }; // The schema is a reference to a schema defined elsewhere. // // If possible we replace it with its definition but if that is not // available then we give up and leave it as-is. let mut parents = if let Some(reference) = &schema.reference { let name = match reference.strip_prefix(&gen.settings().definitions_path) { Some(name) => name, // Reference is defined elsewhere, nothing we can do. None => return, }; // We are in a recursive reference loop. No point in continuing. if parents.iter().any(|parent| parent == name) { return; } let name = name.to_owned(); *schema = match gen.definitions().get(&name) { Some(Schema::Object(schema)) => schema.clone(), _ => return, }; parents.push(name); DropGuard::new(parents, |parents| drop(parents.pop())) } else { DropGuard::unguarded(parents) }; if let Some(object) = &mut schema.object { // For objects KeyValueMap uses the $key$ property so we need to remove it from // the inner schema. done |= object.properties.remove("$key$").is_some(); done |= object.required.remove("$key$"); if let Some(max) = &mut object.max_properties { *max = max.saturating_sub(1); } if let Some(min) = &mut object.max_properties { *min = min.saturating_sub(1); } } if let Some(array) = &mut schema.array { // For arrays KeyValueMap uses the first array element so we need to remove it // from the inner schema. if let Some(SingleOrVec::Vec(items)) = &mut array.items { // If the array is empty then the leading element may be following the // additionalItem schema. In that case we do nothing. if !items.is_empty() { items.remove(0); done = true; } } if let Some(max) = &mut array.max_items { *max = max.saturating_sub(1); } if let Some(min) = &mut array.min_items { *min = min.saturating_sub(1); } } // We've already modified the schema so there's no need to do more work. if done { return; } let subschemas = match &mut schema.subschemas { Some(subschemas) => subschemas, None => return, }; if let Some(one_of) = &mut subschemas.one_of { for subschema in one_of { Self::kvmap_transform_schema_impl(gen, subschema, &mut parents, depth + 1); } } if let Some(any_of) = &mut subschemas.any_of { for subschema in any_of { Self::kvmap_transform_schema_impl(gen, subschema, &mut parents, depth + 1); } } if let Some(all_of) = &mut subschemas.all_of { for subschema in all_of { Self::kvmap_transform_schema_impl(gen, subschema, &mut parents, depth + 1); } } } } impl JsonSchemaAs> for KeyValueMap where TA: JsonSchemaAs, { fn schema_name() -> String { std::format!("KeyValueMap({})", >::schema_name()) } fn schema_id() -> Cow<'static, str> { std::format!( "serde_with::KeyValueMap({})", >::schema_id() ) .into() } fn json_schema(gen: &mut SchemaGenerator) -> Schema { let mut value = >::json_schema(gen); , KeyValueMap>>::kvmap_transform_schema(gen, &mut value); SchemaObject { instance_type: Some(InstanceType::Object.into()), object: Some(Box::new(ObjectValidation { additional_properties: Some(Box::new(value)), ..Default::default() })), ..Default::default() } .into() } fn is_referenceable() -> bool { true } } impl JsonSchemaAs<[(K, V); N]> for Map where VA: JsonSchemaAs, { forward_schema!(WrapSchema, BTreeMap>); } macro_rules! map_first_last_wins_schema { ($(=> $extra:ident)? $type:ty) => { impl JsonSchemaAs<$type> for MapFirstKeyWins where VA: JsonSchemaAs, { forward_schema!(BTreeMap, WrapSchema>); } impl JsonSchemaAs<$type> for MapPreventDuplicates where VA: JsonSchemaAs, { forward_schema!(BTreeMap, WrapSchema>); } } } map_first_last_wins_schema!(BTreeMap); map_first_last_wins_schema!(=> S HashMap); #[cfg(feature = "hashbrown_0_14")] map_first_last_wins_schema!(=> S hashbrown_0_14::HashMap); #[cfg(feature = "hashbrown_0_15")] map_first_last_wins_schema!(=> S hashbrown_0_15::HashMap); #[cfg(feature = "indexmap_1")] map_first_last_wins_schema!(=> S indexmap_1::IndexMap); #[cfg(feature = "indexmap_2")] map_first_last_wins_schema!(=> S indexmap_2::IndexMap); impl JsonSchemaAs> for OneOrMany where TA: JsonSchemaAs, { fn schema_name() -> String { std::format!( "OneOrMany({},PreferOne)", >::schema_name() ) } fn schema_id() -> Cow<'static, str> { std::format!( "serde_with::OneOrMany({},PreferOne)", >::schema_id() ) .into() } fn json_schema(gen: &mut SchemaGenerator) -> Schema { let single = gen.subschema_for::>(); let array = SchemaObject { instance_type: Some(InstanceType::Array.into()), array: Some(Box::new(ArrayValidation { items: Some(single.clone().into()), ..Default::default() })), ..Default::default() }; SchemaObject { subschemas: Some(Box::new(SubschemaValidation { any_of: Some(std::vec![single, array.into()]), ..Default::default() })), ..Default::default() } .into() } } impl JsonSchemaAs> for OneOrMany where TA: JsonSchemaAs, { fn schema_name() -> String { std::format!( "OneOrMany<{}, PreferMany>", >::schema_name() ) } fn schema_id() -> Cow<'static, str> { std::format!( "serde_with::OneOrMany<{}, PreferMany>", >::schema_id() ) .into() } fn json_schema(gen: &mut SchemaGenerator) -> Schema { let inner = gen.subschema_for::>(); let single = SchemaObject { metadata: Some(Box::new(Metadata { write_only: true, ..Default::default() })), subschemas: Some(Box::new(SubschemaValidation { all_of: Some(std::vec![inner.clone()]), ..Default::default() })), ..Default::default() }; let array = SchemaObject { instance_type: Some(InstanceType::Array.into()), array: Some(Box::new(ArrayValidation { items: Some(Schema::from(single.clone()).into()), ..Default::default() })), ..Default::default() }; SchemaObject { subschemas: Some(Box::new(SubschemaValidation { any_of: Some(std::vec![single.into(), array.into()]), ..Default::default() })), ..Default::default() } .into() } } macro_rules! schema_for_pickfirst { ($( $param:ident )+) => { impl JsonSchemaAs for PickFirst<($( $param, )+)> where $( $param: JsonSchemaAs, )+ { fn schema_name() -> String { std::format!( concat!( "PickFirst(", $( "{", stringify!($param), "}", )+ ")" ), $( $param = >::schema_name(), )+ ) } fn schema_id() -> Cow<'static, str> { std::format!( concat!( "serde_with::PickFirst(", $( "{", stringify!($param), "}", )+ ")" ), $( $param = >::schema_id(), )+ ) .into() } fn json_schema(gen: &mut SchemaGenerator) -> Schema { let mut first = true; let subschemas = std::vec![$( { let is_first = std::mem::replace(&mut first, false); let schema = gen.subschema_for::>(); if !is_first { SchemaObject { metadata: Some(Box::new(Metadata { write_only: true, ..Default::default() })), subschemas: Some(Box::new(SubschemaValidation { all_of: Some(std::vec![schema]), ..Default::default() })), ..Default::default() } .into() } else { schema } } ),+]; SchemaObject { subschemas: Some(Box::new(SubschemaValidation { any_of: Some(subschemas), ..Default::default() })), ..Default::default() } .into() } } } } schema_for_pickfirst!(A); schema_for_pickfirst!(A B); schema_for_pickfirst!(A B C); schema_for_pickfirst!(A B C D); impl JsonSchemaAs for SetLastValueWins where TA: JsonSchemaAs, { fn schema_id() -> Cow<'static, str> { std::format!( "serde_with::SetLastValueWins<{}>", as JsonSchema>::schema_id() ) .into() } fn schema_name() -> String { std::format!( "SetLastValueWins<{}>", as JsonSchema>::schema_name() ) } fn json_schema(gen: &mut SchemaGenerator) -> Schema { let schema = as JsonSchema>::json_schema(gen); let mut schema = schema.into_object(); // We explicitly allow duplicate items since the whole point of // SetLastValueWins is to take the duplicate value. if let Some(array) = &mut schema.array { array.unique_items = None; } schema.into() } fn is_referenceable() -> bool { false } } impl JsonSchemaAs for SetPreventDuplicates where TA: JsonSchemaAs, { forward_schema!(WrapSchema); } impl JsonSchemaAs for StringWithSeparator where SEP: Separator, { forward_schema!(String); } impl JsonSchemaAs> for VecSkipError where TA: JsonSchemaAs, { forward_schema!(Vec>); } mod timespan { use super::*; // #[non_exhaustive] is not actually necessary here but it should // help avoid warnings about semver breakage if this ever changes. #[non_exhaustive] #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum TimespanTargetType { String, F64, U64, I64, } impl TimespanTargetType { pub const fn is_signed(self) -> bool { !matches!(self, Self::U64) } } /// Internal helper trait used to constrain which types we implement /// `JsonSchemaAs` for. pub trait TimespanSchemaTarget { /// The underlying type. /// /// This is mainly used to decide which variant of the resulting schema /// should be marked as `write_only: true`. const TYPE: TimespanTargetType; /// Whether the target type is signed. /// /// This is only true for `std::time::Duration`. const SIGNED: bool = true; } macro_rules! timespan_type_of { (String) => { TimespanTargetType::String }; (f64) => { TimespanTargetType::F64 }; (i64) => { TimespanTargetType::I64 }; (u64) => { TimespanTargetType::U64 }; } macro_rules! declare_timespan_target { ( $target:ty { $($format:ident),* $(,)? } ) => { $( impl TimespanSchemaTarget<$format> for $target { const TYPE: TimespanTargetType = timespan_type_of!($format); } )* } } impl TimespanSchemaTarget for Duration { const TYPE: TimespanTargetType = TimespanTargetType::U64; const SIGNED: bool = false; } impl TimespanSchemaTarget for Duration { const TYPE: TimespanTargetType = TimespanTargetType::F64; const SIGNED: bool = false; } impl TimespanSchemaTarget for Duration { const TYPE: TimespanTargetType = TimespanTargetType::String; const SIGNED: bool = false; } declare_timespan_target!(SystemTime { i64, f64, String }); #[cfg(feature = "chrono_0_4")] declare_timespan_target!(::chrono_0_4::Duration { i64, f64, String }); #[cfg(feature = "chrono_0_4")] declare_timespan_target!(::chrono_0_4::DateTime<::chrono_0_4::Utc> { i64, f64, String }); #[cfg(feature = "chrono_0_4")] declare_timespan_target!(::chrono_0_4::DateTime<::chrono_0_4::Local> { i64, f64, String }); #[cfg(feature = "chrono_0_4")] declare_timespan_target!(::chrono_0_4::NaiveDateTime { i64, f64, String }); #[cfg(feature = "time_0_3")] declare_timespan_target!(::time_0_3::Duration { i64, f64, String }); #[cfg(feature = "time_0_3")] declare_timespan_target!(::time_0_3::OffsetDateTime { i64, f64, String }); #[cfg(feature = "time_0_3")] declare_timespan_target!(::time_0_3::PrimitiveDateTime { i64, f64, String }); } use self::timespan::{TimespanSchemaTarget, TimespanTargetType}; /// Internal type used for the base impls on `DurationXXX` and `TimestampYYY` types. /// /// This allows the `JsonSchema` impls that are Strict to be generic without /// committing to it as part of the public API. struct Timespan(PhantomData<(Format, Strictness)>); impl JsonSchemaAs for Timespan where T: TimespanSchemaTarget, F: Format + JsonSchema, { forward_schema!(F); } impl TimespanTargetType { pub(crate) fn to_flexible_schema(self, signed: bool) -> Schema { use ::schemars_0_8::schema::StringValidation; let mut number = SchemaObject { instance_type: Some(InstanceType::Number.into()), number: (!signed).then(|| { Box::new(NumberValidation { minimum: Some(0.0), ..Default::default() }) }), ..Default::default() }; // This is a more lenient version of the regex used to determine // whether JSON numbers are valid. Specifically, it allows multiple // leading zeroes whereas that is illegal in JSON. let regex = r#"[0-9]+(\.[0-9]+)?([eE][+-]?[0-9]+)?"#; let mut string = SchemaObject { instance_type: Some(InstanceType::String.into()), string: Some(Box::new(StringValidation { pattern: Some(match signed { true => std::format!("^-?{regex}$"), false => std::format!("^{regex}$"), }), ..Default::default() })), ..Default::default() }; if self == Self::String { number.metadata().write_only = true; } else { string.metadata().write_only = true; } SchemaObject { subschemas: Some(Box::new(SubschemaValidation { one_of: Some(std::vec![number.into(), string.into()]), ..Default::default() })), ..Default::default() } .into() } pub(crate) fn schema_id(self) -> &'static str { match self { Self::String => "serde_with::FlexibleStringTimespan", Self::F64 => "serde_with::FlexibleF64Timespan", Self::U64 => "serde_with::FlexibleU64Timespan", Self::I64 => "serde_with::FlexibleI64Timespan", } } } impl JsonSchemaAs for Timespan where T: TimespanSchemaTarget, F: Format + JsonSchema, { fn schema_name() -> String { >::TYPE .schema_id() .strip_prefix("serde_with::") .expect("schema id did not start with `serde_with::` - this is a bug") .into() } fn schema_id() -> Cow<'static, str> { >::TYPE.schema_id().into() } fn json_schema(_: &mut SchemaGenerator) -> Schema { >::TYPE .to_flexible_schema(>::SIGNED) } fn is_referenceable() -> bool { false } } macro_rules! forward_duration_schema { ($ty:ident) => { impl JsonSchemaAs for $ty where T: TimespanSchemaTarget, F: Format + JsonSchema { forward_schema!(WrapSchema>); } impl JsonSchemaAs for $ty where T: TimespanSchemaTarget, F: Format + JsonSchema { forward_schema!(WrapSchema>); } }; } forward_duration_schema!(DurationSeconds); forward_duration_schema!(DurationMilliSeconds); forward_duration_schema!(DurationMicroSeconds); forward_duration_schema!(DurationNanoSeconds); forward_duration_schema!(DurationSecondsWithFrac); forward_duration_schema!(DurationMilliSecondsWithFrac); forward_duration_schema!(DurationMicroSecondsWithFrac); forward_duration_schema!(DurationNanoSecondsWithFrac); forward_duration_schema!(TimestampSeconds); forward_duration_schema!(TimestampMilliSeconds); forward_duration_schema!(TimestampMicroSeconds); forward_duration_schema!(TimestampNanoSeconds); forward_duration_schema!(TimestampSecondsWithFrac); forward_duration_schema!(TimestampMilliSecondsWithFrac); forward_duration_schema!(TimestampMicroSecondsWithFrac); forward_duration_schema!(TimestampNanoSecondsWithFrac); //=================================================================== // Extra internal helper structs struct DropGuard { value: ManuallyDrop, guard: Option, } impl DropGuard { pub fn new(value: T, guard: F) -> Self { Self { value: ManuallyDrop::new(value), guard: Some(guard), } } pub fn unguarded(value: T) -> Self { Self { value: ManuallyDrop::new(value), guard: None, } } } impl Deref for DropGuard { type Target = T; fn deref(&self) -> &Self::Target { &self.value } } impl DerefMut for DropGuard { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.value } } impl Drop for DropGuard { fn drop(&mut self) { // SAFETY: value is known to be initialized since we only ever remove it here. let value = unsafe { ManuallyDrop::take(&mut self.value) }; if let Some(guard) = self.guard.take() { guard(value); } } }