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