Skip to main content

openapiv3_1/
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
5use indexmap::IndexMap;
6use is_empty::IsEmpty;
7use ordered_float::OrderedFloat;
8use serde_derive::{Deserialize, Serialize};
9
10use super::extensions::Extensions;
11use super::security::SecurityScheme;
12use super::{RefOr, Response};
13
14#[allow(unused_imports)]
15use super::security::SecurityRequirement;
16
17/// Create an _`empty`_ [`Schema`] that serializes to _`null`_.
18///
19/// Can be used in places where an item can be serialized as `null`. This is used with unit type
20/// enum variants and tuple unit types.
21pub fn empty() -> Schema {
22    Schema::object(Object::builder().default(serde_json::Value::Null).build())
23}
24
25/// Implements [OpenAPI Components Object][components] which holds supported
26/// reusable objects.
27///
28/// Components can hold either reusable types themselves or references to other reusable
29/// types.
30///
31/// [components]: https://spec.openapis.org/oas/latest.html#components-object
32#[non_exhaustive]
33#[derive(Serialize, Deserialize, Default, Clone, PartialEq, bon::Builder, IsEmpty)]
34#[cfg_attr(feature = "debug", derive(Debug))]
35#[serde(rename_all = "camelCase")]
36#[builder(on(_, into))]
37pub struct Components {
38    /// Map of reusable [OpenAPI Schema Object][schema]s.
39    ///
40    /// [schema]: https://spec.openapis.org/oas/latest.html#schema-object
41    #[serde(skip_serializing_if = "IndexMap::is_empty", default)]
42    #[builder(field)]
43    #[is_empty(if = "IndexMap::is_empty")]
44    pub schemas: IndexMap<String, Schema>,
45
46    /// Map of reusable response name, to [OpenAPI Response Object][response]s or [OpenAPI
47    /// Reference][reference]s to [OpenAPI Response Object][response]s.
48    ///
49    /// [response]: https://spec.openapis.org/oas/latest.html#response-object
50    /// [reference]: https://spec.openapis.org/oas/latest.html#reference-object
51    #[serde(skip_serializing_if = "IndexMap::is_empty", default)]
52    #[builder(field)]
53    #[is_empty(if = "IndexMap::is_empty")]
54    pub responses: IndexMap<String, RefOr<Response>>,
55
56    /// Map of reusable [OpenAPI Security Scheme Object][security_scheme]s.
57    ///
58    /// [security_scheme]: https://spec.openapis.org/oas/latest.html#security-scheme-object
59    #[serde(skip_serializing_if = "IndexMap::is_empty", default)]
60    #[builder(field)]
61    #[is_empty(if = "IndexMap::is_empty")]
62    pub security_schemes: IndexMap<String, SecurityScheme>,
63
64    /// Optional extensions "x-something".
65    #[serde(skip_serializing_if = "Option::is_none", default, flatten)]
66    #[is_empty(if = "is_empty::is_option_really_empty")]
67    pub extensions: Option<Extensions>,
68}
69
70impl Components {
71    /// Construct a new [`Components`].
72    pub fn new() -> Self {
73        Self { ..Default::default() }
74    }
75
76    /// Add [`SecurityScheme`] to [`Components`].
77    ///
78    /// Accepts two arguments where first is the name of the [`SecurityScheme`]. This is later when
79    /// referenced by [`SecurityRequirement`]s. Second parameter is the [`SecurityScheme`].
80    ///
81    pub fn add_security_scheme<N: Into<String>, S: Into<SecurityScheme>>(&mut self, name: N, security_scheme: S) {
82        self.security_schemes.insert(name.into(), security_scheme.into());
83    }
84
85    /// Add iterator of [`SecurityScheme`]s to [`Components`].
86    ///
87    /// Accepts two arguments where first is the name of the [`SecurityScheme`]. This is later when
88    /// referenced by [`SecurityRequirement`]'s. Second parameter is the [`SecurityScheme`].
89    pub fn add_security_schemes_from_iter<N: Into<String>, S: Into<SecurityScheme>>(
90        &mut self,
91        schemas: impl IntoIterator<Item = (N, S)>,
92    ) {
93        self.security_schemes
94            .extend(schemas.into_iter().map(|(name, item)| (name.into(), item.into())));
95    }
96
97    /// Add [`Schema`] to [`Components`].
98    ///
99    /// Accepts two arguments where first is the name of the [`Schema`]. This is later when
100    /// referenced by [`Ref::ref_location`]s. Second parameter is the [`Schema`].
101    pub fn add_schema<N: Into<String>, S: Into<Schema>>(&mut self, name: N, scheme: S) {
102        self.schemas.insert(name.into(), scheme.into());
103    }
104
105    /// Add iterator of [`Schema`]s to [`Components`].
106    ///
107    /// Accepts two arguments where first is the name of the [`Schema`]. This is later when
108    /// referenced by [`Ref::ref_location`]s. Second parameter is the [`Schema`].
109    ///
110    /// [requirement]: ../security/struct.SecurityRequirement.html
111    pub fn add_schemas_from_iter<N: Into<String>, S: Into<Schema>>(&mut self, schemas: impl IntoIterator<Item = (N, S)>) {
112        self.schemas
113            .extend(schemas.into_iter().map(|(name, item)| (name.into(), item.into())));
114    }
115}
116
117impl<S: components_builder::State> ComponentsBuilder<S> {
118    /// Add [`Schema`] to [`Components`].
119    ///
120    /// Accepts two arguments where first is name of the schema and second is the schema itself.
121    pub fn schema(mut self, name: impl Into<String>, schema: impl Into<Schema>) -> Self {
122        self.schemas.insert(name.into(), schema.into());
123        self
124    }
125
126    /// Add [`Schema`]s from iterator.
127    ///
128    /// # Examples
129    /// ```rust
130    /// # use openapiv3_1::schema::{Components, Object, Type, Schema};
131    /// Components::builder().schemas_from_iter([(
132    ///     "Pet",
133    ///     Schema::from(
134    ///         Object::builder()
135    ///             .property(
136    ///                 "name",
137    ///                 Object::builder().schema_type(Type::String),
138    ///             )
139    ///             .required(["name"])
140    ///     ),
141    /// )]);
142    /// ```
143    pub fn schemas_from_iter<I: IntoIterator<Item = (S2, C)>, C: Into<Schema>, S2: Into<String>>(
144        mut self,
145        schemas: I,
146    ) -> Self {
147        self.schemas
148            .extend(schemas.into_iter().map(|(name, schema)| (name.into(), schema.into())));
149
150        self
151    }
152
153    /// Add [`struct@Response`] to [`Components`].
154    ///
155    /// Method accepts tow arguments; `name` of the reusable response and `response` which is the
156    /// reusable response itself.
157    pub fn response<S2: Into<String>, R: Into<RefOr<Response>>>(mut self, name: S2, response: R) -> Self {
158        self.responses.insert(name.into(), response.into());
159        self
160    }
161
162    /// Add multiple [`struct@Response`]s to [`Components`] from iterator.
163    ///
164    /// Like the [`ComponentsBuilder::schemas_from_iter`] this allows adding multiple responses by
165    /// any iterator what returns tuples of (name, response) values.
166    pub fn responses_from_iter<I: IntoIterator<Item = (S2, R)>, S2: Into<String>, R: Into<RefOr<Response>>>(
167        mut self,
168        responses: I,
169    ) -> Self {
170        self.responses
171            .extend(responses.into_iter().map(|(name, response)| (name.into(), response.into())));
172
173        self
174    }
175
176    /// Add [`SecurityScheme`] to [`Components`].
177    ///
178    /// Accepts two arguments where first is the name of the [`SecurityScheme`]. This is later when
179    /// referenced by [`SecurityRequirement`][requirement]s. Second parameter is the [`SecurityScheme`].
180    ///
181    /// [requirement]: ../security/struct.SecurityRequirement.html
182    pub fn security_scheme<N: Into<String>, S2: Into<SecurityScheme>>(mut self, name: N, security_scheme: S2) -> Self {
183        self.security_schemes.insert(name.into(), security_scheme.into());
184
185        self
186    }
187}
188
189impl<S: components_builder::IsComplete> From<ComponentsBuilder<S>> for Components {
190    fn from(value: ComponentsBuilder<S>) -> Self {
191        value.build()
192    }
193}
194
195impl Default for Schema {
196    fn default() -> Self {
197        Schema::Bool(true)
198    }
199}
200
201/// OpenAPI [Discriminator][discriminator] object which can be optionally used together with
202/// [`Object`] composite object.
203///
204/// [discriminator]: https://spec.openapis.org/oas/latest.html#discriminator-object
205#[derive(Serialize, Deserialize, Clone, Default, PartialEq, Eq, IsEmpty)]
206#[serde(rename_all = "camelCase")]
207#[cfg_attr(feature = "debug", derive(Debug))]
208pub struct Discriminator {
209    /// Defines a discriminator property name which must be found within all composite
210    /// objects.
211    pub property_name: String,
212
213    /// An object to hold mappings between payload values and schema names or references.
214    /// This field can only be populated manually. There is no macro support and no
215    /// validation.
216    #[serde(skip_serializing_if = "IndexMap::is_empty", default)]
217    #[is_empty(if = "IndexMap::is_empty")]
218    pub mapping: IndexMap<String, String>,
219
220    /// Optional extensions "x-something".
221    #[serde(skip_serializing_if = "Option::is_none", flatten)]
222    #[is_empty(if = "is_empty::is_option_really_empty")]
223    pub extensions: Option<Extensions>,
224}
225
226impl Discriminator {
227    /// Construct a new [`Discriminator`] object with property name.
228    ///
229    /// # Examples
230    ///
231    /// Create a new [`Discriminator`] object for `pet_type` property.
232    /// ```rust
233    /// # use openapiv3_1::schema::Discriminator;
234    /// let discriminator = Discriminator::new("pet_type");
235    /// ```
236    pub fn new<I: Into<String>>(property_name: I) -> Self {
237        Self {
238            property_name: property_name.into(),
239            mapping: IndexMap::new(),
240            ..Default::default()
241        }
242    }
243
244    /// Construct a new [`Discriminator`] object with property name and mappings.
245    ///
246    ///
247    /// Method accepts two arguments. First _`property_name`_ to use as `discriminator` and
248    /// _`mapping`_ for custom property name mappings.
249    ///
250    /// # Examples
251    ///
252    /// _**Construct an ew [`Discriminator`] with custom mapping.**_
253    ///
254    /// ```rust
255    /// # use openapiv3_1::schema::Discriminator;
256    /// let discriminator = Discriminator::with_mapping("pet_type", [
257    ///     ("cat","#/components/schemas/Cat")
258    /// ]);
259    /// ```
260    pub fn with_mapping<P: Into<String>, M: IntoIterator<Item = (K, V)>, K: Into<String>, V: Into<String>>(
261        property_name: P,
262        mapping: M,
263    ) -> Self {
264        Self {
265            property_name: property_name.into(),
266            mapping: IndexMap::from_iter(mapping.into_iter().map(|(key, val)| (key.into(), val.into()))),
267            ..Default::default()
268        }
269    }
270}
271
272/// Implements [OpenAPI Reference Object][reference] that can be used to reference
273/// reusable components such as [`Schema`]s or [`Response`]s.
274///
275/// [reference]: https://spec.openapis.org/oas/latest.html#reference-object
276#[non_exhaustive]
277#[derive(Serialize, Deserialize, Default, Clone, PartialEq, Eq, bon::Builder, IsEmpty)]
278#[cfg_attr(feature = "debug", derive(Debug))]
279#[builder(on(_, into))]
280pub struct Ref {
281    /// Reference location of the actual component.
282    #[serde(rename = "$ref")]
283    pub ref_location: String,
284
285    /// A description which by default should override that of the referenced component.
286    /// Description supports markdown syntax. If referenced object type does not support
287    /// description this field does not have effect.
288    #[serde(skip_serializing_if = "String::is_empty", default)]
289    #[builder(default)]
290    pub description: String,
291
292    /// A short summary which by default should override that of the referenced component. If
293    /// referenced component does not support summary field this does not have effect.
294    #[serde(skip_serializing_if = "String::is_empty", default)]
295    #[builder(default)]
296    pub summary: String,
297}
298
299impl Ref {
300    /// Construct a new [`Ref`] with custom ref location. In most cases this is not necessary
301    /// and [`Ref::from_schema_name`] could be used instead.
302    pub fn new<I: Into<String>>(ref_location: I) -> Self {
303        Self {
304            ref_location: ref_location.into(),
305            ..Default::default()
306        }
307    }
308
309    /// Construct a new [`Ref`] from provided schema name. This will create a [`Ref`] that
310    /// references the the reusable schemas.
311    pub fn from_schema_name<I: Into<String>>(schema_name: I) -> Self {
312        Self::new(format!("#/components/schemas/{}", schema_name.into()))
313    }
314
315    /// Construct a new [`Ref`] from provided response name. This will create a [`Ref`] that
316    /// references the reusable response.
317    pub fn from_response_name<I: Into<String>>(response_name: I) -> Self {
318        Self::new(format!("#/components/responses/{}", response_name.into()))
319    }
320}
321
322impl<S: ref_builder::IsComplete> From<RefBuilder<S>> for Schema {
323    fn from(builder: RefBuilder<S>) -> Self {
324        Self::from(builder.build())
325    }
326}
327
328impl From<Ref> for Schema {
329    fn from(r: Ref) -> Self {
330        Self::object(
331            Object::builder()
332                .reference(r.ref_location)
333                .description(r.description)
334                .summary(r.summary)
335                .build(),
336        )
337    }
338}
339
340impl<T> From<T> for RefOr<T> {
341    fn from(t: T) -> Self {
342        Self::T(t)
343    }
344}
345
346/// JSON Schema Type
347/// <https://www.learnjsonschema.com/2020-12/validation/type>
348#[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Copy)]
349#[cfg_attr(feature = "debug", derive(Debug))]
350#[non_exhaustive]
351pub enum Type {
352    /// JSON array
353    #[serde(rename = "array")]
354    Array,
355    /// The JSON true or false constants
356    #[serde(rename = "boolean")]
357    Boolean,
358    /// A JSON number that represents an integer
359    #[serde(rename = "integer")]
360    Integer,
361    /// The JSON null constant
362    #[serde(rename = "null")]
363    Null,
364    /// A JSON number
365    #[serde(rename = "number")]
366    Number,
367    /// A JSON object
368    #[serde(rename = "object")]
369    Object,
370    /// A JSON string
371    #[serde(rename = "string")]
372    String,
373}
374
375/// JSON Schema Type
376///
377/// `type` can either be a singular type or an array of types.
378///
379/// <https://www.learnjsonschema.com/2020-12/validation/type>
380#[derive(Serialize, Deserialize, Clone, PartialEq)]
381#[cfg_attr(feature = "debug", derive(Debug))]
382#[serde(untagged)]
383pub enum Types {
384    /// A singular type
385    Single(Type),
386    /// Multiple types
387    Multi(Vec<Type>),
388}
389
390impl From<Type> for Types {
391    fn from(value: Type) -> Self {
392        Self::Single(value)
393    }
394}
395
396impl From<Vec<Type>> for Types {
397    fn from(mut value: Vec<Type>) -> Self {
398        if value.len() == 1 {
399            Self::Single(value.remove(0))
400        } else {
401            Self::Multi(value)
402        }
403    }
404}
405
406fn is_opt_json_value_empty(t: &Option<serde_json::Value>) -> bool {
407    match t {
408        Some(j) => j.is_null(),
409        _ => true,
410    }
411}
412
413fn is_opt_bool_empty_with_default_false(t: &Option<bool>) -> bool {
414    match t {
415        None => true,
416        Some(t) => !*t,
417    }
418}
419
420/// A JSON Schema Object as per JSON Schema specification.
421/// <https://www.learnjsonschema.com/2020-12/>
422#[derive(Serialize, Deserialize, Clone, PartialEq, Default, bon::Builder, IsEmpty)]
423#[serde(default, deny_unknown_fields)]
424#[builder(on(_, into))]
425#[cfg_attr(feature = "debug", derive(Debug))]
426#[non_exhaustive]
427pub struct Object {
428    /// The `properties` keyword restricts object properties to the given subschemas.
429    /// Collected annotations report which properties were evaluated.
430    /// <https://www.learnjsonschema.com/2020-12/applicator/properties/>
431    #[serde(skip_serializing_if = "IndexMap::is_empty")]
432    #[builder(field)]
433    #[is_empty(if = "IndexMap::is_empty")]
434    pub properties: IndexMap<String, Schema>,
435    /// The `examples` keyword provides example instances for documentation.
436    /// Does not affect validation.
437    /// <https://www.learnjsonschema.com/2020-12/meta-data/examples/>
438    #[serde(skip_serializing_if = "Vec::is_empty")]
439    #[builder(field)]
440    pub examples: Vec<serde_json::Value>,
441    /// The `prefixItems` keyword validates the first items of an array against a sequence of subschemas.
442    /// Remaining items fall back to `items`, if present.
443    /// <https://www.learnjsonschema.com/2020-12/applicator/prefixitems/>
444    #[serde(rename = "prefixItems", skip_serializing_if = "Option::is_none")]
445    #[builder(field)]
446    #[is_empty(if = "is_empty::is_option_really_empty")]
447    pub prefix_items: Option<Vec<Schema>>,
448    /// The `enum` keyword restricts instances to a finite set of values.
449    /// <https://www.learnjsonschema.com/2020-12/validation/enum/>
450    #[serde(rename = "enum", skip_serializing_if = "Option::is_none")]
451    #[builder(field)]
452    #[is_empty(if = "is_empty::is_option_really_empty")]
453    pub enum_values: Option<Vec<serde_json::Value>>,
454    /// The `required` keyword lists property names that must be present in an object.
455    /// <https://www.learnjsonschema.com/2020-12/applicator/required/>
456    #[serde(skip_serializing_if = "Vec::is_empty")]
457    #[builder(field)]
458    pub required: Vec<String>,
459    /// The `allOf` keyword requires instance validation against all subschemas.
460    /// <https://www.learnjsonschema.com/2020-12/validation/allof/>
461    #[serde(rename = "allOf", skip_serializing_if = "Vec::is_empty")]
462    #[builder(field)]
463    pub all_of: Vec<Schema>,
464    /// The `anyOf` keyword requires validation against at least one subschema.
465    /// <https://www.learnjsonschema.com/2020-12/validation/anyof/>
466    #[serde(rename = "anyOf", skip_serializing_if = "Option::is_none")]
467    #[builder(field)]
468    #[is_empty(if = "is_empty::is_option_really_empty")]
469    pub any_of: Option<Vec<Schema>>,
470    /// The `oneOf` keyword requires validation against exactly one subschema.
471    /// <https://www.learnjsonschema.com/2020-12/validation/oneof/>
472    #[serde(rename = "oneOf", skip_serializing_if = "Option::is_none")]
473    #[builder(field)]
474    #[is_empty(if = "is_empty::is_option_really_empty")]
475    pub one_of: Option<Vec<Schema>>,
476    /// The `$id` keyword defines a unique identifier for the schema.
477    /// <https://www.learnjsonschema.com/2020-12/meta-data/id/>
478    #[serde(rename = "$id", skip_serializing_if = "String::is_empty")]
479    #[builder(default)]
480    pub id: String,
481    /// The `$schema` keyword declares the JSON Schema version.
482    /// <https://www.learnjsonschema.com/2020-12/meta-data/schema/>
483    #[serde(rename = "$schema", skip_serializing_if = "Option::is_none")]
484    #[is_empty(if = "is_empty::is_option_really_empty")]
485    pub schema: Option<Schema>,
486    /// The `$ref` keyword references an external or internal schema by URI.
487    /// <https://www.learnjsonschema.com/2020-12/structure/$ref/>
488    #[serde(rename = "$ref", skip_serializing_if = "String::is_empty")]
489    #[builder(default, name = "reference")]
490    pub reference: String,
491    /// The `$comment` keyword provides annotations for documentation.
492    /// <https://www.learnjsonschema.com/2020-12/meta-data/comment/>
493    #[serde(rename = "$comment", skip_serializing_if = "String::is_empty")]
494    #[builder(default)]
495    pub comment: String,
496    /// The `title` keyword provides a short descriptive title.
497    /// <https://www.learnjsonschema.com/2020-12/meta-data/title/>
498    #[serde(skip_serializing_if = "String::is_empty")]
499    #[builder(default)]
500    pub title: String,
501    /// The `description` keyword provides a detailed description.
502    /// <https://www.learnjsonschema.com/2020-12/meta-data/description/>
503    #[serde(skip_serializing_if = "String::is_empty")]
504    #[builder(default)]
505    pub description: String,
506    /// The `summary` keyword offers a brief summary for documentation.
507    /// <https://www.learnjsonschema.com/2020-12/meta-data/summary/>
508    #[serde(skip_serializing_if = "String::is_empty")]
509    #[builder(default)]
510    pub summary: String,
511    /// The `default` keyword provides a default instance value.
512    /// <https://www.learnjsonschema.com/2020-12/validation/default/>
513    #[serde(skip_serializing_if = "Option::is_none")]
514    #[is_empty(if = "is_opt_json_value_empty")]
515    pub default: Option<serde_json::Value>,
516    /// The `readOnly` keyword marks a property as read-only.
517    /// <https://www.learnjsonschema.com/2020-12/validation/readOnly/>
518    #[serde(rename = "readOnly", skip_serializing_if = "Option::is_none")]
519    #[is_empty(if = "is_opt_bool_empty_with_default_false")]
520    pub read_only: Option<bool>,
521    /// The `deprecated` keyword marks a schema as deprecated.
522    /// <https://www.learnjsonschema.com/2020-12/meta-data/deprecated/>
523    #[serde(rename = "deprecated", skip_serializing_if = "Option::is_none")]
524    #[is_empty(if = "is_opt_bool_empty_with_default_false")]
525    pub deprecated: Option<bool>,
526    /// The `writeOnly` keyword marks a property as write-only.
527    /// <https://www.learnjsonschema.com/2020-12/validation/writeOnly/>
528    #[serde(rename = "writeOnly", skip_serializing_if = "Option::is_none")]
529    #[is_empty(if = "is_opt_bool_empty_with_default_false")]
530    pub write_only: Option<bool>,
531    /// The `multipleOf` keyword ensures the number is a multiple of this value.
532    /// <https://www.learnjsonschema.com/2020-12/validation/multipleOf/>
533    #[serde(rename = "multipleOf", skip_serializing_if = "Option::is_none")]
534    pub multiple_of: Option<OrderedFloat<f64>>,
535    /// The `maximum` keyword defines the maximum numeric value.
536    /// <https://www.learnjsonschema.com/2020-12/validation/maximum/>
537    #[serde(skip_serializing_if = "Option::is_none")]
538    pub maximum: Option<OrderedFloat<f64>>,
539    /// The `exclusiveMaximum` keyword requires the number to be less than this value.
540    /// <https://www.learnjsonschema.com/2020-12/validation/exclusiveMaximum/>
541    #[serde(rename = "exclusiveMaximum", skip_serializing_if = "Option::is_none")]
542    pub exclusive_maximum: Option<OrderedFloat<f64>>,
543    /// The `minimum` keyword defines the minimum numeric value.
544    /// <https://www.learnjsonschema.com/2020-12/validation/minimum/>
545    #[serde(skip_serializing_if = "Option::is_none")]
546    pub minimum: Option<OrderedFloat<f64>>,
547    /// The `exclusiveMinimum` keyword requires the number to be greater than this value.
548    /// <https://www.learnjsonschema.com/2020-12/validation/exclusiveMinimum/>
549    #[serde(rename = "exclusiveMinimum", skip_serializing_if = "Option::is_none")]
550    pub exclusive_minimum: Option<OrderedFloat<f64>>,
551    /// The `maxLength` keyword restricts string length to at most this value.
552    /// <https://www.learnjsonschema.com/2020-12/validation/maxLength/>
553    #[serde(rename = "maxLength", skip_serializing_if = "Option::is_none")]
554    pub max_length: Option<u64>,
555    /// The `minLength` keyword restricts string length to at least this value.
556    /// <https://www.learnjsonschema.com/2020-12/validation/minLength/>
557    #[serde(rename = "minLength", skip_serializing_if = "Option::is_none")]
558    pub min_length: Option<u64>,
559    /// The `pattern` keyword restricts strings to those matching this regular expression.
560    /// <https://www.learnjsonschema.com/2020-12/validation/pattern/>
561    #[serde(skip_serializing_if = "Option::is_none")]
562    #[is_empty(if = "is_empty::is_option_really_empty")]
563    pub pattern: Option<String>,
564    /// The `additionalItems` keyword defines the schema for array elements beyond those covered by a tuple definition.
565    /// <https://www.learnjsonschema.com/2020-12/applicator/additionalItems/>
566    #[serde(rename = "additionalItems", skip_serializing_if = "Option::is_none")]
567    #[is_empty(if = "is_empty::is_option_really_empty")]
568    pub additional_items: Option<Schema>,
569    /// The `items` keyword restricts all elements in an array to this schema, or provides a tuple of schemas for positional validation.
570    /// <https://www.learnjsonschema.com/2020-12/applicator/items/>
571    #[serde(skip_serializing_if = "Option::is_none")]
572    #[is_empty(if = "is_empty::is_option_really_empty")]
573    pub items: Option<Schema>,
574    /// The `maxItems` keyword restricts the number of elements in an array to at most this value.
575    /// <https://www.learnjsonschema.com/2020-12/validation/maxItems/>
576    #[serde(rename = "maxItems", skip_serializing_if = "Option::is_none")]
577    pub max_items: Option<u64>,
578    /// The `minItems` keyword restricts the number of elements in an array to at least this value.
579    /// <https://www.learnjsonschema.com/2020-12/validation/minItems/>
580    #[serde(rename = "minItems", skip_serializing_if = "Option::is_none")]
581    pub min_items: Option<u64>,
582    /// The `uniqueItems` keyword ensures that all elements in an array are unique.
583    /// <https://www.learnjsonschema.com/2020-12/validation/uniqueItems/>
584    #[serde(rename = "uniqueItems", skip_serializing_if = "Option::is_none")]
585    #[is_empty(if = "is_opt_bool_empty_with_default_false")]
586    pub unique_items: Option<bool>,
587    /// The `contains` keyword ensures that at least one element in the array matches the specified schema.
588    /// <https://www.learnjsonschema.com/2020-12/applicator/contains/>
589    #[serde(skip_serializing_if = "Option::is_none")]
590    #[is_empty(if = "is_empty::is_option_really_empty")]
591    pub contains: Option<Schema>,
592    /// The `maxProperties` keyword restricts the number of properties in an object to at most this value.
593    /// <https://www.learnjsonschema.com/2020-12/validation/maxProperties/>
594    #[serde(rename = "maxProperties", skip_serializing_if = "Option::is_none")]
595    pub max_properties: Option<u64>,
596    /// The `minProperties` keyword restricts the number of properties in an object to at least this value.
597    /// <https://www.learnjsonschema.com/2020-12/validation/minProperties/>
598    #[serde(rename = "minProperties", skip_serializing_if = "Option::is_none")]
599    pub min_properties: Option<u64>,
600    /// The `maxContains` keyword limits how many items matching `contains` may appear in an array.
601    /// <https://www.learnjsonschema.com/2020-12/applicator/maxContains/>
602    #[serde(rename = "maxContains", skip_serializing_if = "Option::is_none")]
603    pub max_contains: Option<u64>,
604    /// The `minContains` keyword requires at least this many items matching `contains` in an array.
605    /// <https://www.learnjsonschema.com/2020-12/applicator/minContains/>
606    #[serde(rename = "minContains", skip_serializing_if = "Option::is_none")]
607    pub min_contains: Option<u64>,
608    /// The `additionalProperties` keyword defines the schema for object properties not explicitly listed.
609    /// <https://www.learnjsonschema.com/2020-12/applicator/additionalProperties/>
610    #[serde(rename = "additionalProperties", skip_serializing_if = "Option::is_none")]
611    #[is_empty(if = "is_empty::is_option_really_empty")]
612    pub additional_properties: Option<Schema>,
613    /// The `definitions` section holds reusable schema definitions for reference.
614    /// <https://www.learnjsonschema.com/2020-12/meta-data/definitions/>
615    #[serde(skip_serializing_if = "IndexMap::is_empty")]
616    #[builder(default)]
617    #[is_empty(if = "IndexMap::is_empty")]
618    pub definitions: IndexMap<String, Schema>,
619    /// The `patternProperties` keyword maps regex patterns to schemas for matching property names.
620    /// <https://www.learnjsonschema.com/2020-12/applicator/patternProperties/>
621    #[serde(rename = "patternProperties", skip_serializing_if = "IndexMap::is_empty")]
622    #[builder(default)]
623    #[is_empty(if = "IndexMap::is_empty")]
624    pub pattern_properties: IndexMap<String, Schema>,
625    /// The `dependencies` keyword specifies schema or property dependencies for an object.
626    /// <https://www.learnjsonschema.com/2020-12/applicator/dependencies/>
627    #[serde(skip_serializing_if = "IndexMap::is_empty")]
628    #[builder(default)]
629    #[is_empty(if = "IndexMap::is_empty")]
630    pub dependencies: IndexMap<String, Schema>,
631    /// The `propertyNames` keyword restricts all property names in an object to match this schema.
632    /// <https://www.learnjsonschema.com/2020-12/applicator/propertyNames/>
633    #[serde(rename = "propertyNames", skip_serializing_if = "Option::is_none")]
634    #[is_empty(if = "is_empty::is_option_really_empty")]
635    pub property_names: Option<Schema>,
636    /// The `const` keyword requires the instance to be exactly this value.
637    /// <https://www.learnjsonschema.com/2020-12/validation/const/>
638    #[serde(rename = "const", skip_serializing_if = "Option::is_none")]
639    #[builder(name = "const_value")]
640    #[is_empty(if = "is_opt_json_value_empty")]
641    pub const_value: Option<serde_json::Value>,
642    /// The `type` keyword restricts the instance to the specified JSON types.
643    /// <https://www.learnjsonschema.com/2020-12/validation/type/>
644    #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
645    #[builder(name = "schema_type")]
646    pub schema_type: Option<Types>,
647    /// The `format` keyword provides semantic validation hints, such as "email" or "date-time".
648    /// <https://www.learnjsonschema.com/2020-12/meta-data/format/>
649    #[serde(skip_serializing_if = "String::is_empty")]
650    #[builder(default)]
651    pub format: String,
652    /// The `contentMediaType` annotation describes the media type for string content.
653    /// <https://www.learnjsonschema.com/2020-12/annotations/contentMediaType/>
654    #[serde(rename = "contentMediaType", skip_serializing_if = "String::is_empty")]
655    #[builder(default)]
656    pub content_media_type: String,
657    /// The `contentEncoding` annotation describes the encoding (e.g., "base64") for string content.
658    /// <https://www.learnjsonschema.com/2020-12/annotations/contentEncoding/>
659    #[serde(rename = "contentEncoding", skip_serializing_if = "String::is_empty")]
660    #[builder(default)]
661    pub content_encoding: String,
662    /// The `contentSchema` annotation defines a schema for binary media represented as a string.
663    /// <https://www.learnjsonschema.com/2020-12/applicator/contentSchema/>
664    #[serde(rename = "contentSchema", skip_serializing_if = "Option::is_none")]
665    #[is_empty(if = "is_empty::is_option_really_empty")]
666    pub content_schema: Option<Schema>,
667    /// The `if` keyword applies conditional schema validation when this subschema is valid.
668    /// <https://www.learnjsonschema.com/2020-12/applicator/if/>
669    #[serde(rename = "if", skip_serializing_if = "Option::is_none")]
670    #[is_empty(if = "is_empty::is_option_really_empty")]
671    pub if_cond: Option<Schema>,
672    /// The `then` keyword applies this subschema when the `if` condition is met.
673    /// <https://www.learnjsonschema.com/2020-12/applicator/then/>
674    #[serde(skip_serializing_if = "Option::is_none")]
675    #[builder(name = "then_cond")]
676    #[is_empty(if = "is_empty::is_option_really_empty")]
677    pub then: Option<Schema>,
678    /// The `else` keyword applies this subschema when the `if` condition is not met.
679    /// <https://www.learnjsonschema.com/2020-12/applicator/else/>
680    #[serde(rename = "else", skip_serializing_if = "Option::is_none")]
681    #[is_empty(if = "is_empty::is_option_really_empty")]
682    pub else_cond: Option<Schema>,
683    /// The `not` keyword ensures the instance does *not* match this subschema.
684    /// <https://www.learnjsonschema.com/2020-12/applicator/not/>
685    #[serde(skip_serializing_if = "Option::is_none")]
686    #[is_empty(if = "is_empty::is_option_really_empty")]
687    pub not: Option<Schema>,
688    /// The `unevaluatedItems` keyword applies schemas to items not covered by `items` or `contains`.
689    /// <https://www.learnjsonschema.com/2020-12/applicator/unevaluatedItems/>
690    #[serde(rename = "unevaluatedItems", skip_serializing_if = "Option::is_none")]
691    #[is_empty(if = "is_empty::is_option_really_empty")]
692    pub unevaluated_items: Option<Schema>,
693    /// The `unevaluatedProperties` keyword applies schemas to properties not covered by `properties` or pattern-based keywords.
694    /// <https://www.learnjsonschema.com/2020-12/applicator/unevaluatedProperties/>
695    #[serde(rename = "unevaluatedProperties", skip_serializing_if = "Option::is_none")]
696    #[is_empty(if = "is_empty::is_option_really_empty")]
697    pub unevaluated_properties: Option<Schema>,
698    /// The `discriminator` keyword provides object property-based type differentiation (OpenAPI).
699    /// <https://spec.openapis.org/oas/v3.1.0#discriminator-object>
700    #[serde(skip_serializing_if = "Option::is_none")]
701    #[is_empty(if = "is_empty::is_option_really_empty")]
702    pub discriminator: Option<Discriminator>,
703    /// All additional, unrecognized fields are stored here as extensions.
704    #[serde(flatten)]
705    #[is_empty(if = "is_empty::is_option_really_empty")]
706    pub extensions: Option<Extensions>,
707}
708
709impl From<Ref> for Object {
710    fn from(value: Ref) -> Self {
711        Self::builder()
712            .reference(value.ref_location)
713            .description(value.description)
714            .summary(value.summary)
715            .build()
716    }
717}
718
719impl<S: object_builder::State> ObjectBuilder<S> {
720    /// Extend the properties using the iterator of `(name, schema)`
721    pub fn properties<P: Into<String>, C: Into<Schema>>(mut self, properties: impl IntoIterator<Item = (P, C)>) -> Self {
722        self.properties
723            .extend(properties.into_iter().map(|(p, s)| (p.into(), s.into())));
724        self
725    }
726
727    /// Add a singular property
728    pub fn property(mut self, name: impl Into<String>, schema: impl Into<Schema>) -> Self {
729        self.properties.insert(name.into(), schema.into());
730        self
731    }
732
733    /// Add a singular schema into the `allOf` array
734    pub fn all_of(mut self, all_of: impl Into<Schema>) -> Self {
735        self.all_of.push(all_of.into());
736        self
737    }
738
739    /// Extend the `allOf` array using the iterator of schemas
740    pub fn all_ofs<C: Into<Schema>>(mut self, all_ofs: impl IntoIterator<Item = C>) -> Self {
741        self.all_of.extend(all_ofs.into_iter().map(|s| s.into()));
742        self
743    }
744
745    /// Extend the `anyOf` array using the iterator of schemas
746    pub fn any_ofs<C: Into<Schema>>(self, any_ofs: impl IntoIterator<Item = C>) -> Self {
747        any_ofs.into_iter().fold(self, |this, c| this.any_of(c))
748    }
749
750    /// Add a singular schema into the `anyOf` array
751    pub fn any_of(mut self, any_of: impl Into<Schema>) -> Self {
752        self.any_of.get_or_insert_default().push(any_of.into());
753        self
754    }
755
756    /// Extend the `oneOfs` array using the iterator of schemas
757    pub fn one_ofs<C: Into<Schema>>(self, one_ofs: impl IntoIterator<Item = C>) -> Self {
758        one_ofs.into_iter().fold(self, |this, c| this.one_of(c))
759    }
760
761    /// Add a singular schema into the `oneOf` array
762    pub fn one_of(mut self, one_of: impl Into<Schema>) -> Self {
763        self.one_of.get_or_insert_default().push(one_of.into());
764        self
765    }
766
767    /// Add a singular item into the `enum` array
768    pub fn enum_value(mut self, enum_value: impl Into<serde_json::Value>) -> Self {
769        self.enum_values.get_or_insert_default().push(enum_value.into());
770        self
771    }
772
773    /// Extend the `enum` array using an iterator of items
774    pub fn enum_values<E: Into<serde_json::Value>>(self, enum_values: impl IntoIterator<Item = E>) -> Self {
775        enum_values.into_iter().fold(self, |this, e| this.enum_value(e))
776    }
777
778    /// Add a single field into the `required` array
779    pub fn require(mut self, require: impl Into<String>) -> Self {
780        self.required.push(require.into());
781        self
782    }
783
784    /// Extend the `required` array from the iterator of fields.
785    pub fn required<R: Into<String>>(self, required: impl IntoIterator<Item = R>) -> Self {
786        required.into_iter().fold(self, |this, e| this.require(e))
787    }
788
789    /// Add a single example to the `examples` array
790    pub fn example(mut self, example: impl Into<serde_json::Value>) -> Self {
791        self.examples.push(example.into());
792        self
793    }
794
795    /// Extend the `examples` array using an iterator of examples.
796    pub fn examples<E: Into<serde_json::Value>>(self, examples: impl IntoIterator<Item = E>) -> Self {
797        examples.into_iter().fold(self, |this, e| this.example(e))
798    }
799}
800
801impl<S: object_builder::IsComplete> ObjectBuilder<S> {
802    /// Convert the object into an array of this type
803    pub fn to_array(self) -> ObjectBuilder<object_builder::SetItems<object_builder::SetSchemaType>> {
804        Object::builder().schema_type(Type::Array).items(self)
805    }
806}
807
808impl<S: object_builder::IsComplete> From<ObjectBuilder<S>> for Object {
809    fn from(value: ObjectBuilder<S>) -> Self {
810        value.build()
811    }
812}
813
814impl<S: object_builder::IsComplete> From<ObjectBuilder<S>> for Schema {
815    fn from(value: ObjectBuilder<S>) -> Self {
816        value.build().into()
817    }
818}
819
820impl Object {
821    /// Create a new object builder with the schema type.
822    /// Short hand for
823    /// ```rust
824    /// # use openapiv3_1::{Object, schema::Type};
825    /// # let ty = Type::Null;
826    /// # let _ = {
827    /// Object::builder().schema_type(ty)
828    /// # };
829    /// ```
830    pub fn with_type(ty: impl Into<Types>) -> ObjectBuilder<object_builder::SetSchemaType> {
831        Object::builder().schema_type(ty)
832    }
833
834    /// An object that represents an [`i32`]
835    pub fn int32() -> Object {
836        Object::builder()
837            .schema_type(Type::Integer)
838            .maximum(i32::MAX as f64)
839            .minimum(i32::MIN as f64)
840            .build()
841    }
842
843    /// An object that represents an [`i64`]
844    pub fn int64() -> Object {
845        Object::builder()
846            .schema_type(Type::Integer)
847            .maximum(i64::MAX as f64)
848            .minimum(i64::MIN as f64)
849            .build()
850    }
851
852    /// An object that represents an [`u32`]
853    pub fn uint32() -> Object {
854        Object::builder()
855            .schema_type(Type::Integer)
856            .maximum(u32::MAX as f64)
857            .minimum(u32::MIN as f64)
858            .build()
859    }
860
861    /// An object that represents an [`u64`]
862    pub fn uint64() -> Object {
863        Object::builder()
864            .schema_type(Type::Integer)
865            .maximum(u64::MAX as f64)
866            .minimum(u64::MIN as f64)
867            .build()
868    }
869
870    /// Convert the object into an array of that type.
871    pub fn to_array(self) -> Self {
872        Self::builder().schema_type(Type::Array).items(self).build()
873    }
874
875    /// Builds a new object where its an aggregate of all the objects in the iterator.
876    /// Short hand for
877    /// ```rust
878    /// # use openapiv3_1::{Object, schema::Type};
879    /// # let all_ofs = [true];
880    /// # let _ = {
881    /// Object::builder().all_ofs(all_ofs).build()
882    /// # };
883    /// ```
884    pub fn all_ofs<S: Into<Schema>>(all_ofs: impl IntoIterator<Item = S>) -> Object {
885        Object::builder().all_ofs(all_ofs).build()
886    }
887}
888
889macro_rules! iter_chain {
890    ($($item:expr),*$(,)?) => {
891        std::iter::empty()
892            $(.chain($item))*
893    };
894}
895
896macro_rules! merge_item {
897    ([$self:ident, $other:ident] => { $($item:ident => $merge_behaviour:expr),*$(,)? }) => {$({
898        let self_item = &mut $self.$item;
899        let other_item = &mut $other.$item;
900        if self_item.is_empty() {
901            *self_item = std::mem::take(other_item);
902        } else if self_item == other_item {
903            std::mem::take(other_item);
904        } else if !other_item.is_empty() {
905            $merge_behaviour(self_item, other_item);
906        }
907    })*};
908}
909
910fn dedupe_array<T: PartialEq>(items: &mut Vec<T>) {
911    let mut dedupe = Vec::new();
912    for item in items.drain(..) {
913        if !dedupe.contains(&item) {
914            dedupe.push(item);
915        }
916    }
917
918    *items = dedupe;
919}
920
921impl Object {
922    /// Optimize the openapi schema
923    /// This will compress nested `allOfs` and try merge things together.
924    pub fn optimize(&mut self) {
925        // Collect allofs.
926        let mut all_ofs = Vec::new();
927        self.take_all_ofs(&mut all_ofs);
928
929        all_ofs
930            .iter_mut()
931            .filter_map(|schema| schema.as_object_mut())
932            .for_each(|schema| self.merge(schema));
933
934        // recursively call optimize
935        let sub_schemas = iter_chain!(
936            self.schema.iter_mut(),
937            self.additional_items.iter_mut(),
938            self.contains.iter_mut(),
939            self.additional_properties.iter_mut(),
940            self.items.iter_mut(),
941            self.prefix_items.iter_mut().flatten(),
942            self.definitions.values_mut(),
943            self.properties.values_mut(),
944            self.pattern_properties.values_mut(),
945            self.dependencies.values_mut(),
946            self.property_names.iter_mut(),
947            self.if_cond.iter_mut(),
948            self.then.iter_mut(),
949            self.else_cond.iter_mut(),
950            self.any_of.iter_mut().flatten(),
951            self.one_of.iter_mut().flatten(),
952            self.not.iter_mut(),
953            self.unevaluated_items.iter_mut(),
954            self.unevaluated_properties.iter_mut(),
955            self.content_schema.iter_mut(),
956        );
957
958        for schema in sub_schemas {
959            schema.optimize();
960        }
961
962        self.all_of = all_ofs.into_iter().filter(|schema| !schema.is_empty()).collect();
963        dedupe_array(&mut self.examples);
964        dedupe_array(&mut self.required);
965        if let Some(_enum) = &mut self.enum_values {
966            dedupe_array(_enum);
967        }
968        dedupe_array(&mut self.all_of);
969        if let Some(any_of) = &mut self.any_of {
970            dedupe_array(any_of);
971        }
972        if let Some(one_of) = &mut self.one_of {
973            dedupe_array(one_of);
974        }
975    }
976
977    /// Convert the value into an optimized version of itself.
978    pub fn into_optimized(mut self) -> Self {
979        self.optimize();
980        self
981    }
982
983    fn take_all_ofs(&mut self, collection: &mut Vec<Schema>) {
984        for mut schema in self.all_of.drain(..) {
985            schema.take_all_ofs(collection);
986            collection.push(schema);
987        }
988    }
989
990    fn merge(&mut self, other: &mut Self) {
991        merge_item!(
992            [self, other] => {
993                id => merge_skip,
994                schema => merge_sub_schema,
995                reference => merge_skip,
996                comment => merge_drop_second,
997                title => merge_drop_second,
998                description => merge_drop_second,
999                summary => merge_drop_second,
1000                default => merge_drop_second,
1001                read_only => merge_set_true,
1002                examples => merge_array_combine,
1003                multiple_of => merge_multiple_of,
1004                maximum => merge_min,
1005                exclusive_maximum => merge_min,
1006                minimum => merge_max,
1007                exclusive_minimum => merge_min,
1008                max_length => merge_min,
1009                min_length => merge_max,
1010                pattern => merge_skip,
1011                additional_items => merge_sub_schema,
1012                items => merge_sub_schema,
1013                prefix_items => merge_prefix_items,
1014                max_items => merge_min,
1015                min_items => merge_max,
1016                unique_items => merge_set_true,
1017                contains => merge_sub_schema,
1018                max_properties => merge_min,
1019                min_properties => merge_max,
1020                max_contains => merge_min,
1021                min_contains => merge_max,
1022                required => merge_array_combine,
1023                additional_properties => merge_sub_schema,
1024                definitions => merge_schema_map,
1025                properties => merge_schema_map,
1026                pattern_properties => merge_schema_map,
1027                dependencies => merge_schema_map,
1028                property_names => merge_sub_schema,
1029                const_value => merge_skip,
1030                enum_values => merge_array_union_optional,
1031                schema_type => merge_type,
1032                format => merge_skip,
1033                content_media_type => merge_skip,
1034                content_encoding => merge_skip,
1035                // _if
1036                // then
1037                // _else
1038                any_of => merge_array_combine_optional,
1039                one_of => merge_array_combine_optional,
1040                not => merge_inverted_if_possible,
1041                unevaluated_items => merge_sub_schema,
1042                unevaluated_properties => merge_sub_schema,
1043                deprecated => merge_set_true,
1044                write_only => merge_set_true,
1045                content_schema => merge_sub_schema,
1046            }
1047        );
1048    }
1049}
1050
1051fn merge_skip<T>(_: &mut T, _: &mut T) {}
1052
1053fn merge_drop_second<T: Default>(_: &mut T, other: &mut T) {
1054    std::mem::take(other);
1055}
1056
1057fn merge_min<T: Ord + Copy>(value: &mut Option<T>, other: &mut Option<T>) {
1058    let value = value.as_mut().unwrap();
1059    let other = other.take().unwrap();
1060    *value = (*value).min(other);
1061}
1062
1063fn merge_max<T: Ord + Copy>(value: &mut Option<T>, other: &mut Option<T>) {
1064    let value = value.as_mut().unwrap();
1065    let other = other.take().unwrap();
1066    *value = (*value).max(other);
1067}
1068
1069fn merge_set_true(value: &mut Option<bool>, other: &mut Option<bool>) {
1070    other.take();
1071    value.replace(true);
1072}
1073
1074fn merge_sub_schema(value: &mut Option<Schema>, other_opt: &mut Option<Schema>) {
1075    let value = value.as_mut().unwrap();
1076    let mut other = other_opt.take().unwrap();
1077    value.merge(&mut other);
1078    if !other.is_empty() {
1079        other_opt.replace(other);
1080    }
1081}
1082
1083fn merge_inverted_if_possible(value_opt: &mut Option<Schema>, other_opt: &mut Option<Schema>) {
1084    // merging inverted objects is more tricky.
1085    // If they have different "schema" or things like "title", we should
1086    // refrain from "optimization". We can however merge certain
1087    // types, for example {not { enum: [A] }} and {not { enum: [B] }}
1088    // can be merged fully into {not { enum: [A,B] }}.
1089    // If merge is not fully successful, just leave separated.
1090    // There is some risk that we may be merging for example different schemas.
1091
1092    let value = value_opt.as_ref().unwrap();
1093    let other = other_opt.as_ref().unwrap();
1094    if let (Schema::Object(value_obj), Schema::Object(other_obj)) = (value, other) {
1095        let mut self_copy = (*value_obj).clone();
1096        let mut other_copy = (*other_obj).clone();
1097        // This has much more skips, min/max & union/combine are inverted
1098        {
1099            merge_item!(
1100                [self_copy, other_copy] => {
1101                    id => merge_skip,
1102                    schema => merge_skip,
1103                    reference => merge_skip,
1104                    comment => merge_skip,
1105                    title => merge_skip,
1106                    description => merge_skip,
1107                    summary => merge_skip,
1108                    default => merge_skip,
1109                    read_only => merge_skip,
1110                    examples => merge_skip,
1111                    multiple_of => merge_skip,
1112                    maximum => merge_max,
1113                    exclusive_maximum => merge_max,
1114                    minimum => merge_min,
1115                    exclusive_minimum => merge_max,
1116                    max_length => merge_max,
1117                    min_length => merge_min,
1118                    pattern => merge_skip,
1119                    additional_items => merge_skip,
1120                    items => merge_skip,
1121                    prefix_items => merge_skip,
1122                    max_items => merge_max,
1123                    min_items => merge_min,
1124                    unique_items => merge_skip,
1125                    contains => merge_skip,
1126                    max_properties => merge_max,
1127                    min_properties => merge_min,
1128                    max_contains => merge_max,
1129                    min_contains => merge_min,
1130                    required => merge_skip,
1131                    additional_properties => merge_skip,
1132                    definitions => merge_skip,
1133                    properties => merge_skip,
1134                    pattern_properties => merge_skip,
1135                    dependencies => merge_skip,
1136                    property_names => merge_skip,
1137                    const_value => merge_skip,
1138                    enum_values => merge_array_combine_optional,
1139                    schema_type => merge_skip,
1140                    format => merge_skip,
1141                    content_media_type => merge_skip,
1142                    content_encoding => merge_skip,
1143                    // _if
1144                    // then
1145                    // _else
1146                    any_of => merge_array_combine_optional,
1147                    one_of => merge_array_combine_optional,
1148                    not => merge_skip,
1149                    unevaluated_items => merge_skip,
1150                    unevaluated_properties => merge_skip,
1151                    deprecated => merge_skip,
1152                    write_only => merge_skip,
1153                    content_schema => merge_skip,
1154                }
1155            );
1156        }
1157
1158        // Special case -> const can be merged into array of disallowed values.
1159        if other_copy.const_value.is_some() {
1160            let mut disallowed = self_copy.enum_values.unwrap_or_default();
1161            disallowed.push(other_copy.const_value.unwrap());
1162            other_copy.const_value = None;
1163            if self_copy.const_value.is_some() {
1164                disallowed.push(self_copy.const_value.unwrap());
1165                self_copy.const_value = None;
1166            }
1167            disallowed.dedup();
1168            self_copy.enum_values = Some(disallowed);
1169        }
1170
1171        // If other got emptied, we successfully merged all inverted items.
1172        if other_copy.is_empty() {
1173            value_opt.replace(Schema::Object(self_copy));
1174            *other_opt = Default::default();
1175        }
1176    }
1177}
1178
1179fn merge_array_combine<T: PartialEq>(value: &mut Vec<T>, other: &mut Vec<T>) {
1180    value.append(other);
1181}
1182
1183fn merge_array_union<T: PartialEq>(value: &mut Vec<T>, other: &mut Vec<T>) {
1184    let other = std::mem::take(other);
1185    value.retain(|v| other.contains(v));
1186}
1187
1188fn merge_array_union_optional<T: PartialEq>(value: &mut Option<Vec<T>>, other: &mut Option<Vec<T>>) {
1189    merge_array_union(value.as_mut().unwrap(), other.as_mut().unwrap());
1190    if other.as_ref().is_some_and(|o| o.is_empty()) {
1191        other.take();
1192    }
1193}
1194
1195fn merge_array_combine_optional<T: PartialEq>(value: &mut Option<Vec<T>>, other: &mut Option<Vec<T>>) {
1196    merge_array_combine(value.as_mut().unwrap(), other.as_mut().unwrap());
1197    if other.as_ref().is_some_and(|o| o.is_empty()) {
1198        other.take();
1199    }
1200}
1201
1202fn merge_schema_map(value: &mut IndexMap<String, Schema>, other: &mut IndexMap<String, Schema>) {
1203    for (key, mut other) in other.drain(..) {
1204        match value.entry(key) {
1205            indexmap::map::Entry::Occupied(mut value) => {
1206                value.get_mut().merge(&mut other);
1207                if !other.is_empty()
1208                    && let Some(obj) = value.get_mut().as_object_mut()
1209                {
1210                    obj.all_of.push(other);
1211                }
1212            }
1213            indexmap::map::Entry::Vacant(v) => {
1214                v.insert(other);
1215            }
1216        }
1217    }
1218}
1219
1220fn merge_type(value: &mut Option<Types>, other: &mut Option<Types>) {
1221    match (value.as_mut().unwrap(), other.take().unwrap()) {
1222        (Types::Single(s), Types::Single(ref o)) if s != o => {
1223            value.replace(Types::Multi(Vec::new()));
1224        }
1225        (Types::Single(_), Types::Single(_)) => {}
1226        (Types::Multi(s), Types::Multi(ref mut o)) => {
1227            merge_array_union(s, o);
1228        }
1229        (&mut Types::Single(s), Types::Multi(ref o)) | (&mut Types::Multi(ref o), Types::Single(s)) => {
1230            if o.contains(&s) {
1231                value.replace(Types::Single(s));
1232            } else {
1233                value.replace(Types::Multi(Vec::new()));
1234            }
1235        }
1236    }
1237}
1238
1239fn merge_prefix_items(value: &mut Option<Vec<Schema>>, other: &mut Option<Vec<Schema>>) {
1240    let mut other = other.take().unwrap_or_default();
1241    let value = value.as_mut().unwrap();
1242    value.extend(other.drain(value.len()..));
1243    for (value, mut other) in value.iter_mut().zip(other) {
1244        value.merge(&mut other);
1245        if !other.is_empty()
1246            && let Some(obj) = value.as_object_mut()
1247        {
1248            obj.all_of.push(other);
1249        }
1250    }
1251}
1252
1253fn merge_multiple_of(value: &mut Option<OrderedFloat<f64>>, other: &mut Option<OrderedFloat<f64>>) {
1254    let value = value.as_mut().unwrap().as_mut();
1255    let other = other.take().unwrap().into_inner();
1256
1257    fn gcd_f64(mut a: f64, mut b: f64) -> f64 {
1258        a = a.abs();
1259        b = b.abs();
1260        // if either is zero, gcd is the other
1261        if a == 0.0 {
1262            return b;
1263        }
1264        if b == 0.0 {
1265            return a;
1266        }
1267        // Euclid’s algorithm via remainer
1268        while b > 0.0 {
1269            let r = a % b;
1270            a = b;
1271            b = r;
1272        }
1273        a
1274    }
1275
1276    /// lcm(a, b) = |a * b| / gcd(a, b)
1277    fn lcm_f64(a: f64, b: f64) -> f64 {
1278        if a == 0.0 || b == 0.0 {
1279            return 0.0;
1280        }
1281        let g = gcd_f64(a, b);
1282        // (a / g) * b is a bit safer against overflow than a * (b / g)
1283        (a / g * b).abs()
1284    }
1285
1286    *value = lcm_f64(*value, other);
1287}
1288
1289/// A JSON Schema can either be the [`Object`] or a [`bool`]
1290#[derive(serde_derive::Serialize, serde_derive::Deserialize, Clone, PartialEq)]
1291#[cfg_attr(feature = "debug", derive(Debug))]
1292#[serde(untagged)]
1293#[non_exhaustive]
1294pub enum Schema {
1295    /// A json schema object
1296    Object(Box<Object>),
1297    /// A singular boolean value
1298    Bool(bool),
1299}
1300
1301impl From<Object> for Schema {
1302    fn from(value: Object) -> Self {
1303        Self::object(value)
1304    }
1305}
1306
1307impl From<bool> for Schema {
1308    fn from(value: bool) -> Self {
1309        Self::Bool(value)
1310    }
1311}
1312
1313impl IsEmpty for Schema {
1314    fn is_empty(&self) -> bool {
1315        match self {
1316            Self::Bool(result) => *result,
1317            Self::Object(obj) => obj.is_empty(),
1318        }
1319    }
1320}
1321
1322impl Schema {
1323    /// Converts the schema into an array of this type.
1324    pub fn to_array(self) -> Self {
1325        Self::object(Object::builder().schema_type(Type::Array).items(self))
1326    }
1327
1328    /// Optimizes the schema
1329    pub fn optimize(&mut self) {
1330        match self {
1331            Self::Bool(_) => {}
1332            Self::Object(obj) => obj.optimize(),
1333        }
1334    }
1335
1336    /// Converts the schema into an optimized version
1337    pub fn into_optimized(mut self) -> Self {
1338        match &mut self {
1339            Self::Bool(_) => {}
1340            Self::Object(obj) => obj.optimize(),
1341        }
1342        self
1343    }
1344
1345    /// Make a schema from an object
1346    pub fn object(value: impl Into<Object>) -> Self {
1347        Self::Object(value.into().into())
1348    }
1349
1350    fn take_all_ofs(&mut self, collection: &mut Vec<Schema>) {
1351        match self {
1352            Self::Bool(_) => {}
1353            Self::Object(obj) => obj.take_all_ofs(collection),
1354        }
1355    }
1356
1357    fn as_object_mut(&mut self) -> Option<&mut Object> {
1358        match self {
1359            Self::Bool(_) => None,
1360            Self::Object(obj) => Some(obj.as_mut()),
1361        }
1362    }
1363
1364    fn merge(&mut self, other: &mut Self) {
1365        match (self, other) {
1366            (this @ Schema::Bool(false), _) | (this, Schema::Bool(false)) => {
1367                *this = Schema::Bool(false);
1368            }
1369            (this @ Schema::Bool(true), other) => {
1370                std::mem::swap(this, other);
1371            }
1372            (_, Schema::Bool(true)) => {}
1373            (Schema::Object(value), Schema::Object(other)) => {
1374                value.merge(other.as_mut());
1375            }
1376        }
1377    }
1378}
1379
1380#[cfg(test)]
1381#[cfg_attr(coverage_nightly, coverage(off))]
1382mod tests {
1383    use insta::assert_json_snapshot;
1384    use serde_json::{Value, json};
1385
1386    use super::*;
1387    use crate::*;
1388
1389    #[test]
1390    fn create_schema_serializes_json() -> Result<(), serde_json::Error> {
1391        let openapi = OpenApi::builder()
1392            .info(Info::new("My api", "1.0.0"))
1393            .paths(Paths::new())
1394            .components(
1395                Components::builder()
1396                    .schema("Person", Ref::new("#/components/PersonModel"))
1397                    .schema(
1398                        "Credential",
1399                        Schema::from(
1400                            Object::builder()
1401                                .property(
1402                                    "id",
1403                                    Object::builder()
1404                                        .schema_type(Type::Integer)
1405                                        .format("int32")
1406                                        .description("Id of credential")
1407                                        .default(1i32),
1408                                )
1409                                .property(
1410                                    "name",
1411                                    Object::builder().schema_type(Type::String).description("Name of credential"),
1412                                )
1413                                .property(
1414                                    "status",
1415                                    Object::builder()
1416                                        .schema_type(Type::String)
1417                                        .default("Active")
1418                                        .description("Credential status")
1419                                        .enum_values(["Active", "NotActive", "Locked", "Expired"]),
1420                                )
1421                                .property("history", Schema::from(Ref::from_schema_name("UpdateHistory")).to_array())
1422                                .property("tags", Object::builder().schema_type(Type::String).build().to_array()),
1423                        ),
1424                    )
1425                    .build(),
1426            )
1427            .build();
1428
1429        let serialized = serde_json::to_string_pretty(&openapi)?;
1430        println!("serialized json:\n {serialized}");
1431
1432        let value = serde_json::to_value(&openapi)?;
1433        let credential = get_json_path(&value, "components.schemas.Credential.properties");
1434        let person = get_json_path(&value, "components.schemas.Person");
1435
1436        assert!(
1437            credential.get("id").is_some(),
1438            "could not find path: components.schemas.Credential.properties.id"
1439        );
1440        assert!(
1441            credential.get("status").is_some(),
1442            "could not find path: components.schemas.Credential.properties.status"
1443        );
1444        assert!(
1445            credential.get("name").is_some(),
1446            "could not find path: components.schemas.Credential.properties.name"
1447        );
1448        assert!(
1449            credential.get("history").is_some(),
1450            "could not find path: components.schemas.Credential.properties.history"
1451        );
1452
1453        let id = credential.get("id").unwrap().as_object().unwrap();
1454        assert_eq!(
1455            id.get("default").unwrap().as_number().unwrap().as_i64().unwrap(),
1456            1,
1457            "components.schemas.Credential.properties.id.default did not match"
1458        );
1459        assert_eq!(
1460            id.get("description").unwrap().as_str().unwrap(),
1461            "Id of credential",
1462            "components.schemas.Credential.properties.id.description did not match"
1463        );
1464        assert_eq!(
1465            id.get("format").unwrap().as_str().unwrap(),
1466            "int32",
1467            "components.schemas.Credential.properties.id.format did not match"
1468        );
1469        assert_eq!(
1470            id.get("type").unwrap().as_str().unwrap(),
1471            "integer",
1472            "components.schemas.Credential.properties.id.type did not match"
1473        );
1474
1475        let name = credential.get("name").unwrap().as_object().unwrap();
1476        assert_eq!(
1477            name.get("description").unwrap().as_str().unwrap(),
1478            "Name of credential",
1479            "components.schemas.Credential.properties.name.description did not match"
1480        );
1481        assert_eq!(
1482            name.get("type").unwrap().as_str().unwrap(),
1483            "string",
1484            "components.schemas.Credential.properties.name.type did not match"
1485        );
1486
1487        let status = credential.get("status").unwrap().as_object().unwrap();
1488        assert_eq!(
1489            status.get("default").unwrap().as_str().unwrap(),
1490            "Active",
1491            "components.schemas.Credential.properties.status.default did not match"
1492        );
1493        assert_eq!(
1494            status.get("description").unwrap().as_str().unwrap(),
1495            "Credential status",
1496            "components.schemas.Credential.properties.status.description did not match"
1497        );
1498        assert_eq!(
1499            status.get("enum").unwrap().to_string(),
1500            r#"["Active","NotActive","Locked","Expired"]"#,
1501            "components.schemas.Credential.properties.status.enum did not match"
1502        );
1503        assert_eq!(
1504            status.get("type").unwrap().as_str().unwrap(),
1505            "string",
1506            "components.schemas.Credential.properties.status.type did not match"
1507        );
1508
1509        let history = credential.get("history").unwrap().as_object().unwrap();
1510        assert_eq!(
1511            history.get("items").unwrap().to_string(),
1512            r###"{"$ref":"#/components/schemas/UpdateHistory"}"###,
1513            "components.schemas.Credential.properties.history.items did not match"
1514        );
1515        assert_eq!(
1516            history.get("type").unwrap().as_str().unwrap(),
1517            "array",
1518            "components.schemas.Credential.properties.history.type did not match"
1519        );
1520
1521        assert_eq!(
1522            person.to_string(),
1523            r###"{"$ref":"#/components/PersonModel"}"###,
1524            "components.schemas.Person.ref did not match"
1525        );
1526
1527        Ok(())
1528    }
1529
1530    // Examples taken from https://spec.openapis.org/oas/latest.html#model-with-map-dictionary-properties
1531    #[test]
1532    fn test_property_order() {
1533        let json_value = Object::builder()
1534            .property(
1535                "id",
1536                Object::builder()
1537                    .schema_type(Type::Integer)
1538                    .format("int32")
1539                    .description("Id of credential")
1540                    .default(1i32),
1541            )
1542            .property(
1543                "name",
1544                Object::builder().schema_type(Type::String).description("Name of credential"),
1545            )
1546            .property(
1547                "status",
1548                Object::builder()
1549                    .schema_type(Type::String)
1550                    .default("Active")
1551                    .description("Credential status")
1552                    .enum_values(["Active", "NotActive", "Locked", "Expired"]),
1553            )
1554            .property("history", Schema::from(Ref::from_schema_name("UpdateHistory")).to_array())
1555            .property("tags", Object::builder().schema_type(Type::String).to_array())
1556            .build();
1557
1558        assert_eq!(
1559            json_value.properties.keys().collect::<Vec<_>>(),
1560            vec!["id", "name", "status", "history", "tags"]
1561        );
1562    }
1563
1564    // Examples taken from https://spec.openapis.org/oas/latest.html#model-with-map-dictionary-properties
1565    #[test]
1566    fn test_additional_properties() {
1567        let json_value = Object::builder()
1568            .schema_type(Type::Object)
1569            .additional_properties(Object::builder().schema_type(Type::String))
1570            .build();
1571        assert_json_snapshot!(json_value, @r#"
1572        {
1573          "additionalProperties": {
1574            "type": "string"
1575          },
1576          "type": "object"
1577        }
1578        "#);
1579
1580        let json_value = Object::builder()
1581            .schema_type(Type::Object)
1582            .additional_properties(Object::builder().schema_type(Type::Number).to_array())
1583            .build();
1584
1585        assert_json_snapshot!(json_value, @r#"
1586        {
1587          "additionalProperties": {
1588            "items": {
1589              "type": "number"
1590            },
1591            "type": "array"
1592          },
1593          "type": "object"
1594        }
1595        "#);
1596
1597        let json_value = Object::builder()
1598            .schema_type(Type::Object)
1599            .additional_properties(Ref::from_schema_name("ComplexModel"))
1600            .build();
1601        assert_json_snapshot!(json_value, @r##"
1602        {
1603          "additionalProperties": {
1604            "$ref": "#/components/schemas/ComplexModel"
1605          },
1606          "type": "object"
1607        }
1608        "##);
1609    }
1610
1611    #[test]
1612    fn test_object_with_title() {
1613        let json_value = Object::builder().schema_type(Type::Object).title("SomeName").build();
1614        assert_json_snapshot!(json_value, @r#"
1615        {
1616          "title": "SomeName",
1617          "type": "object"
1618        }
1619        "#);
1620    }
1621
1622    #[test]
1623    fn derive_object_with_examples() {
1624        let json_value = Object::builder()
1625            .schema_type(Type::Object)
1626            .examples([json!({"age": 20, "name": "bob the cat"})])
1627            .build();
1628        assert_json_snapshot!(json_value, @r#"
1629        {
1630          "examples": [
1631            {
1632              "age": 20,
1633              "name": "bob the cat"
1634            }
1635          ],
1636          "type": "object"
1637        }
1638        "#);
1639    }
1640
1641    fn get_json_path<'a>(value: &'a Value, path: &str) -> &'a Value {
1642        path.split('.').fold(value, |acc, fragment| {
1643            acc.get(fragment).unwrap_or(&serde_json::value::Value::Null)
1644        })
1645    }
1646
1647    #[test]
1648    fn test_array_new() {
1649        let array = Object::builder()
1650            .property(
1651                "id",
1652                Object::builder()
1653                    .schema_type(Type::Integer)
1654                    .format("int32")
1655                    .description("Id of credential")
1656                    .default(json!(1i32)),
1657            )
1658            .to_array()
1659            .build();
1660
1661        assert!(matches!(array.schema_type, Some(Types::Single(Type::Array))));
1662    }
1663
1664    #[test]
1665    fn test_array_builder() {
1666        let array = Object::builder()
1667            .schema_type(Type::Array)
1668            .items(
1669                Object::builder().property(
1670                    "id",
1671                    Object::builder()
1672                        .schema_type(Type::Integer)
1673                        .format("int32")
1674                        .description("Id of credential")
1675                        .default(1i32),
1676                ),
1677            )
1678            .build();
1679
1680        assert!(matches!(array.schema_type, Some(Types::Single(Type::Array))));
1681    }
1682
1683    #[test]
1684    fn reserialize_deserialized_schema_components() {
1685        let components = Components::builder()
1686            .schemas_from_iter([(
1687                "Comp",
1688                Schema::from(
1689                    Object::builder()
1690                        .property("name", Object::builder().schema_type(Type::String))
1691                        .required(["name"]),
1692                ),
1693            )])
1694            .responses_from_iter(vec![("200", Response::builder().description("Okay").build())])
1695            .security_scheme(
1696                "TLS",
1697                SecurityScheme::MutualTls {
1698                    description: None,
1699                    extensions: None,
1700                },
1701            )
1702            .build();
1703
1704        let serialized_components = serde_json::to_string(&components).unwrap();
1705
1706        let deserialized_components: Components = serde_json::from_str(serialized_components.as_str()).unwrap();
1707
1708        assert_eq!(
1709            serialized_components,
1710            serde_json::to_string(&deserialized_components).unwrap()
1711        )
1712    }
1713
1714    #[test]
1715    fn reserialize_deserialized_object_component() {
1716        let prop = Object::builder()
1717            .property("name", Object::builder().schema_type(Type::String))
1718            .required(["name"])
1719            .build();
1720
1721        let serialized_components = serde_json::to_string(&prop).unwrap();
1722        let deserialized_components: Object = serde_json::from_str(serialized_components.as_str()).unwrap();
1723
1724        assert_eq!(
1725            serialized_components,
1726            serde_json::to_string(&deserialized_components).unwrap()
1727        )
1728    }
1729
1730    #[test]
1731    fn reserialize_deserialized_property() {
1732        let prop = Object::builder().schema_type(Type::String).build();
1733
1734        let serialized_components = serde_json::to_string(&prop).unwrap();
1735        let deserialized_components: Object = serde_json::from_str(serialized_components.as_str()).unwrap();
1736
1737        assert_eq!(
1738            serialized_components,
1739            serde_json::to_string(&deserialized_components).unwrap()
1740        )
1741    }
1742
1743    #[test]
1744    fn deserialize_reserialize_one_of_default_type() {
1745        let a = Object::builder()
1746            .one_ofs([
1747                Object::builder().property("element", Ref::new("#/test")),
1748                Object::builder().property("foobar", Ref::new("#/foobar")),
1749            ])
1750            .build();
1751
1752        let serialized_json = serde_json::to_string(&a).expect("should serialize to json");
1753        let b: Object = serde_json::from_str(&serialized_json).expect("should deserialize OneOf");
1754        let reserialized_json = serde_json::to_string(&b).expect("reserialized json");
1755
1756        println!("{serialized_json}");
1757        println!("{reserialized_json}",);
1758        assert_eq!(serialized_json, reserialized_json);
1759    }
1760
1761    #[test]
1762    fn serialize_deserialize_any_of_of_within_ref_or_t_object_builder() {
1763        let ref_or_schema = Object::builder()
1764            .property(
1765                "test",
1766                Object::builder()
1767                    .any_ofs([
1768                        Object::builder().property("element", Ref::new("#/test")).build().to_array(),
1769                        Object::builder().property("foobar", Ref::new("#/foobar")).build(),
1770                    ])
1771                    .build(),
1772            )
1773            .build();
1774
1775        let json_str = serde_json::to_string(&ref_or_schema).expect("");
1776        println!("----------------------------");
1777        println!("{json_str}");
1778
1779        let deserialized: RefOr<Schema> = serde_json::from_str(&json_str).expect("");
1780
1781        let json_de_str = serde_json::to_string(&deserialized).expect("");
1782        println!("----------------------------");
1783        println!("{json_de_str}");
1784        assert!(json_str.contains("\"anyOf\""));
1785        assert_eq!(json_str, json_de_str);
1786    }
1787
1788    #[test]
1789    fn serialize_deserialize_schema_array_ref_or_t() {
1790        let ref_or_schema = Object::builder()
1791            .property("element", Ref::new("#/test"))
1792            .to_array()
1793            .to_array()
1794            .build();
1795
1796        let json_str = serde_json::to_string(&ref_or_schema).expect("");
1797        println!("----------------------------");
1798        println!("{json_str}");
1799
1800        let deserialized: RefOr<Schema> = serde_json::from_str(&json_str).expect("");
1801
1802        let json_de_str = serde_json::to_string(&deserialized).expect("");
1803        println!("----------------------------");
1804        println!("{json_de_str}");
1805
1806        assert_eq!(json_str, json_de_str);
1807    }
1808
1809    #[test]
1810    fn serialize_deserialize_schema_array_builder() {
1811        let ref_or_schema = Object::builder().property("element", Ref::new("#/test")).build().to_array();
1812
1813        let json_str = serde_json::to_string(&ref_or_schema).expect("");
1814        println!("----------------------------");
1815        println!("{json_str}");
1816
1817        let deserialized: RefOr<Schema> = serde_json::from_str(&json_str).expect("");
1818
1819        let json_de_str = serde_json::to_string(&deserialized).expect("");
1820        println!("----------------------------");
1821        println!("{json_de_str}");
1822
1823        assert_eq!(json_str, json_de_str);
1824    }
1825
1826    #[test]
1827    fn serialize_deserialize_schema_with_additional_properties() {
1828        let schema = Object::builder()
1829            .property("map", Object::builder().additional_properties(true))
1830            .build();
1831
1832        let json_str = serde_json::to_string(&schema).unwrap();
1833        println!("----------------------------");
1834        println!("{json_str}");
1835
1836        let deserialized: RefOr<Schema> = serde_json::from_str(&json_str).unwrap();
1837
1838        let json_de_str = serde_json::to_string(&deserialized).unwrap();
1839        println!("----------------------------");
1840        println!("{json_de_str}");
1841
1842        assert_eq!(json_str, json_de_str);
1843    }
1844
1845    #[test]
1846    fn serialize_deserialize_schema_with_additional_properties_object() {
1847        let schema = Object::builder()
1848            .property(
1849                "map",
1850                Object::builder()
1851                    .additional_properties(Object::builder().property("name", Object::builder().schema_type(Type::String))),
1852            )
1853            .build();
1854
1855        let json_str = serde_json::to_string(&schema).unwrap();
1856        println!("----------------------------");
1857        println!("{json_str}");
1858
1859        let deserialized: RefOr<Schema> = serde_json::from_str(&json_str).unwrap();
1860
1861        let json_de_str = serde_json::to_string(&deserialized).unwrap();
1862        println!("----------------------------");
1863        println!("{json_de_str}");
1864
1865        assert_eq!(json_str, json_de_str);
1866    }
1867
1868    #[test]
1869    fn serialize_discriminator_with_mapping() {
1870        let mut discriminator = Discriminator::new("type");
1871        discriminator.mapping = [("int".to_string(), "#/components/schemas/MyInt".to_string())]
1872            .into_iter()
1873            .collect::<IndexMap<_, _>>();
1874        let one_of = Object::builder()
1875            .one_of(Ref::from_schema_name("MyInt"))
1876            .discriminator(discriminator)
1877            .build();
1878        assert_json_snapshot!(one_of, @r##"
1879        {
1880          "oneOf": [
1881            {
1882              "$ref": "#/components/schemas/MyInt"
1883            }
1884          ],
1885          "discriminator": {
1886            "propertyName": "type",
1887            "mapping": {
1888              "int": "#/components/schemas/MyInt"
1889            }
1890          }
1891        }
1892        "##);
1893    }
1894
1895    #[test]
1896    fn serialize_deserialize_object_with_multiple_schema_types() {
1897        let object = Object::builder().schema_type(vec![Type::Object, Type::Null]).build();
1898
1899        let json_str = serde_json::to_string(&object).unwrap();
1900        println!("----------------------------");
1901        println!("{json_str}");
1902
1903        let deserialized: Object = serde_json::from_str(&json_str).unwrap();
1904
1905        let json_de_str = serde_json::to_string(&deserialized).unwrap();
1906        println!("----------------------------");
1907        println!("{json_de_str}");
1908
1909        assert_eq!(json_str, json_de_str);
1910    }
1911
1912    #[test]
1913    fn object_with_extensions() {
1914        let expected = json!("value");
1915        let extensions = extensions::Extensions::default().add("x-some-extension", expected.clone());
1916        let json_value = Object::builder().extensions(extensions).build();
1917
1918        let value = serde_json::to_value(&json_value).unwrap();
1919        assert_eq!(value.get("x-some-extension"), Some(&expected));
1920    }
1921
1922    #[test]
1923    fn array_with_extensions() {
1924        let expected = json!("value");
1925        let extensions = extensions::Extensions::default().add("x-some-extension", expected.clone());
1926        let json_value = Object::builder().extensions(extensions).to_array().build();
1927
1928        let value = serde_json::to_value(&json_value).unwrap();
1929        assert_eq!(value["items"].get("x-some-extension"), Some(&expected));
1930    }
1931
1932    #[test]
1933    fn oneof_with_extensions() {
1934        let expected = json!("value");
1935        let extensions = extensions::Extensions::default().add("x-some-extension", expected.clone());
1936        let json_value = Object::builder()
1937            .one_of(Object::builder().extensions(extensions).build())
1938            .build();
1939
1940        let value = serde_json::to_value(&json_value).unwrap();
1941        assert_eq!(value["oneOf"][0].get("x-some-extension"), Some(&expected));
1942    }
1943
1944    #[test]
1945    fn allof_with_extensions() {
1946        let expected = json!("value");
1947        let extensions = extensions::Extensions::default().add("x-some-extension", expected.clone());
1948        let json_value = Object::builder()
1949            .all_of(Object::builder().extensions(extensions).build())
1950            .build();
1951
1952        let value = serde_json::to_value(&json_value).unwrap();
1953        assert_eq!(value["allOf"][0].get("x-some-extension"), Some(&expected));
1954    }
1955
1956    #[test]
1957    fn anyof_with_extensions() {
1958        let expected = json!("value");
1959        let extensions = extensions::Extensions::default().add("x-some-extension", expected.clone());
1960        let json_value = Object::builder()
1961            .any_of(Object::builder().extensions(extensions).build())
1962            .build();
1963
1964        let value = serde_json::to_value(&json_value).unwrap();
1965        assert_eq!(value["anyOf"][0].get("x-some-extension"), Some(&expected));
1966    }
1967
1968    #[test]
1969    fn merge_objects_with_not_enum_values() {
1970        let main_obj = Schema::object(
1971            Object::builder()
1972                .one_ofs([
1973                    Schema::object(Object::builder().schema_type(Type::Number).build()),
1974                    Schema::object(
1975                        Object::builder()
1976                            .schema_type(Type::String)
1977                            .enum_values(vec![
1978                                serde_json::Value::from("Infinity"),
1979                                serde_json::Value::from("-Infinity"),
1980                                serde_json::Value::from("NaN"),
1981                            ])
1982                            .build(),
1983                    ),
1984                ])
1985                .build(),
1986        );
1987
1988        let not_nan = Schema::object(
1989            Object::builder()
1990                .not(Schema::object(
1991                    Object::builder()
1992                        .schema_type(Type::String)
1993                        .enum_values(vec![serde_json::Value::from("NaN")])
1994                        .build(),
1995                ))
1996                .build(),
1997        );
1998
1999        let not_infinity = Schema::object(
2000            Object::builder()
2001                .not(Schema::object(
2002                    Object::builder()
2003                        .schema_type(Type::String)
2004                        .enum_values(vec![serde_json::Value::from("Infinity")])
2005                        .build(),
2006                ))
2007                .build(),
2008        );
2009
2010        let schemas = vec![main_obj, not_nan, not_infinity];
2011        let merged = Object::all_ofs(schemas).into_optimized();
2012
2013        assert_json_snapshot!(merged, @r#"
2014        {
2015          "oneOf": [
2016            {
2017              "type": "number"
2018            },
2019            {
2020              "enum": [
2021                "Infinity",
2022                "-Infinity",
2023                "NaN"
2024              ],
2025              "type": "string"
2026            }
2027          ],
2028          "not": {
2029            "enum": [
2030              "NaN",
2031              "Infinity"
2032            ],
2033            "type": "string"
2034          }
2035        }
2036        "#);
2037    }
2038
2039    #[test]
2040    fn merge_objects_with_not_consts() {
2041        let not_a = Schema::object(
2042            Object::builder()
2043                .not(Schema::object(
2044                    Object::builder()
2045                        .schema_type(Type::String)
2046                        .const_value(serde_json::Value::from("A"))
2047                        .build(),
2048                ))
2049                .build(),
2050        );
2051
2052        let not_b = Schema::object(
2053            Object::builder()
2054                .not(Schema::object(
2055                    Object::builder()
2056                        .schema_type(Type::String)
2057                        .const_value(serde_json::Value::from("B"))
2058                        .build(),
2059                ))
2060                .build(),
2061        );
2062
2063        let schemas = vec![not_a, not_b];
2064        let merged = Object::all_ofs(schemas).into_optimized();
2065
2066        assert_json_snapshot!(merged, @r#"
2067        {
2068          "not": {
2069            "enum": [
2070              "B",
2071              "A"
2072            ],
2073            "type": "string"
2074          }
2075        }
2076        "#);
2077    }
2078
2079    #[test]
2080    fn dont_merge_objects_with_not_if_impossible() {
2081        let not_format_a = Schema::object(
2082            Object::builder()
2083                .not(Schema::object(
2084                    Object::builder().schema_type(Type::String).format("email").build(),
2085                ))
2086                .build(),
2087        );
2088
2089        let not_format_b = Schema::object(
2090            Object::builder()
2091                .not(Schema::object(
2092                    Object::builder().schema_type(Type::String).format("date-time").build(),
2093                ))
2094                .build(),
2095        );
2096
2097        let not_format_c = Schema::object(
2098            Object::builder()
2099                .not(Schema::object(
2100                    Object::builder().schema_type(Type::String).format("ipv4").build(),
2101                ))
2102                .build(),
2103        );
2104
2105        let schemas = vec![not_format_a, not_format_b, not_format_c];
2106        let merged = Object::all_ofs(schemas).into_optimized();
2107
2108        assert_json_snapshot!(merged, @r#"
2109        {
2110          "allOf": [
2111            {
2112              "not": {
2113                "type": "string",
2114                "format": "date-time"
2115              }
2116            },
2117            {
2118              "not": {
2119                "type": "string",
2120                "format": "ipv4"
2121              }
2122            }
2123          ],
2124          "not": {
2125            "type": "string",
2126            "format": "email"
2127          }
2128        }
2129        "#);
2130    }
2131
2132    #[test]
2133    fn is_empty_works_parsed_from_json() {
2134        let schema: Schema = serde_json::from_str("{}").unwrap();
2135
2136        assert!(schema.is_empty());
2137    }
2138}