Skip to main content

salvo_oapi/openapi/
schema.rs

1//! Implements [OpenAPI Schema Object][schema] types which can be
2//! used to define field properties, enum values, array or object types.
3//!
4//! [schema]: https://spec.openapis.org/oas/latest.html#schema-object
5mod all_of;
6mod any_of;
7mod array;
8mod number;
9mod object;
10mod one_of;
11
12use std::ops::{Deref, DerefMut};
13
14pub use all_of::AllOf;
15pub use any_of::AnyOf;
16pub use array::{Array, ArrayItems};
17pub use number::Number;
18pub use object::Object;
19pub use one_of::OneOf;
20use serde::{Deserialize, Serialize};
21
22use crate::{PropMap, RefOr};
23
24/// Schemas collection for OpenApi.
25#[derive(Serialize, Deserialize, Default, Clone, Debug, PartialEq)]
26#[serde(rename_all = "camelCase")]
27pub struct Schemas(pub PropMap<String, RefOr<Schema>>);
28
29impl<K, R> From<PropMap<K, R>> for Schemas
30where
31    K: Into<String>,
32    R: Into<RefOr<Schema>>,
33{
34    fn from(inner: PropMap<K, R>) -> Self {
35        Self(
36            inner
37                .into_iter()
38                .map(|(k, v)| (k.into(), v.into()))
39                .collect(),
40        )
41    }
42}
43impl<K, R, const N: usize> From<[(K, R); N]> for Schemas
44where
45    K: Into<String>,
46    R: Into<RefOr<Schema>>,
47{
48    fn from(inner: [(K, R); N]) -> Self {
49        Self(
50            <[(K, R)]>::into_vec(Box::new(inner))
51                .into_iter()
52                .map(|(k, v)| (k.into(), v.into()))
53                .collect(),
54        )
55    }
56}
57
58impl Deref for Schemas {
59    type Target = PropMap<String, RefOr<Schema>>;
60
61    fn deref(&self) -> &Self::Target {
62        &self.0
63    }
64}
65
66impl DerefMut for Schemas {
67    fn deref_mut(&mut self) -> &mut Self::Target {
68        &mut self.0
69    }
70}
71
72impl IntoIterator for Schemas {
73    type Item = (String, RefOr<Schema>);
74    type IntoIter = <PropMap<String, RefOr<Schema>> as IntoIterator>::IntoIter;
75
76    fn into_iter(self) -> Self::IntoIter {
77        self.0.into_iter()
78    }
79}
80
81impl Schemas {
82    /// Construct a new empty [`Schemas`]. This is effectively same as calling [`Schemas::default`].
83    #[must_use]
84    pub fn new() -> Self {
85        Default::default()
86    }
87    /// Inserts a key-value pair into the instance and returns `self`.
88    #[must_use]
89    pub fn schema<K: Into<String>, V: Into<RefOr<Schema>>>(mut self, key: K, value: V) -> Self {
90        self.insert(key, value);
91        self
92    }
93    /// Inserts a key-value pair into the instance.
94    pub fn insert<K: Into<String>, V: Into<RefOr<Schema>>>(&mut self, key: K, value: V) {
95        self.0.insert(key.into(), value.into());
96    }
97    /// Moves all elements from `other` into `self`, leaving `other` empty.
98    ///
99    /// If a key from `other` is already present in `self`, the respective
100    /// value from `self` will be overwritten with the respective value from `other`.
101    pub fn append(&mut self, other: &mut Self) {
102        let items = std::mem::take(&mut other.0);
103        for item in items {
104            self.insert(item.0, item.1);
105        }
106    }
107    /// Extends a collection with the contents of an iterator.
108    pub fn extend<I, K, V>(&mut self, iter: I)
109    where
110        I: IntoIterator<Item = (K, V)>,
111        K: Into<String>,
112        V: Into<RefOr<Schema>>,
113    {
114        for (k, v) in iter.into_iter() {
115            self.insert(k, v);
116        }
117    }
118}
119
120/// Create an _`empty`_ [`Schema`] that serializes to _`null`_.
121///
122/// Can be used in places where an item can be serialized as `null`. This is used with unit type
123/// enum variants and tuple unit types.
124#[must_use]
125pub fn empty() -> Schema {
126    Schema::object(
127        Object::new()
128            .schema_type(SchemaType::AnyValue)
129            .default_value(serde_json::Value::Null),
130    )
131}
132
133/// Is super type for [OpenAPI Schema Object][schemas]. Schema is reusable resource what can be
134/// referenced from path operations and other components using [`Ref`].
135///
136/// [schemas]: https://spec.openapis.org/oas/latest.html#schema-object
137#[non_exhaustive]
138#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
139#[serde(untagged, rename_all = "camelCase")]
140pub enum Schema {
141    /// Defines array schema from another schema. Typically used with
142    /// [`Schema::Object`]. Slice and Vec types are translated to [`Schema::Array`] types.
143    Array(Array),
144    /// Defines object schema. Object is either `object` holding **properties** which are other
145    /// [`Schema`]s or can be a field within the [`Object`].
146    Object(Box<Object>),
147    /// Creates a _OneOf_ type [composite Object][composite] schema. This schema
148    /// is used to map multiple schemas together where API endpoint could return any of them.
149    /// [`Schema::OneOf`] is created form complex enum where enum holds other than unit types.
150    ///
151    /// [composite]: https://spec.openapis.org/oas/latest.html#components-object
152    OneOf(OneOf),
153
154    /// Creates an _AllOf_ type [composite Object][composite] schema.
155    ///
156    /// [composite]: https://spec.openapis.org/oas/latest.html#components-object
157    AllOf(AllOf),
158
159    /// Creates a _AnyOf_ type [composite Object][composite] schema.
160    ///
161    /// [composite]: https://spec.openapis.org/oas/latest.html#components-object
162    AnyOf(AnyOf),
163}
164
165impl Default for Schema {
166    fn default() -> Self {
167        Self::Object(Default::default())
168    }
169}
170
171impl Schema {
172    /// Construct a new [`Schema`] object.
173    #[must_use]
174    pub fn object(obj: Object) -> Self {
175        Self::Object(Box::new(obj))
176    }
177}
178
179/// OpenAPI [Discriminator][discriminator] object which can be optionally used together with
180/// [`OneOf`] composite object.
181///
182/// [discriminator]: https://spec.openapis.org/oas/latest.html#discriminator-object
183#[non_exhaustive]
184#[derive(Serialize, Deserialize, Clone, Default, Debug, PartialEq, Eq)]
185#[serde(rename_all = "camelCase")]
186pub struct Discriminator {
187    /// Defines a discriminator property name which must be found within all composite
188    /// objects.
189    pub property_name: String,
190
191    /// An object to hold mappings between payload values and schema names or references.
192    /// This field can only be populated manually. There is no macro support and no
193    /// validation.
194    #[serde(skip_serializing_if = "PropMap::is_empty", default)]
195    pub mapping: PropMap<String, String>,
196
197    /// The schema name or URI reference to the schema that validates the payload when the
198    /// discriminating property is absent, or holds a value with no explicit or implicit
199    /// mapping. Added in OpenAPI 3.2.
200    ///
201    /// Required by the spec when the discriminating property is optional.
202    ///
203    /// See <https://spec.openapis.org/oas/v3.2.0.html#discriminator-object>.
204    #[serde(skip_serializing_if = "Option::is_none", default)]
205    pub default_mapping: Option<String>,
206
207    /// Optional extensions "x-something"
208    #[serde(skip_serializing_if = "PropMap::is_empty", flatten)]
209    pub extensions: PropMap<String, serde_json::Value>,
210}
211
212impl Discriminator {
213    /// Construct a new [`Discriminator`] object with property name.
214    ///
215    /// # Examples
216    ///
217    /// Create a new [`Discriminator`] object for `pet_type` property.
218    /// ```
219    /// # use salvo_oapi::schema::Discriminator;
220    /// let discriminator = Discriminator::new("pet_type");
221    /// ```
222    pub fn new<I: Into<String>>(property_name: I) -> Self {
223        Self {
224            property_name: property_name.into(),
225            mapping: PropMap::new(),
226            default_mapping: None,
227            extensions: PropMap::new(),
228        }
229    }
230
231    /// Add a mapping between a payload value and a schema name or URI reference.
232    #[must_use]
233    pub fn add_mapping<K: Into<String>, V: Into<String>>(mut self, value: K, schema: V) -> Self {
234        self.mapping.insert(value.into(), schema.into());
235        self
236    }
237
238    /// Set the fallback schema used when the discriminating property is missing or unmapped.
239    /// Requires OpenAPI 3.2.
240    #[must_use]
241    pub fn default_mapping<I: Into<String>>(mut self, default_mapping: I) -> Self {
242        self.default_mapping = Some(default_mapping.into());
243        self
244    }
245
246    /// Add openapi extensions (`x-something`) for [`Discriminator`].
247    #[must_use]
248    pub fn extensions(mut self, extensions: PropMap<String, serde_json::Value>) -> Self {
249        self.extensions = extensions;
250        self
251    }
252}
253
254#[allow(clippy::trivially_copy_pass_by_ref)]
255fn is_false(value: &bool) -> bool {
256    !*value
257}
258
259/// AdditionalProperties is used to define values of map fields of the [`Schema`].
260///
261/// The value can either be [`RefOr`] or _`bool`_.
262#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
263#[serde(untagged)]
264pub enum AdditionalProperties<T> {
265    /// Use when value type of the map is a known [`Schema`] or [`Ref`] to the [`Schema`].
266    RefOr(RefOr<T>),
267    /// Use _`AdditionalProperties::FreeForm(true)`_ when any value is allowed in the map.
268    FreeForm(bool),
269}
270
271impl<T> From<RefOr<T>> for AdditionalProperties<T> {
272    fn from(value: RefOr<T>) -> Self {
273        Self::RefOr(value)
274    }
275}
276
277impl From<Object> for AdditionalProperties<Schema> {
278    fn from(value: Object) -> Self {
279        Self::RefOr(RefOr::Type(Schema::object(value)))
280    }
281}
282
283impl From<Array> for AdditionalProperties<Schema> {
284    fn from(value: Array) -> Self {
285        Self::RefOr(RefOr::Type(Schema::Array(value)))
286    }
287}
288
289impl From<Ref> for AdditionalProperties<Schema> {
290    fn from(value: Ref) -> Self {
291        Self::RefOr(RefOr::Ref(value))
292    }
293}
294
295impl From<OneOf> for AdditionalProperties<Schema> {
296    fn from(value: OneOf) -> Self {
297        Self::RefOr(RefOr::Type(Schema::OneOf(value)))
298    }
299}
300
301/// Implements [OpenAPI Reference Object][reference] that can be used to reference
302/// reusable components such as [`Schema`]s or [`Response`](super::Response)s.
303///
304/// [reference]: https://spec.openapis.org/oas/latest.html#reference-object
305#[non_exhaustive]
306#[derive(Serialize, Deserialize, Default, Clone, Debug, PartialEq, Eq)]
307pub struct Ref {
308    /// Reference location of the actual component.
309    #[serde(rename = "$ref")]
310    pub ref_location: String,
311
312    /// A description which by default should override that of the referenced component.
313    /// Description supports markdown syntax. If referenced object type does not support
314    /// description this field does not have effect.
315    #[serde(skip_serializing_if = "String::is_empty", default)]
316    pub description: String,
317
318    /// A short summary which by default should override that of the referenced component. If
319    /// referenced component does not support summary field this does not have effect.
320    #[serde(skip_serializing_if = "String::is_empty", default)]
321    pub summary: String,
322}
323
324impl Ref {
325    /// Construct a new [`Ref`] with custom ref location. In most cases this is not necessary
326    /// and [`Ref::from_schema_name`] could be used instead.
327    #[must_use]
328    pub fn new<I: Into<String>>(ref_location: I) -> Self {
329        Self {
330            ref_location: ref_location.into(),
331            ..Default::default()
332        }
333    }
334
335    /// Construct a new [`Ref`] from provided schema name. This will create a [`Ref`] that
336    /// references the reusable schemas.
337    #[must_use]
338    pub fn from_schema_name<I: Into<String>>(schema_name: I) -> Self {
339        Self::new(format!("#/components/schemas/{}", schema_name.into()))
340    }
341
342    /// Construct a new [`Ref`] from provided response name. This will create a [`Ref`] that
343    /// references the reusable response.
344    #[must_use]
345    pub fn from_response_name<I: Into<String>>(response_name: I) -> Self {
346        Self::new(format!("#/components/responses/{}", response_name.into()))
347    }
348
349    /// Add or change reference location of the actual component.
350    #[must_use]
351    pub fn ref_location(mut self, ref_location: String) -> Self {
352        self.ref_location = ref_location;
353        self
354    }
355
356    /// Add or change reference location of the actual component automatically formatting the $ref
357    /// to `#/components/schemas/...` format.
358    #[must_use]
359    pub fn ref_location_from_schema_name<S: Into<String>>(mut self, schema_name: S) -> Self {
360        self.ref_location = format!("#/components/schemas/{}", schema_name.into());
361        self
362    }
363
364    // TODO: REMOVE THE unnecessary description Option wrapping.
365
366    /// Add or change description which by default should override that of the referenced component.
367    /// Description supports markdown syntax. If referenced object type does not support
368    /// description this field does not have effect.
369    #[must_use]
370    pub fn description<S: Into<String>>(mut self, description: S) -> Self {
371        self.description = description.into();
372        self
373    }
374
375    /// Add or change short summary which by default should override that of the referenced
376    /// component. If referenced component does not support summary field this does not have
377    /// effect.
378    #[must_use]
379    pub fn summary<S: Into<String>>(mut self, summary: S) -> Self {
380        self.summary = summary.into();
381        self
382    }
383
384    /// Convert type to [`Array`].
385    #[must_use]
386    pub fn to_array(self) -> Array {
387        Array::new().items(self)
388    }
389}
390
391impl From<Ref> for RefOr<Schema> {
392    fn from(r: Ref) -> Self {
393        Self::Ref(r)
394    }
395}
396
397impl<T> From<T> for RefOr<T> {
398    fn from(t: T) -> Self {
399        Self::Type(t)
400    }
401}
402
403impl Default for RefOr<Schema> {
404    fn default() -> Self {
405        Self::Type(Schema::object(Object::new()))
406    }
407}
408
409// impl ToArray for RefOr<Schema> {}
410
411/// Represents type of [`Schema`].
412#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
413#[serde(untagged)]
414pub enum SchemaType {
415    /// Single type known from OpenAPI spec 3.0
416    Basic(BasicType),
417    /// Multiple types rendered as [`slice`]
418    Array(Vec<BasicType>),
419    /// Type that is considered typeless. _`AnyValue`_ will omit the type definition from the schema
420    /// making it to accept any type possible.
421    AnyValue,
422}
423
424impl Default for SchemaType {
425    fn default() -> Self {
426        Self::Basic(BasicType::default())
427    }
428}
429
430impl From<BasicType> for SchemaType {
431    fn from(value: BasicType) -> Self {
432        Self::basic(value)
433    }
434}
435
436impl FromIterator<BasicType> for SchemaType {
437    fn from_iter<T: IntoIterator<Item = BasicType>>(iter: T) -> Self {
438        Self::Array(iter.into_iter().collect())
439    }
440}
441impl SchemaType {
442    /// Instantiate new [`SchemaType`] of given [`BasicType`]
443    ///
444    /// Method accepts one argument `type` to create [`SchemaType`] for.
445    ///
446    /// # Examples
447    ///
448    /// _**Create string [`SchemaType`]**_
449    /// ```rust
450    /// # use salvo_oapi::schema::{SchemaType, BasicType};
451    /// let ty = SchemaType::basic(BasicType::String);
452    /// ```
453    #[must_use]
454    pub fn basic(r#type: BasicType) -> Self {
455        Self::Basic(r#type)
456    }
457
458    //// Instantiate new [`SchemaType::AnyValue`].
459    /// This is same as calling [`SchemaType::AnyValue`] but in a function form `() -> SchemaType`
460    /// allowing it to be used as argument for _serde's_ _`default = "..."`_.
461    #[must_use]
462    pub fn any() -> Self {
463        Self::AnyValue
464    }
465
466    /// Check whether this [`SchemaType`] is any value _(typeless)_ returning true on any value
467    /// schema type.
468    #[must_use]
469    pub fn is_any_value(&self) -> bool {
470        matches!(self, Self::AnyValue)
471    }
472}
473
474/// Represents data type fragment of [`Schema`].
475///
476/// [`BasicType`] is used to create a [`SchemaType`] that defines the type of the [`Schema`].
477/// [`SchemaType`] can be created from a single [`BasicType`] or multiple [`BasicType`]s according
478/// to the OpenAPI 3.1 spec. Since the OpenAPI 3.1 is fully compatible with JSON schema the
479/// definition of the _**type**_ property comes from [JSON Schema type](https://json-schema.org/understanding-json-schema/reference/type).
480///
481/// # Examples
482/// _**Create nullable string [`SchemaType`]**_
483/// ```rust
484/// # use std::iter::FromIterator;
485/// # use salvo_oapi::schema::{BasicType, SchemaType};
486/// let _: SchemaType = [BasicType::String, BasicType::Null].into_iter().collect();
487/// ```
488/// _**Create string [`SchemaType`]**_
489/// ```rust
490/// # use salvo_oapi::schema::{BasicType, SchemaType};
491/// let _ = SchemaType::basic(BasicType::String);
492/// ```
493#[derive(Serialize, Deserialize, Clone, Default, Debug, PartialEq, Eq)]
494#[serde(rename_all = "lowercase")]
495pub enum BasicType {
496    /// Used with [`Object`] to describe schema that has _properties_ describing fields. have
497    #[default]
498    Object,
499    /// Indicates string type of content. Used with [`Object`] on a `string`
500    /// field.
501    String,
502    /// Indicates integer type of content. Used with [`Object`] on a `number`
503    /// field.
504    Integer,
505    /// Indicates floating point number type of content. Used with
506    /// [`Object`] on a `number` field.
507    Number,
508    /// Indicates boolean type of content. Used with [`Object`] on
509    /// a `bool` field.
510    Boolean,
511    /// Used with [`Array`]. Indicates array type of content.
512    Array,
513    /// Null type. Used together with other type to indicate nullable values.
514    Null,
515}
516
517/// Additional format for [`SchemaType`] to fine tune the data type used.
518///
519/// If the **format** is not supported by the UI it may default back to [`SchemaType`] alone.
520/// Format is an open value, so you can use any formats, even not those defined by the
521/// OpenAPI Specification.
522#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
523#[serde(rename_all = "lowercase", untagged)]
524pub enum SchemaFormat {
525    /// Use to define additional detail about the value.
526    KnownFormat(KnownFormat),
527    /// Can be used to provide additional detail about the value when [`SchemaFormat::KnownFormat`]
528    /// is not suitable.
529    Custom(String),
530}
531
532/// Known schema format modifier property to provide fine detail of the primitive type.
533///
534/// Known format is defined in <https://spec.openapis.org/oas/latest.html#data-types> and
535/// <https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-validation-00#section-7.3> as
536/// well as by few known data types that are enabled by specific feature flag e.g. _`uuid`_.
537#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
538#[serde(rename_all = "kebab-case")]
539pub enum KnownFormat {
540    /// 8 bit integer.
541    Int8,
542    /// 16 bit integer.
543    Int16,
544    /// 32 bit integer.
545    Int32,
546    /// 64 bit integer.
547    Int64,
548    /// 8 bit unsigned integer.
549    #[serde(rename = "uint8")]
550    UInt8,
551    /// 16 bit unsigned integer.
552    #[serde(rename = "uint16")]
553    UInt16,
554    /// 32 bit unsigned integer.
555    #[serde(rename = "uint32")]
556    UInt32,
557    /// 64 bit unsigned integer.
558    #[serde(rename = "uint64")]
559    UInt64,
560    /// floating point number.
561    Float,
562    /// double (floating point) number.
563    Double,
564    /// base64 encoded chars.
565    Byte,
566    /// binary data (octet).
567    Binary,
568    /// ISO-8601 full time format [RFC3339](https://xml2rfc.ietf.org/public/rfc/html/rfc3339.html#anchor14).
569    Time,
570    /// ISO-8601 full date [RFC3339](https://xml2rfc.ietf.org/public/rfc/html/rfc3339.html#anchor14).
571    Date,
572    /// ISO-8601 full date time [RFC3339](https://xml2rfc.ietf.org/public/rfc/html/rfc3339.html#anchor14).
573    DateTime,
574    /// duration format from [RFC3339 Appendix-A](https://datatracker.ietf.org/doc/html/rfc3339#appendix-A).
575    Duration,
576    /// Hint to UI to obscure input.
577    Password,
578    /// Use for compact string
579    String,
580    /// Used with [`String`] values to indicate value is in decimal format.
581    ///
582    /// **decimal** feature need to be enabled.
583    #[cfg(any(feature = "decimal", feature = "decimal-float"))]
584    #[cfg_attr(docsrs, doc(cfg(any(feature = "decimal", feature = "decimal-float"))))]
585    Decimal,
586    /// Used with [`String`] values to indicate value is in ULID format.
587    #[cfg(feature = "ulid")]
588    #[cfg_attr(docsrs, doc(cfg(feature = "ulid")))]
589    Ulid,
590
591    /// Used with [`String`] values to indicate value is in UUID format.
592    #[cfg(feature = "uuid")]
593    #[cfg_attr(docsrs, doc(cfg(feature = "uuid")))]
594    Uuid,
595    /// Used with [`String`] values to indicate value is in Url format.
596    ///
597    /// **url** feature need to be enabled.
598    #[cfg(feature = "url")]
599    #[cfg_attr(docsrs, doc(cfg(feature = "url")))]
600    Url,
601    /// A string instance is valid against this attribute if it is a valid URI Reference
602    /// (either a URI or a relative-reference) according to
603    /// [RFC3986](https://datatracker.ietf.org/doc/html/rfc3986).
604    #[cfg(feature = "url")]
605    #[cfg_attr(docsrs, doc(cfg(feature = "url")))]
606    UriReference,
607    /// A string instance is valid against this attribute if it is a
608    /// valid IRI, according to [RFC3987](https://datatracker.ietf.org/doc/html/rfc3987).
609    #[cfg(feature = "url")]
610    #[cfg_attr(docsrs, doc(cfg(feature = "url")))]
611    Iri,
612    /// A string instance is valid against this attribute if it is a valid IRI Reference
613    /// (either an IRI or a relative-reference)
614    /// according to [RFC3987](https://datatracker.ietf.org/doc/html/rfc3987).
615    #[cfg(feature = "url")]
616    #[cfg_attr(docsrs, doc(cfg(feature = "url")))]
617    IriReference,
618    /// As defined in "Mailbox" rule [RFC5321](https://datatracker.ietf.org/doc/html/rfc5321#section-4.1.2).
619    Email,
620    /// As defined by extended "Mailbox" rule [RFC6531](https://datatracker.ietf.org/doc/html/rfc6531#section-3.3).
621    IdnEmail,
622    /// As defined by [RFC1123](https://datatracker.ietf.org/doc/html/rfc1123#section-2.1), including host names
623    /// produced using the Punycode algorithm
624    /// specified in [RFC5891](https://datatracker.ietf.org/doc/html/rfc5891#section-4.4).
625    Hostname,
626    /// As defined by either [RFC1123](https://datatracker.ietf.org/doc/html/rfc1123#section-2.1) as for hostname,
627    /// or an internationalized hostname as defined by [RFC5890](https://datatracker.ietf.org/doc/html/rfc5890#section-2.3.2.3).
628    IdnHostname,
629    /// An IPv4 address according to [RFC2673](https://datatracker.ietf.org/doc/html/rfc2673#section-3.2).
630    Ipv4,
631    /// An IPv6 address according to [RFC4291](https://datatracker.ietf.org/doc/html/rfc4291#section-2.2).
632    Ipv6,
633    /// A string instance is a valid URI Template if it is according to
634    /// [RFC6570](https://datatracker.ietf.org/doc/html/rfc6570).
635    ///
636    /// _**Note!**_ There are no separate IRL template.
637    UriTemplate,
638    /// A valid JSON string representation of a JSON Pointer according to [RFC6901](https://datatracker.ietf.org/doc/html/rfc6901#section-5).
639    JsonPointer,
640    /// A valid relative JSON Pointer according to [draft-handrews-relative-json-pointer-01](https://datatracker.ietf.org/doc/html/draft-handrews-relative-json-pointer-01).
641    RelativeJsonPointer,
642    /// Regular expression, which SHOULD be valid according to the
643    /// [ECMA-262](https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-validation-00#ref-ecma262).
644    Regex,
645}
646
647#[cfg(test)]
648mod tests {
649    use assert_json_diff::assert_json_eq;
650    use serde_json::{Value, json};
651
652    use super::*;
653    use crate::*;
654
655    #[test]
656    fn create_schema_serializes_json() -> Result<(), serde_json::Error> {
657        let openapi = OpenApi::new("My api", "1.0.0").components(
658            Components::new()
659                .add_schema("Person", Ref::new("#/components/PersonModel"))
660                .add_schema(
661                    "Credential",
662                    Schema::from(
663                        Object::new()
664                            .property(
665                                "id",
666                                Object::new()
667                                    .schema_type(BasicType::Integer)
668                                    .format(SchemaFormat::KnownFormat(KnownFormat::Int32))
669                                    .description("Id of credential")
670                                    .default_value(json!(1i32)),
671                            )
672                            .property(
673                                "name",
674                                Object::new()
675                                    .schema_type(BasicType::String)
676                                    .description("Name of credential"),
677                            )
678                            .property(
679                                "status",
680                                Object::new()
681                                    .schema_type(BasicType::String)
682                                    .default_value(json!("Active"))
683                                    .description("Credential status")
684                                    .enum_values(["Active", "NotActive", "Locked", "Expired"]),
685                            )
686                            .property(
687                                "history",
688                                Array::new().items(Ref::from_schema_name("UpdateHistory")),
689                            )
690                            .property("tags", Object::with_type(BasicType::String).to_array()),
691                    ),
692                ),
693        );
694
695        let serialized = serde_json::to_string_pretty(&openapi)?;
696        println!("serialized json:\n {serialized}");
697
698        let value = serde_json::to_value(&openapi)?;
699        let credential = get_json_path(&value, "components.schemas.Credential.properties");
700        let person = get_json_path(&value, "components.schemas.Person");
701
702        assert!(
703            credential.get("id").is_some(),
704            "could not find path: components.schemas.Credential.properties.id"
705        );
706        assert!(
707            credential.get("status").is_some(),
708            "could not find path: components.schemas.Credential.properties.status"
709        );
710        assert!(
711            credential.get("name").is_some(),
712            "could not find path: components.schemas.Credential.properties.name"
713        );
714        assert!(
715            credential.get("history").is_some(),
716            "could not find path: components.schemas.Credential.properties.history"
717        );
718        assert_json_eq!(
719            credential
720                .get("id")
721                .unwrap_or(&serde_json::value::Value::Null),
722            json!({"type":"integer","format":"int32","description":"Id of credential","default":1})
723        );
724        assert_json_eq!(
725            credential
726                .get("name")
727                .unwrap_or(&serde_json::value::Value::Null),
728            json!({"type":"string","description":"Name of credential"})
729        );
730        assert_json_eq!(
731            credential
732                .get("status")
733                .unwrap_or(&serde_json::value::Value::Null),
734            json!({"default":"Active","description":"Credential status","enum":["Active","NotActive","Locked","Expired"],"type":"string"})
735        );
736        assert_json_eq!(
737            credential
738                .get("history")
739                .unwrap_or(&serde_json::value::Value::Null),
740            json!({"items":{"$ref":"#/components/schemas/UpdateHistory"},"type":"array"})
741        );
742        assert_eq!(person, &json!({"$ref":"#/components/PersonModel"}));
743
744        Ok(())
745    }
746
747    // Examples taken from https://spec.openapis.org/oas/latest.html#model-with-map-dictionary-properties
748    #[test]
749    fn test_property_order() {
750        let json_value = Object::new()
751            .property(
752                "id",
753                Object::new()
754                    .schema_type(BasicType::Integer)
755                    .format(SchemaFormat::KnownFormat(KnownFormat::Int32))
756                    .description("Id of credential")
757                    .default_value(json!(1i32)),
758            )
759            .property(
760                "name",
761                Object::new()
762                    .schema_type(BasicType::String)
763                    .description("Name of credential"),
764            )
765            .property(
766                "status",
767                Object::new()
768                    .schema_type(BasicType::String)
769                    .default_value(json!("Active"))
770                    .description("Credential status")
771                    .enum_values(["Active", "NotActive", "Locked", "Expired"]),
772            )
773            .property(
774                "history",
775                Array::new().items(Ref::from_schema_name("UpdateHistory")),
776            )
777            .property("tags", Object::with_type(BasicType::String).to_array());
778
779        #[cfg(not(feature = "preserve-order"))]
780        assert_eq!(
781            json_value.properties.keys().collect::<Vec<_>>(),
782            vec!["history", "id", "name", "status", "tags"]
783        );
784
785        #[cfg(feature = "preserve-order")]
786        assert_eq!(
787            json_value.properties.keys().collect::<Vec<_>>(),
788            vec!["id", "name", "status", "history", "tags"]
789        );
790    }
791
792    // Examples taken from https://spec.openapis.org/oas/latest.html#model-with-map-dictionary-properties
793    #[test]
794    fn test_additional_properties() {
795        let json_value =
796            Object::new().additional_properties(Object::new().schema_type(BasicType::String));
797        assert_json_eq!(
798            json_value,
799            json!({
800                "type": "object",
801                "additionalProperties": {
802                    "type": "string"
803                }
804            })
805        );
806
807        let json_value = Object::new().additional_properties(
808            Array::new().items(Object::new().schema_type(BasicType::Number)),
809        );
810        assert_json_eq!(
811            json_value,
812            json!({
813                "type": "object",
814                "additionalProperties": {
815                    "items": {
816                        "type": "number",
817                    },
818                    "type": "array",
819                }
820            })
821        );
822
823        let json_value = Object::new().additional_properties(Ref::from_schema_name("ComplexModel"));
824        assert_json_eq!(
825            json_value,
826            json!({
827                "type": "object",
828                "additionalProperties": {
829                    "$ref": "#/components/schemas/ComplexModel"
830                }
831            })
832        )
833    }
834
835    #[test]
836    fn test_object_with_name() {
837        let json_value = Object::new().name("SomeName");
838        assert_json_eq!(
839            json_value,
840            json!({
841                "type": "object",
842                "name": "SomeName"
843            })
844        );
845    }
846
847    #[test]
848    fn test_derive_object_with_examples() {
849        let expected = r#"{"type":"object","examples":[{"age":20,"name":"bob the cat"}]}"#;
850        let json_value = Object::new().examples([json!({"age": 20, "name": "bob the cat"})]);
851
852        let value_string = serde_json::to_string(&json_value).unwrap();
853        assert_eq!(
854            value_string, expected,
855            "value string != expected string, {value_string} != {expected}"
856        );
857    }
858
859    fn get_json_path<'a>(value: &'a Value, path: &str) -> &'a Value {
860        path.split('.').fold(value, |acc, fragment| {
861            acc.get(fragment).unwrap_or(&serde_json::value::Value::Null)
862        })
863    }
864
865    #[test]
866    fn test_array_new() {
867        let array = Array::new().items(
868            Object::new().property(
869                "id",
870                Object::new()
871                    .schema_type(BasicType::Integer)
872                    .format(SchemaFormat::KnownFormat(KnownFormat::Int32))
873                    .description("Id of credential")
874                    .default_value(json!(1i32)),
875            ),
876        );
877
878        assert!(matches!(
879            array.schema_type,
880            SchemaType::Basic(BasicType::Array)
881        ));
882    }
883
884    #[test]
885    fn test_array_builder() {
886        let array: Array = Array::new().items(
887            Object::new().property(
888                "id",
889                Object::new()
890                    .schema_type(BasicType::Integer)
891                    .format(SchemaFormat::KnownFormat(KnownFormat::Int32))
892                    .description("Id of credential")
893                    .default_value(json!(1i32)),
894            ),
895        );
896
897        assert!(matches!(
898            array.schema_type,
899            SchemaType::Basic(BasicType::Array)
900        ));
901    }
902
903    #[test]
904    fn reserialize_deserialized_schema_components() {
905        let components = Components::new()
906            .extend_schemas(vec![(
907                "Comp",
908                Schema::from(
909                    Object::new()
910                        .property("name", Object::new().schema_type(BasicType::String))
911                        .required("name"),
912                ),
913            )])
914            .response("204", Response::new("No Content"))
915            .extend_responses(vec![("200", Response::new("Okay"))])
916            .add_security_scheme(
917                "TLS",
918                SecurityScheme::MutualTls {
919                    description: None,
920                    deprecated: None,
921                },
922            )
923            .extend_security_schemes(vec![(
924                "APIKey",
925                SecurityScheme::Http(security::Http::default()),
926            )]);
927
928        let serialized_components = serde_json::to_string(&components).unwrap();
929
930        let deserialized_components: Components =
931            serde_json::from_str(serialized_components.as_str()).unwrap();
932
933        assert_eq!(
934            serialized_components,
935            serde_json::to_string(&deserialized_components).unwrap()
936        )
937    }
938
939    #[test]
940    fn reserialize_deserialized_object_component() {
941        let prop = Object::new()
942            .property("name", Object::new().schema_type(BasicType::String))
943            .required("name");
944
945        let serialized_components = serde_json::to_string(&prop).unwrap();
946        let deserialized_components: Object =
947            serde_json::from_str(serialized_components.as_str()).unwrap();
948
949        assert_eq!(
950            serialized_components,
951            serde_json::to_string(&deserialized_components).unwrap()
952        )
953    }
954
955    #[test]
956    fn reserialize_deserialized_property() {
957        let prop = Object::new().schema_type(BasicType::String);
958
959        let serialized_components = serde_json::to_string(&prop).unwrap();
960        let deserialized_components: Object =
961            serde_json::from_str(serialized_components.as_str()).unwrap();
962
963        assert_eq!(
964            serialized_components,
965            serde_json::to_string(&deserialized_components).unwrap()
966        )
967    }
968
969    #[test]
970    fn serialize_deserialize_array_within_ref_or_t_object_builder() {
971        let ref_or_schema = RefOr::Type(Schema::object(Object::new().property(
972            "test",
973            RefOr::Type(Schema::Array(Array::new().items(RefOr::Type(
974                Schema::object(Object::new().property("element", RefOr::Ref(Ref::new("#/test")))),
975            )))),
976        )));
977
978        let json_str = serde_json::to_string(&ref_or_schema).expect("");
979        let deserialized: RefOr<Schema> = serde_json::from_str(&json_str).expect("");
980        let json_de_str = serde_json::to_string(&deserialized).expect("");
981        assert_eq!(json_str, json_de_str);
982    }
983
984    #[test]
985    fn serialize_deserialize_one_of_within_ref_or_t_object_builder() {
986        let ref_or_schema = RefOr::Type(Schema::object(
987            Object::new().property(
988                "test",
989                RefOr::Type(Schema::OneOf(
990                    OneOf::new()
991                        .item(Schema::Array(Array::new().items(RefOr::Type(
992                            Schema::object(
993                                Object::new().property("element", RefOr::Ref(Ref::new("#/test"))),
994                            ),
995                        ))))
996                        .item(Schema::Array(Array::new().items(RefOr::Type(
997                            Schema::object(
998                                Object::new().property("foobar", RefOr::Ref(Ref::new("#/foobar"))),
999                            ),
1000                        )))),
1001                )),
1002            ),
1003        ));
1004
1005        let json_str = serde_json::to_string(&ref_or_schema).expect("");
1006        let deserialized: RefOr<Schema> = serde_json::from_str(&json_str).expect("");
1007        let json_de_str = serde_json::to_string(&deserialized).expect("");
1008        assert_eq!(json_str, json_de_str);
1009    }
1010
1011    #[test]
1012    fn serialize_deserialize_all_of_of_within_ref_or_t_object() {
1013        let ref_or_schema = RefOr::Type(Schema::object(
1014            Object::new().property(
1015                "test",
1016                RefOr::Type(Schema::AllOf(
1017                    AllOf::new()
1018                        .item(Schema::Array(Array::new().items(RefOr::Type(
1019                            Schema::object(
1020                                Object::new().property("element", RefOr::Ref(Ref::new("#/test"))),
1021                            ),
1022                        ))))
1023                        .item(RefOr::Type(Schema::object(
1024                            Object::new().property("foobar", RefOr::Ref(Ref::new("#/foobar"))),
1025                        ))),
1026                )),
1027            ),
1028        ));
1029
1030        let json_str = serde_json::to_string(&ref_or_schema).expect("");
1031        let deserialized: RefOr<Schema> = serde_json::from_str(&json_str).expect("");
1032        let json_de_str = serde_json::to_string(&deserialized).expect("");
1033        assert_eq!(json_str, json_de_str);
1034    }
1035
1036    #[test]
1037    fn serialize_deserialize_any_of_of_within_ref_or_t_object() {
1038        let ref_or_schema = RefOr::Type(Schema::object(
1039            Object::new().property(
1040                "test",
1041                RefOr::Type(Schema::AnyOf(
1042                    AnyOf::new()
1043                        .item(Schema::Array(Array::new().items(RefOr::Type(
1044                            Schema::object(
1045                                Object::new().property("element", RefOr::Ref(Ref::new("#/test"))),
1046                            ),
1047                        ))))
1048                        .item(RefOr::Type(Schema::object(
1049                            Object::new().property("foobar", RefOr::Ref(Ref::new("#/foobar"))),
1050                        ))),
1051                )),
1052            ),
1053        ));
1054
1055        let json_str = serde_json::to_string(&ref_or_schema).expect("");
1056        let deserialized: RefOr<Schema> = serde_json::from_str(&json_str).expect("");
1057        let json_de_str = serde_json::to_string(&deserialized).expect("");
1058        assert!(json_str.contains("\"anyOf\""));
1059        assert_eq!(json_str, json_de_str);
1060    }
1061
1062    #[test]
1063    fn serialize_deserialize_schema_array_ref_or_t() {
1064        let ref_or_schema = RefOr::Type(Schema::Array(Array::new().items(RefOr::Type(
1065            Schema::Object(Box::new(
1066                Object::new().property("element", RefOr::Ref(Ref::new("#/test"))),
1067            )),
1068        ))));
1069
1070        let json_str = serde_json::to_string(&ref_or_schema).expect("");
1071        let deserialized: RefOr<Schema> = serde_json::from_str(&json_str).expect("");
1072        let json_de_str = serde_json::to_string(&deserialized).expect("");
1073        assert_eq!(json_str, json_de_str);
1074    }
1075
1076    #[test]
1077    fn serialize_deserialize_schema_array() {
1078        let ref_or_schema = Array::new().items(RefOr::Type(Schema::object(
1079            Object::new().property("element", RefOr::Ref(Ref::new("#/test"))),
1080        )));
1081
1082        let json_str = serde_json::to_string(&ref_or_schema).expect("");
1083        let deserialized: RefOr<Schema> = serde_json::from_str(&json_str).expect("");
1084        let json_de_str = serde_json::to_string(&deserialized).expect("");
1085        assert_eq!(json_str, json_de_str);
1086    }
1087
1088    #[test]
1089    fn serialize_deserialize_schema_with_additional_properties() {
1090        let schema = Schema::object(Object::new().property(
1091            "map",
1092            Object::new().additional_properties(AdditionalProperties::FreeForm(true)),
1093        ));
1094
1095        let json_str = serde_json::to_string(&schema).unwrap();
1096        let deserialized: RefOr<Schema> = serde_json::from_str(&json_str).unwrap();
1097        let json_de_str = serde_json::to_string(&deserialized).unwrap();
1098        assert_eq!(json_str, json_de_str);
1099    }
1100
1101    #[test]
1102    fn serialize_deserialize_schema_with_additional_properties_object() {
1103        let schema = Schema::object(Object::new().property(
1104            "map",
1105            Object::new().additional_properties(
1106                Object::new().property("name", Object::with_type(BasicType::String)),
1107            ),
1108        ));
1109
1110        let json_str = serde_json::to_string(&schema).expect("serde json should success");
1111        let deserialized: RefOr<Schema> =
1112            serde_json::from_str(&json_str).expect("serde json should success");
1113        let json_de_str = serde_json::to_string(&deserialized).expect("serde json should success");
1114        assert_eq!(json_str, json_de_str);
1115    }
1116
1117    #[test]
1118    fn serialize_discriminator_with_mapping() {
1119        let mut discriminator = Discriminator::new("type");
1120        discriminator.mapping = [("int".to_owned(), "#/components/schemas/MyInt".to_owned())]
1121            .into_iter()
1122            .collect::<PropMap<_, _>>();
1123        let one_of = OneOf::new()
1124            .item(Ref::from_schema_name("MyInt"))
1125            .discriminator(discriminator);
1126        let json_value = serde_json::to_value(one_of).expect("serde json should success");
1127
1128        assert_json_eq!(
1129            json_value,
1130            json!({
1131                "oneOf": [
1132                    {
1133                        "$ref": "#/components/schemas/MyInt"
1134                    }
1135                ],
1136                "discriminator": {
1137                    "propertyName": "type",
1138                    "mapping": {
1139                        "int": "#/components/schemas/MyInt"
1140                    }
1141                }
1142            })
1143        );
1144    }
1145
1146    #[test]
1147    fn deserialize_reserialize_one_of_default_type() {
1148        let a = OneOf::new()
1149            .item(Schema::Array(Array::new().items(RefOr::Type(
1150                Schema::object(Object::new().property("element", RefOr::Ref(Ref::new("#/test")))),
1151            ))))
1152            .item(Schema::Array(Array::new().items(RefOr::Type(
1153                Schema::object(Object::new().property("foobar", RefOr::Ref(Ref::new("#/foobar")))),
1154            ))));
1155
1156        let serialized_json = serde_json::to_string(&a).expect("should serialize to json");
1157        let b: OneOf = serde_json::from_str(&serialized_json).expect("should deserialize OneOf");
1158        let reserialized_json = serde_json::to_string(&b).expect("reserialized json");
1159
1160        assert_eq!(serialized_json, reserialized_json);
1161    }
1162
1163    #[test]
1164    fn serialize_deserialize_object_with_multiple_schema_types() {
1165        let object =
1166            Object::new().schema_type(SchemaType::from_iter([BasicType::Object, BasicType::Null]));
1167
1168        let json_str = serde_json::to_string(&object).expect("serde json should success");
1169        let deserialized: Object =
1170            serde_json::from_str(&json_str).expect("serde json should success");
1171        let json_de_str = serde_json::to_string(&deserialized).expect("serde json should success");
1172        assert_eq!(json_str, json_de_str);
1173    }
1174
1175    #[test]
1176    fn test_empty_schema() {
1177        let schema = empty();
1178        assert_json_eq!(
1179            schema,
1180            json!({
1181                "default": null
1182            })
1183        )
1184    }
1185
1186    #[test]
1187    fn test_default_schema() {
1188        let schema = Schema::default();
1189        assert_json_eq!(
1190            schema,
1191            json!({
1192                "type": "object",
1193            })
1194        )
1195    }
1196
1197    #[test]
1198    fn test_ref_from_response_name() {
1199        let _ref = Ref::from_response_name("MyResponse");
1200        assert_json_eq!(
1201            _ref,
1202            json!({
1203                "$ref": "#/components/responses/MyResponse"
1204            })
1205        )
1206    }
1207
1208    #[test]
1209    fn test_additional_properties_from_ref_or() {
1210        let additional_properties =
1211            AdditionalProperties::from(RefOr::Type(Schema::Object(Box::default())));
1212        assert_json_eq!(
1213            additional_properties,
1214            json!({
1215                "type": "object",
1216            })
1217        )
1218    }
1219}