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