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