//! 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`].
usecrate::{
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},
};
/// 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<i32> for PositiveInt { /// // ... /// # fn serialize_as<S>(&value: &i32, ser: S) -> Result<S::Ok, S::Error> /// # 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<D>(de: D) -> Result<i32, D::Error> /// # 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<i32> for PositiveInt { /// fn schema_name() -> String { /// "PositiveInt".into() /// } /// /// fn json_schema(gen: &mut SchemaGenerator) -> Schema { /// let mut schema = <i32 as JsonSchema>::json_schema(gen).into_object(); /// schema.number().minimum = Some(0.0); /// schema.into() /// } /// } /// ``` /// /// [0]: crate::serde_as /// [1]: crate::Schema pubtrait JsonSchemaAs<T: ?Sized> { /// 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<T, TA> JsonSchema for WrapSchema<T, TA> where
T: ?Sized,
TA: JsonSchemaAs<T>,
{ fn schema_name() -> String {
TA::schema_name()
}
impl<T, TA> JsonSchemaAs<Rc<T>> for Rc<TA> where
T: ?Sized,
TA: JsonSchemaAs<T>,
{
forward_schema!(Rc<WrapSchema<T, TA>>);
}
impl<T, TA> JsonSchemaAs<Arc<T>> for Arc<TA> where
T: ?Sized,
TA: JsonSchemaAs<T>,
{
forward_schema!(Arc<WrapSchema<T, TA>>);
}
impl<T, TA> JsonSchemaAs<Vec<T>> for Vec<TA> where
TA: JsonSchemaAs<T>,
{
forward_schema!(Vec<WrapSchema<T, TA>>);
}
impl<T, TA> JsonSchemaAs<VecDeque<T>> for VecDeque<TA> where
TA: JsonSchemaAs<T>,
{
forward_schema!(VecDeque<WrapSchema<T, TA>>);
}
// schemars only requires that V implement JsonSchema for BTreeMap<K, V> impl<K, V, KA, VA> JsonSchemaAs<BTreeMap<K, V>> for BTreeMap<KA, VA> where
VA: JsonSchemaAs<V>,
{
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> JsonSchemaAs<HashMap<K, V, S>> for HashMap<KA, VA, S> where
VA: JsonSchemaAs<V>,
{
forward_schema!(HashMap<WrapSchema<K, KA>, WrapSchema<V, VA>, S>);
}
impl<T, TA> JsonSchemaAs<BTreeSet<T>> for BTreeSet<TA> where
TA: JsonSchemaAs<T>,
{
forward_schema!(BTreeSet<WrapSchema<T, TA>>);
}
impl<T, TA, S> JsonSchemaAs<T> for HashSet<TA, S> where
TA: JsonSchemaAs<T>,
{
forward_schema!(HashSet<WrapSchema<T, TA>, S>);
}
impl<T, TA, const N: usize> JsonSchemaAs<[T; N]> for [TA; N] where
TA: JsonSchemaAs<T>,
{ fn schema_name() -> String {
std::format!("[{}; {}]", <WrapSchema<T, TA>>::schema_name(), N)
}
impl<T, TA> JsonSchemaAs<T> for DefaultOnError<TA> where
TA: JsonSchemaAs<T>,
{
forward_schema!(WrapSchema<T, TA>);
}
impl<T, TA> JsonSchemaAs<T> for DefaultOnNull<TA> where
TA: JsonSchemaAs<T>,
{
forward_schema!(Option<WrapSchema<T, TA>>);
}
impl<O, T: JsonSchema> JsonSchemaAs<O> for FromInto<T> {
forward_schema!(T);
}
impl<O, T: JsonSchema> JsonSchemaAs<O> for FromIntoRef<T> {
forward_schema!(T);
}
impl<T, U: JsonSchema> JsonSchemaAs<T> for TryFromInto<U> {
forward_schema!(U);
}
impl<T, U: JsonSchema> JsonSchemaAs<T> for TryFromIntoRef<U> {
forward_schema!(U);
}
impl<T, TA, FA> JsonSchemaAs<T> for IfIsHumanReadable<TA, FA> where
TA: JsonSchemaAs<T>,
{ // serde_json always has `is_human_readable` set to true so we just use the // schema for the human readable variant.
forward_schema!(WrapSchema<T, TA>);
}
macro_rules! schema_for_map {
($type:ty) => { impl<K, V, KA, VA> JsonSchemaAs<$type> for Map<KA, VA> where
VA: JsonSchemaAs<V>,
{
forward_schema!(WrapSchema<BTreeMap<K, V>, BTreeMap<KA, VA>>);
}
};
}
// 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 { letmut 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 { iflet Some(object) = schema.into_object().object {
properties.extend(object.properties.into_iter());
}
}
impl<T, TA> WrapSchema<Vec<T>, KeyValueMap<TA>> where
TA: JsonSchemaAs<T>,
{ /// Transform a schema from the entry type of a `KeyValueMap<T>` 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: &an style='color:red'>mut Schema) { letmut parents = Vec::new();
letmut 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. letmut parents = iflet 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 = matchgen.definitions().get(&name) {
Some(Schema::Object(schema)) => schema.clone(),
_ => return,
};
iflet Some(array) = &mut schema.array { // For arrays KeyValueMap uses the first array element so we need to remove it // from the inner schema.
iflet 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;
}
}
fn json_schema(gen: &mut SchemaGenerator) -> Schema { let schema = <WrapSchema<T, TA> as JsonSchema>::json_schema(gen); letmut schema = schema.into_object();
// We explicitly allow duplicate items since the whole point of // SetLastValueWins is to take the duplicate value. iflet Some(array) = &mut schema.array {
array.unique_items = None;
}
schema.into()
}
fn is_referenceable() -> bool { false
}
}
impl<T, TA> JsonSchemaAs<T> for SetPreventDuplicates<TA> where
TA: JsonSchemaAs<T>,
{
forward_schema!(WrapSchema<T, TA>);
}
impl<SEP, T, TA> JsonSchemaAs<T> for StringWithSeparator<SEP, TA> where
SEP: Separator,
{
forward_schema!(String);
}
impl<T, TA> JsonSchemaAs<Vec<T>> for VecSkipError<TA> where
TA: JsonSchemaAs<T>,
{
forward_schema!(Vec<WrapSchema<T, TA>>);
}
mod timespan { usesuper::*;
// #[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)] pubenum TimespanTargetType {
String,
F64,
U64,
I64,
}
/// Internal helper trait used to constrain which types we implement /// `JsonSchemaAs<T>` for. pubtrait TimespanSchemaTarget<F> { /// The underlying type. /// /// This is mainly used to decide which variant of the resulting schema /// should be marked as `write_only: true`. constTYPE: TimespanTargetType;
/// Whether the target type is signed. /// /// This is only true for `std::time::Duration`. const SIGNED: bool = true;
}
/// 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<Format, Strictness>(PhantomData<(Format, Strictness)>);
impl<T, F> JsonSchemaAs<T> for Timespan<F, Strict> where
T: TimespanSchemaTarget<F>,
F: Format + JsonSchema,
{
forward_schema!(F);
}
// 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]+)?"#; letmut 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()
};
impl<T, F> JsonSchemaAs<T> for Timespan<F, Flexible> where
T: TimespanSchemaTarget<F>,
F: Format + JsonSchema,
{ fn schema_name() -> String {
<T as TimespanSchemaTarget<F>>::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> {
<T as TimespanSchemaTarget<F>>::TYPE.schema_id().into()
}
fn json_schema(_: &mut SchemaGenerator) -> Schema {
<T as TimespanSchemaTarget<F>>::TYPE
.to_flexible_schema(<T as TimespanSchemaTarget<F>>::SIGNED)
}
fn is_referenceable() -> bool { false
}
}
macro_rules! forward_duration_schema {
($ty:ident) => { impl<T, F> JsonSchemaAs<T> for $ty<F, Strict> where
T: TimespanSchemaTarget<F>,
F: Format + JsonSchema
{
forward_schema!(WrapSchema<T, Timespan<F, Strict>>);
}
impl<T, F> JsonSchemaAs<T> for $ty<F, Flexible> where
T: TimespanSchemaTarget<F>,
F: Format + JsonSchema
{
forward_schema!(WrapSchema<T, Timespan<F, Flexible>>);
}
};
}
impl<T, F: FnOnce(T)> Drop for DropGuard<T, F> { fn drop(&mutself) { // SAFETY: value is known to be initialized since we only ever remove it here. let value = unsafe { ManuallyDrop::take(&mutself.value) };
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.