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        assert_eq!(
1451            credential.get("id").unwrap_or(&serde_json::value::Value::Null).to_string(),
1452            r#"{"default":1,"description":"Id of credential","format":"int32","type":"integer"}"#,
1453            "components.schemas.Credential.properties.id did not match"
1454        );
1455        assert_eq!(
1456            credential.get("name").unwrap_or(&serde_json::value::Value::Null).to_string(),
1457            r#"{"description":"Name of credential","type":"string"}"#,
1458            "components.schemas.Credential.properties.name did not match"
1459        );
1460        assert_eq!(
1461            credential
1462                .get("status")
1463                .unwrap_or(&serde_json::value::Value::Null)
1464                .to_string(),
1465            r#"{"default":"Active","description":"Credential status","enum":["Active","NotActive","Locked","Expired"],"type":"string"}"#,
1466            "components.schemas.Credential.properties.status did not match"
1467        );
1468        assert_eq!(
1469            credential
1470                .get("history")
1471                .unwrap_or(&serde_json::value::Value::Null)
1472                .to_string(),
1473            r###"{"items":{"$ref":"#/components/schemas/UpdateHistory"},"type":"array"}"###,
1474            "components.schemas.Credential.properties.history did not match"
1475        );
1476        assert_eq!(
1477            person.to_string(),
1478            r###"{"$ref":"#/components/PersonModel"}"###,
1479            "components.schemas.Person.ref did not match"
1480        );
1481
1482        Ok(())
1483    }
1484
1485    // Examples taken from https://spec.openapis.org/oas/latest.html#model-with-map-dictionary-properties
1486    #[test]
1487    fn test_property_order() {
1488        let json_value = Object::builder()
1489            .property(
1490                "id",
1491                Object::builder()
1492                    .schema_type(Type::Integer)
1493                    .format("int32")
1494                    .description("Id of credential")
1495                    .default(1i32),
1496            )
1497            .property(
1498                "name",
1499                Object::builder().schema_type(Type::String).description("Name of credential"),
1500            )
1501            .property(
1502                "status",
1503                Object::builder()
1504                    .schema_type(Type::String)
1505                    .default("Active")
1506                    .description("Credential status")
1507                    .enum_values(["Active", "NotActive", "Locked", "Expired"]),
1508            )
1509            .property("history", Schema::from(Ref::from_schema_name("UpdateHistory")).to_array())
1510            .property("tags", Object::builder().schema_type(Type::String).to_array())
1511            .build();
1512
1513        assert_eq!(
1514            json_value.properties.keys().collect::<Vec<_>>(),
1515            vec!["id", "name", "status", "history", "tags"]
1516        );
1517    }
1518
1519    // Examples taken from https://spec.openapis.org/oas/latest.html#model-with-map-dictionary-properties
1520    #[test]
1521    fn test_additional_properties() {
1522        let json_value = Object::builder()
1523            .schema_type(Type::Object)
1524            .additional_properties(Object::builder().schema_type(Type::String))
1525            .build();
1526        assert_json_snapshot!(json_value, @r#"
1527        {
1528          "additionalProperties": {
1529            "type": "string"
1530          },
1531          "type": "object"
1532        }
1533        "#);
1534
1535        let json_value = Object::builder()
1536            .schema_type(Type::Object)
1537            .additional_properties(Object::builder().schema_type(Type::Number).to_array())
1538            .build();
1539
1540        assert_json_snapshot!(json_value, @r#"
1541        {
1542          "additionalProperties": {
1543            "items": {
1544              "type": "number"
1545            },
1546            "type": "array"
1547          },
1548          "type": "object"
1549        }
1550        "#);
1551
1552        let json_value = Object::builder()
1553            .schema_type(Type::Object)
1554            .additional_properties(Ref::from_schema_name("ComplexModel"))
1555            .build();
1556        assert_json_snapshot!(json_value, @r##"
1557        {
1558          "additionalProperties": {
1559            "$ref": "#/components/schemas/ComplexModel"
1560          },
1561          "type": "object"
1562        }
1563        "##);
1564    }
1565
1566    #[test]
1567    fn test_object_with_title() {
1568        let json_value = Object::builder().schema_type(Type::Object).title("SomeName").build();
1569        assert_json_snapshot!(json_value, @r#"
1570        {
1571          "title": "SomeName",
1572          "type": "object"
1573        }
1574        "#);
1575    }
1576
1577    #[test]
1578    fn derive_object_with_examples() {
1579        let json_value = Object::builder()
1580            .schema_type(Type::Object)
1581            .examples([json!({"age": 20, "name": "bob the cat"})])
1582            .build();
1583        assert_json_snapshot!(json_value, @r#"
1584        {
1585          "examples": [
1586            {
1587              "age": 20,
1588              "name": "bob the cat"
1589            }
1590          ],
1591          "type": "object"
1592        }
1593        "#);
1594    }
1595
1596    fn get_json_path<'a>(value: &'a Value, path: &str) -> &'a Value {
1597        path.split('.').fold(value, |acc, fragment| {
1598            acc.get(fragment).unwrap_or(&serde_json::value::Value::Null)
1599        })
1600    }
1601
1602    #[test]
1603    fn test_array_new() {
1604        let array = Object::builder()
1605            .property(
1606                "id",
1607                Object::builder()
1608                    .schema_type(Type::Integer)
1609                    .format("int32")
1610                    .description("Id of credential")
1611                    .default(json!(1i32)),
1612            )
1613            .to_array()
1614            .build();
1615
1616        assert!(matches!(array.schema_type, Some(Types::Single(Type::Array))));
1617    }
1618
1619    #[test]
1620    fn test_array_builder() {
1621        let array = Object::builder()
1622            .schema_type(Type::Array)
1623            .items(
1624                Object::builder().property(
1625                    "id",
1626                    Object::builder()
1627                        .schema_type(Type::Integer)
1628                        .format("int32")
1629                        .description("Id of credential")
1630                        .default(1i32),
1631                ),
1632            )
1633            .build();
1634
1635        assert!(matches!(array.schema_type, Some(Types::Single(Type::Array))));
1636    }
1637
1638    #[test]
1639    fn reserialize_deserialized_schema_components() {
1640        let components = Components::builder()
1641            .schemas_from_iter([(
1642                "Comp",
1643                Schema::from(
1644                    Object::builder()
1645                        .property("name", Object::builder().schema_type(Type::String))
1646                        .required(["name"]),
1647                ),
1648            )])
1649            .responses_from_iter(vec![("200", Response::builder().description("Okay").build())])
1650            .security_scheme(
1651                "TLS",
1652                SecurityScheme::MutualTls {
1653                    description: None,
1654                    extensions: None,
1655                },
1656            )
1657            .build();
1658
1659        let serialized_components = serde_json::to_string(&components).unwrap();
1660
1661        let deserialized_components: Components = serde_json::from_str(serialized_components.as_str()).unwrap();
1662
1663        assert_eq!(
1664            serialized_components,
1665            serde_json::to_string(&deserialized_components).unwrap()
1666        )
1667    }
1668
1669    #[test]
1670    fn reserialize_deserialized_object_component() {
1671        let prop = Object::builder()
1672            .property("name", Object::builder().schema_type(Type::String))
1673            .required(["name"])
1674            .build();
1675
1676        let serialized_components = serde_json::to_string(&prop).unwrap();
1677        let deserialized_components: Object = serde_json::from_str(serialized_components.as_str()).unwrap();
1678
1679        assert_eq!(
1680            serialized_components,
1681            serde_json::to_string(&deserialized_components).unwrap()
1682        )
1683    }
1684
1685    #[test]
1686    fn reserialize_deserialized_property() {
1687        let prop = Object::builder().schema_type(Type::String).build();
1688
1689        let serialized_components = serde_json::to_string(&prop).unwrap();
1690        let deserialized_components: Object = serde_json::from_str(serialized_components.as_str()).unwrap();
1691
1692        assert_eq!(
1693            serialized_components,
1694            serde_json::to_string(&deserialized_components).unwrap()
1695        )
1696    }
1697
1698    #[test]
1699    fn deserialize_reserialize_one_of_default_type() {
1700        let a = Object::builder()
1701            .one_ofs([
1702                Object::builder().property("element", Ref::new("#/test")),
1703                Object::builder().property("foobar", Ref::new("#/foobar")),
1704            ])
1705            .build();
1706
1707        let serialized_json = serde_json::to_string(&a).expect("should serialize to json");
1708        let b: Object = serde_json::from_str(&serialized_json).expect("should deserialize OneOf");
1709        let reserialized_json = serde_json::to_string(&b).expect("reserialized json");
1710
1711        println!("{serialized_json}");
1712        println!("{reserialized_json}",);
1713        assert_eq!(serialized_json, reserialized_json);
1714    }
1715
1716    #[test]
1717    fn serialize_deserialize_any_of_of_within_ref_or_t_object_builder() {
1718        let ref_or_schema = Object::builder()
1719            .property(
1720                "test",
1721                Object::builder()
1722                    .any_ofs([
1723                        Object::builder().property("element", Ref::new("#/test")).build().to_array(),
1724                        Object::builder().property("foobar", Ref::new("#/foobar")).build(),
1725                    ])
1726                    .build(),
1727            )
1728            .build();
1729
1730        let json_str = serde_json::to_string(&ref_or_schema).expect("");
1731        println!("----------------------------");
1732        println!("{json_str}");
1733
1734        let deserialized: RefOr<Schema> = serde_json::from_str(&json_str).expect("");
1735
1736        let json_de_str = serde_json::to_string(&deserialized).expect("");
1737        println!("----------------------------");
1738        println!("{json_de_str}");
1739        assert!(json_str.contains("\"anyOf\""));
1740        assert_eq!(json_str, json_de_str);
1741    }
1742
1743    #[test]
1744    fn serialize_deserialize_schema_array_ref_or_t() {
1745        let ref_or_schema = Object::builder()
1746            .property("element", Ref::new("#/test"))
1747            .to_array()
1748            .to_array()
1749            .build();
1750
1751        let json_str = serde_json::to_string(&ref_or_schema).expect("");
1752        println!("----------------------------");
1753        println!("{json_str}");
1754
1755        let deserialized: RefOr<Schema> = serde_json::from_str(&json_str).expect("");
1756
1757        let json_de_str = serde_json::to_string(&deserialized).expect("");
1758        println!("----------------------------");
1759        println!("{json_de_str}");
1760
1761        assert_eq!(json_str, json_de_str);
1762    }
1763
1764    #[test]
1765    fn serialize_deserialize_schema_array_builder() {
1766        let ref_or_schema = Object::builder().property("element", Ref::new("#/test")).build().to_array();
1767
1768        let json_str = serde_json::to_string(&ref_or_schema).expect("");
1769        println!("----------------------------");
1770        println!("{json_str}");
1771
1772        let deserialized: RefOr<Schema> = serde_json::from_str(&json_str).expect("");
1773
1774        let json_de_str = serde_json::to_string(&deserialized).expect("");
1775        println!("----------------------------");
1776        println!("{json_de_str}");
1777
1778        assert_eq!(json_str, json_de_str);
1779    }
1780
1781    #[test]
1782    fn serialize_deserialize_schema_with_additional_properties() {
1783        let schema = Object::builder()
1784            .property("map", Object::builder().additional_properties(true))
1785            .build();
1786
1787        let json_str = serde_json::to_string(&schema).unwrap();
1788        println!("----------------------------");
1789        println!("{json_str}");
1790
1791        let deserialized: RefOr<Schema> = serde_json::from_str(&json_str).unwrap();
1792
1793        let json_de_str = serde_json::to_string(&deserialized).unwrap();
1794        println!("----------------------------");
1795        println!("{json_de_str}");
1796
1797        assert_eq!(json_str, json_de_str);
1798    }
1799
1800    #[test]
1801    fn serialize_deserialize_schema_with_additional_properties_object() {
1802        let schema = Object::builder()
1803            .property(
1804                "map",
1805                Object::builder()
1806                    .additional_properties(Object::builder().property("name", Object::builder().schema_type(Type::String))),
1807            )
1808            .build();
1809
1810        let json_str = serde_json::to_string(&schema).unwrap();
1811        println!("----------------------------");
1812        println!("{json_str}");
1813
1814        let deserialized: RefOr<Schema> = serde_json::from_str(&json_str).unwrap();
1815
1816        let json_de_str = serde_json::to_string(&deserialized).unwrap();
1817        println!("----------------------------");
1818        println!("{json_de_str}");
1819
1820        assert_eq!(json_str, json_de_str);
1821    }
1822
1823    #[test]
1824    fn serialize_discriminator_with_mapping() {
1825        let mut discriminator = Discriminator::new("type");
1826        discriminator.mapping = [("int".to_string(), "#/components/schemas/MyInt".to_string())]
1827            .into_iter()
1828            .collect::<IndexMap<_, _>>();
1829        let one_of = Object::builder()
1830            .one_of(Ref::from_schema_name("MyInt"))
1831            .discriminator(discriminator)
1832            .build();
1833        assert_json_snapshot!(one_of, @r##"
1834        {
1835          "oneOf": [
1836            {
1837              "$ref": "#/components/schemas/MyInt"
1838            }
1839          ],
1840          "discriminator": {
1841            "propertyName": "type",
1842            "mapping": {
1843              "int": "#/components/schemas/MyInt"
1844            }
1845          }
1846        }
1847        "##);
1848    }
1849
1850    #[test]
1851    fn serialize_deserialize_object_with_multiple_schema_types() {
1852        let object = Object::builder().schema_type(vec![Type::Object, Type::Null]).build();
1853
1854        let json_str = serde_json::to_string(&object).unwrap();
1855        println!("----------------------------");
1856        println!("{json_str}");
1857
1858        let deserialized: Object = serde_json::from_str(&json_str).unwrap();
1859
1860        let json_de_str = serde_json::to_string(&deserialized).unwrap();
1861        println!("----------------------------");
1862        println!("{json_de_str}");
1863
1864        assert_eq!(json_str, json_de_str);
1865    }
1866
1867    #[test]
1868    fn object_with_extensions() {
1869        let expected = json!("value");
1870        let extensions = extensions::Extensions::default().add("x-some-extension", expected.clone());
1871        let json_value = Object::builder().extensions(extensions).build();
1872
1873        let value = serde_json::to_value(&json_value).unwrap();
1874        assert_eq!(value.get("x-some-extension"), Some(&expected));
1875    }
1876
1877    #[test]
1878    fn array_with_extensions() {
1879        let expected = json!("value");
1880        let extensions = extensions::Extensions::default().add("x-some-extension", expected.clone());
1881        let json_value = Object::builder().extensions(extensions).to_array().build();
1882
1883        let value = serde_json::to_value(&json_value).unwrap();
1884        assert_eq!(value["items"].get("x-some-extension"), Some(&expected));
1885    }
1886
1887    #[test]
1888    fn oneof_with_extensions() {
1889        let expected = json!("value");
1890        let extensions = extensions::Extensions::default().add("x-some-extension", expected.clone());
1891        let json_value = Object::builder()
1892            .one_of(Object::builder().extensions(extensions).build())
1893            .build();
1894
1895        let value = serde_json::to_value(&json_value).unwrap();
1896        assert_eq!(value["oneOf"][0].get("x-some-extension"), Some(&expected));
1897    }
1898
1899    #[test]
1900    fn allof_with_extensions() {
1901        let expected = json!("value");
1902        let extensions = extensions::Extensions::default().add("x-some-extension", expected.clone());
1903        let json_value = Object::builder()
1904            .all_of(Object::builder().extensions(extensions).build())
1905            .build();
1906
1907        let value = serde_json::to_value(&json_value).unwrap();
1908        assert_eq!(value["allOf"][0].get("x-some-extension"), Some(&expected));
1909    }
1910
1911    #[test]
1912    fn anyof_with_extensions() {
1913        let expected = json!("value");
1914        let extensions = extensions::Extensions::default().add("x-some-extension", expected.clone());
1915        let json_value = Object::builder()
1916            .any_of(Object::builder().extensions(extensions).build())
1917            .build();
1918
1919        let value = serde_json::to_value(&json_value).unwrap();
1920        assert_eq!(value["anyOf"][0].get("x-some-extension"), Some(&expected));
1921    }
1922
1923    #[test]
1924    fn merge_objects_with_not_enum_values() {
1925        let main_obj = Schema::object(
1926            Object::builder()
1927                .one_ofs([
1928                    Schema::object(Object::builder().schema_type(Type::Number).build()),
1929                    Schema::object(
1930                        Object::builder()
1931                            .schema_type(Type::String)
1932                            .enum_values(vec![
1933                                serde_json::Value::from("Infinity"),
1934                                serde_json::Value::from("-Infinity"),
1935                                serde_json::Value::from("NaN"),
1936                            ])
1937                            .build(),
1938                    ),
1939                ])
1940                .build(),
1941        );
1942
1943        let not_nan = Schema::object(
1944            Object::builder()
1945                .not(Schema::object(
1946                    Object::builder()
1947                        .schema_type(Type::String)
1948                        .enum_values(vec![serde_json::Value::from("NaN")])
1949                        .build(),
1950                ))
1951                .build(),
1952        );
1953
1954        let not_infinity = Schema::object(
1955            Object::builder()
1956                .not(Schema::object(
1957                    Object::builder()
1958                        .schema_type(Type::String)
1959                        .enum_values(vec![serde_json::Value::from("Infinity")])
1960                        .build(),
1961                ))
1962                .build(),
1963        );
1964
1965        let schemas = vec![main_obj, not_nan, not_infinity];
1966        let merged = Object::all_ofs(schemas).into_optimized();
1967
1968        assert_json_snapshot!(merged, @r#"
1969        {
1970          "oneOf": [
1971            {
1972              "type": "number"
1973            },
1974            {
1975              "enum": [
1976                "Infinity",
1977                "-Infinity",
1978                "NaN"
1979              ],
1980              "type": "string"
1981            }
1982          ],
1983          "not": {
1984            "enum": [
1985              "NaN",
1986              "Infinity"
1987            ],
1988            "type": "string"
1989          }
1990        }
1991        "#);
1992    }
1993
1994    #[test]
1995    fn merge_objects_with_not_consts() {
1996        let not_a = Schema::object(
1997            Object::builder()
1998                .not(Schema::object(
1999                    Object::builder()
2000                        .schema_type(Type::String)
2001                        .const_value(serde_json::Value::from("A"))
2002                        .build(),
2003                ))
2004                .build(),
2005        );
2006
2007        let not_b = Schema::object(
2008            Object::builder()
2009                .not(Schema::object(
2010                    Object::builder()
2011                        .schema_type(Type::String)
2012                        .const_value(serde_json::Value::from("B"))
2013                        .build(),
2014                ))
2015                .build(),
2016        );
2017
2018        let schemas = vec![not_a, not_b];
2019        let merged = Object::all_ofs(schemas).into_optimized();
2020
2021        assert_json_snapshot!(merged, @r#"
2022        {
2023          "not": {
2024            "enum": [
2025              "B",
2026              "A"
2027            ],
2028            "type": "string"
2029          }
2030        }
2031        "#);
2032    }
2033
2034    #[test]
2035    fn dont_merge_objects_with_not_if_impossible() {
2036        let not_format_a = Schema::object(
2037            Object::builder()
2038                .not(Schema::object(
2039                    Object::builder().schema_type(Type::String).format("email").build(),
2040                ))
2041                .build(),
2042        );
2043
2044        let not_format_b = Schema::object(
2045            Object::builder()
2046                .not(Schema::object(
2047                    Object::builder().schema_type(Type::String).format("date-time").build(),
2048                ))
2049                .build(),
2050        );
2051
2052        let not_format_c = Schema::object(
2053            Object::builder()
2054                .not(Schema::object(
2055                    Object::builder().schema_type(Type::String).format("ipv4").build(),
2056                ))
2057                .build(),
2058        );
2059
2060        let schemas = vec![not_format_a, not_format_b, not_format_c];
2061        let merged = Object::all_ofs(schemas).into_optimized();
2062
2063        assert_json_snapshot!(merged, @r#"
2064        {
2065          "allOf": [
2066            {
2067              "not": {
2068                "type": "string",
2069                "format": "date-time"
2070              }
2071            },
2072            {
2073              "not": {
2074                "type": "string",
2075                "format": "ipv4"
2076              }
2077            }
2078          ],
2079          "not": {
2080            "type": "string",
2081            "format": "email"
2082          }
2083        }
2084        "#);
2085    }
2086
2087    #[test]
2088    fn is_empty_works_parsed_from_json() {
2089        let schema: Schema = serde_json::from_str("{}").unwrap();
2090
2091        assert!(schema.is_empty());
2092    }
2093}