Skip to main content

kynos_openapi/model/schema/
object.rs

1//! The keyword-carrying form of a Schema Object.
2
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5
6use crate::{
7    Map,
8    model::{
9        external_docs::ExternalDocumentation,
10        schema::{Schema, discriminator::Discriminator, types::TypeSet, xml::Xml},
11    },
12};
13
14/// A schema with keywords.
15///
16/// Keywords are grouped below in the order the JSON Schema 2020-12
17/// specification presents them: core, applicator, unevaluated, validation,
18/// format, content, metadata; then the four keywords of the OAS base
19/// vocabulary.
20///
21/// Unrecognized keywords are preserved in
22/// [`unknown_keywords`](SchemaObject::unknown_keywords) rather than dropped,
23/// because JSON Schema is extensible by design and a description parsed from an
24/// external source must round-trip.
25#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
26pub struct SchemaObject {
27    // --- Core ------------------------------------------------------------
28    /// The dialect this schema resource is written in.
29    ///
30    /// Permitted only on a schema resource root. When absent, the document's
31    /// [`json_schema_dialect`](crate::Document::json_schema_dialect) applies.
32    #[serde(rename = "$schema", default, skip_serializing_if = "Option::is_none")]
33    pub schema_dialect: Option<String>,
34
35    /// The base URI of this schema resource.
36    #[serde(rename = "$id", default, skip_serializing_if = "Option::is_none")]
37    pub id: Option<String>,
38
39    /// A reference to another schema, applied together with any siblings.
40    #[serde(rename = "$ref", default, skip_serializing_if = "Option::is_none")]
41    pub reference: Option<String>,
42
43    /// A plain-name fragment identifying this schema within its resource.
44    #[serde(rename = "$anchor", default, skip_serializing_if = "Option::is_none")]
45    pub anchor: Option<String>,
46
47    /// A reference resolved dynamically against the evaluation path.
48    #[serde(
49        rename = "$dynamicRef",
50        default,
51        skip_serializing_if = "Option::is_none"
52    )]
53    pub dynamic_ref: Option<String>,
54
55    /// A dynamic anchor, the target of a `$dynamicRef`.
56    #[serde(
57        rename = "$dynamicAnchor",
58        default,
59        skip_serializing_if = "Option::is_none"
60    )]
61    pub dynamic_anchor: Option<String>,
62
63    /// A comment for maintainers, carrying no validation effect.
64    #[serde(rename = "$comment", default, skip_serializing_if = "Option::is_none")]
65    pub comment: Option<String>,
66
67    /// Reusable subschemas.
68    ///
69    /// These are *not* visible to OpenAPI component-name resolution: a
70    /// [`Discriminator`] implicit mapping and a `#/components/schemas` lookup
71    /// cannot see entries defined here.
72    #[serde(rename = "$defs", default, skip_serializing_if = "Map::is_empty")]
73    pub defs: Map<Schema>,
74
75    // --- Applicator ------------------------------------------------------
76    /// The instance must validate against every subschema.
77    #[serde(rename = "allOf", default, skip_serializing_if = "Option::is_none")]
78    pub all_of: Option<Vec<Schema>>,
79
80    /// The instance must validate against at least one subschema.
81    #[serde(rename = "anyOf", default, skip_serializing_if = "Option::is_none")]
82    pub any_of: Option<Vec<Schema>>,
83
84    /// The instance must validate against exactly one subschema.
85    #[serde(rename = "oneOf", default, skip_serializing_if = "Option::is_none")]
86    pub one_of: Option<Vec<Schema>>,
87
88    /// The instance must not validate against this subschema.
89    #[serde(default, skip_serializing_if = "Option::is_none")]
90    pub not: Option<Box<Schema>>,
91
92    /// The condition of a conditional schema.
93    #[serde(rename = "if", default, skip_serializing_if = "Option::is_none")]
94    pub if_schema: Option<Box<Schema>>,
95
96    /// Applied when [`if_schema`](SchemaObject::if_schema) succeeds.
97    #[serde(rename = "then", default, skip_serializing_if = "Option::is_none")]
98    pub then_schema: Option<Box<Schema>>,
99
100    /// Applied when [`if_schema`](SchemaObject::if_schema) fails.
101    #[serde(rename = "else", default, skip_serializing_if = "Option::is_none")]
102    pub else_schema: Option<Box<Schema>>,
103
104    /// Schemas applied when a given property is present.
105    #[serde(
106        rename = "dependentSchemas",
107        default,
108        skip_serializing_if = "Map::is_empty"
109    )]
110    pub dependent_schemas: Map<Schema>,
111
112    /// Schemas applied to the array items at the corresponding positions.
113    #[serde(
114        rename = "prefixItems",
115        default,
116        skip_serializing_if = "Option::is_none"
117    )]
118    pub prefix_items: Option<Vec<Schema>>,
119
120    /// The schema applied to array items past
121    /// [`prefix_items`](SchemaObject::prefix_items).
122    #[serde(default, skip_serializing_if = "Option::is_none")]
123    pub items: Option<Box<Schema>>,
124
125    /// At least one array item must validate against this schema.
126    #[serde(default, skip_serializing_if = "Option::is_none")]
127    pub contains: Option<Box<Schema>>,
128
129    /// Schemas applied to named object properties.
130    #[serde(default, skip_serializing_if = "Map::is_empty")]
131    pub properties: Map<Schema>,
132
133    /// Schemas applied to properties whose names match a regular expression.
134    #[serde(
135        rename = "patternProperties",
136        default,
137        skip_serializing_if = "Map::is_empty"
138    )]
139    pub pattern_properties: Map<Schema>,
140
141    /// The schema applied to properties matched by no other applicator.
142    #[serde(
143        rename = "additionalProperties",
144        default,
145        skip_serializing_if = "Option::is_none"
146    )]
147    pub additional_properties: Option<Box<Schema>>,
148
149    /// A schema every property *name* must validate against.
150    #[serde(
151        rename = "propertyNames",
152        default,
153        skip_serializing_if = "Option::is_none"
154    )]
155    pub property_names: Option<Box<Schema>>,
156
157    // --- Unevaluated -----------------------------------------------------
158    /// Applied to array items no other applicator evaluated.
159    #[serde(
160        rename = "unevaluatedItems",
161        default,
162        skip_serializing_if = "Option::is_none"
163    )]
164    pub unevaluated_items: Option<Box<Schema>>,
165
166    /// Applied to properties no other applicator evaluated.
167    #[serde(
168        rename = "unevaluatedProperties",
169        default,
170        skip_serializing_if = "Option::is_none"
171    )]
172    pub unevaluated_properties: Option<Box<Schema>>,
173
174    // --- Validation ------------------------------------------------------
175    /// The permitted primitive type or types.
176    #[serde(rename = "type", default, skip_serializing_if = "Option::is_none")]
177    pub ty: Option<TypeSet>,
178
179    /// The instance must equal this value.
180    #[serde(
181        rename = "const",
182        default,
183        deserialize_with = "crate::model::nullable::some",
184        skip_serializing_if = "Option::is_none"
185    )]
186    pub const_value: Option<Value>,
187
188    /// The instance must equal one of these values.
189    #[serde(rename = "enum", default, skip_serializing_if = "Option::is_none")]
190    pub enumeration: Option<Vec<Value>>,
191
192    /// The number must be a multiple of this value.
193    #[serde(
194        rename = "multipleOf",
195        default,
196        skip_serializing_if = "Option::is_none"
197    )]
198    pub multiple_of: Option<f64>,
199
200    /// The inclusive upper bound of a number.
201    #[serde(default, skip_serializing_if = "Option::is_none")]
202    pub maximum: Option<f64>,
203
204    /// The exclusive upper bound of a number.
205    ///
206    /// A number from OpenAPI 3.1 onward. In 3.0 this was a boolean modifying
207    /// `maximum`; that form must never be emitted.
208    #[serde(
209        rename = "exclusiveMaximum",
210        default,
211        skip_serializing_if = "Option::is_none"
212    )]
213    pub exclusive_maximum: Option<f64>,
214
215    /// The inclusive lower bound of a number.
216    #[serde(default, skip_serializing_if = "Option::is_none")]
217    pub minimum: Option<f64>,
218
219    /// The exclusive lower bound of a number.
220    #[serde(
221        rename = "exclusiveMinimum",
222        default,
223        skip_serializing_if = "Option::is_none"
224    )]
225    pub exclusive_minimum: Option<f64>,
226
227    /// The maximum length of a string, in Unicode scalar values.
228    #[serde(rename = "maxLength", default, skip_serializing_if = "Option::is_none")]
229    pub max_length: Option<u64>,
230
231    /// The minimum length of a string, in Unicode scalar values.
232    #[serde(rename = "minLength", default, skip_serializing_if = "Option::is_none")]
233    pub min_length: Option<u64>,
234
235    /// An ECMA-262 regular expression the string must match.
236    #[serde(default, skip_serializing_if = "Option::is_none")]
237    pub pattern: Option<String>,
238
239    /// The maximum number of array items.
240    #[serde(rename = "maxItems", default, skip_serializing_if = "Option::is_none")]
241    pub max_items: Option<u64>,
242
243    /// The minimum number of array items.
244    #[serde(rename = "minItems", default, skip_serializing_if = "Option::is_none")]
245    pub min_items: Option<u64>,
246
247    /// Whether array items must be pairwise distinct.
248    #[serde(
249        rename = "uniqueItems",
250        default,
251        skip_serializing_if = "Option::is_none"
252    )]
253    pub unique_items: Option<bool>,
254
255    /// The maximum number of items matching [`contains`](SchemaObject::contains).
256    #[serde(
257        rename = "maxContains",
258        default,
259        skip_serializing_if = "Option::is_none"
260    )]
261    pub max_contains: Option<u64>,
262
263    /// The minimum number of items matching [`contains`](SchemaObject::contains).
264    #[serde(
265        rename = "minContains",
266        default,
267        skip_serializing_if = "Option::is_none"
268    )]
269    pub min_contains: Option<u64>,
270
271    /// The maximum number of object properties.
272    #[serde(
273        rename = "maxProperties",
274        default,
275        skip_serializing_if = "Option::is_none"
276    )]
277    pub max_properties: Option<u64>,
278
279    /// The minimum number of object properties.
280    #[serde(
281        rename = "minProperties",
282        default,
283        skip_serializing_if = "Option::is_none"
284    )]
285    pub min_properties: Option<u64>,
286
287    /// The names of properties that must be present.
288    #[serde(default, skip_serializing_if = "Option::is_none")]
289    pub required: Option<Vec<String>>,
290
291    /// Properties required when a given property is present.
292    #[serde(
293        rename = "dependentRequired",
294        default,
295        skip_serializing_if = "Map::is_empty"
296    )]
297    pub dependent_required: Map<Vec<String>>,
298
299    // --- Format ----------------------------------------------------------
300    /// A semantic format annotation such as `date-time` or `uuid`.
301    ///
302    /// Non-validating by default. OpenAPI itself defines only `int32`, `int64`,
303    /// `float`, `double` and `password`; everything else comes from the OAI
304    /// Format Registry and support for it is optional.
305    #[serde(default, skip_serializing_if = "Option::is_none")]
306    pub format: Option<String>,
307
308    // --- Content ---------------------------------------------------------
309    /// How the string is encoded, such as `base64`.
310    ///
311    /// Together with [`content_media_type`](SchemaObject::content_media_type)
312    /// this replaces the OpenAPI 3.0 `format: binary`, which must never be
313    /// emitted.
314    #[serde(
315        rename = "contentEncoding",
316        default,
317        skip_serializing_if = "Option::is_none"
318    )]
319    pub content_encoding: Option<String>,
320
321    /// The media type of the string's decoded contents.
322    #[serde(
323        rename = "contentMediaType",
324        default,
325        skip_serializing_if = "Option::is_none"
326    )]
327    pub content_media_type: Option<String>,
328
329    /// A schema for the string's decoded contents.
330    #[serde(
331        rename = "contentSchema",
332        default,
333        skip_serializing_if = "Option::is_none"
334    )]
335    pub content_schema: Option<Box<Schema>>,
336
337    // --- Metadata --------------------------------------------------------
338    /// A short title for the schema.
339    #[serde(default, skip_serializing_if = "Option::is_none")]
340    pub title: Option<String>,
341
342    /// A description of the schema. [CommonMark] syntax may be used.
343    ///
344    /// [CommonMark]: https://spec.commonmark.org/
345    #[serde(default, skip_serializing_if = "Option::is_none")]
346    pub description: Option<String>,
347
348    /// The default value for the described instance.
349    #[serde(
350        default,
351        deserialize_with = "crate::model::nullable::some",
352        skip_serializing_if = "Option::is_none"
353    )]
354    pub default: Option<Value>,
355
356    /// Whether the described instance is deprecated.
357    #[serde(default, skip_serializing_if = "Option::is_none")]
358    pub deprecated: Option<bool>,
359
360    /// The instance is sent by the server but not accepted from the client.
361    #[serde(rename = "readOnly", default, skip_serializing_if = "Option::is_none")]
362    pub read_only: Option<bool>,
363
364    /// The instance is accepted from the client but not sent by the server.
365    #[serde(rename = "writeOnly", default, skip_serializing_if = "Option::is_none")]
366    pub write_only: Option<bool>,
367
368    /// Example instances.
369    ///
370    /// An array, per JSON Schema. This supersedes the OAS
371    /// [`example`](SchemaObject::example) field.
372    #[serde(default, skip_serializing_if = "Option::is_none")]
373    pub examples: Option<Vec<Value>>,
374
375    // --- OAS base vocabulary ---------------------------------------------
376    /// Polymorphism support for `oneOf`, `anyOf` and `allOf`.
377    #[serde(default, skip_serializing_if = "Option::is_none")]
378    pub discriminator: Option<Discriminator>,
379
380    /// Metadata describing the XML representation of this schema.
381    #[serde(default, skip_serializing_if = "Option::is_none")]
382    pub xml: Option<Xml>,
383
384    /// Additional external documentation for this schema.
385    #[serde(
386        rename = "externalDocs",
387        default,
388        skip_serializing_if = "Option::is_none"
389    )]
390    pub external_docs: Option<ExternalDocumentation>,
391
392    /// A single example instance.
393    ///
394    /// **Deprecated by the specification** in favour of
395    /// [`examples`](SchemaObject::examples). Present so that parsed
396    /// descriptions round-trip; Kynos does not emit it.
397    #[deprecated(note = "use `examples`, which the specification supersedes this with")]
398    #[serde(
399        default,
400        deserialize_with = "crate::model::nullable::some",
401        skip_serializing_if = "Option::is_none"
402    )]
403    pub example: Option<Value>,
404
405    /// Keywords not recognized by this model.
406    ///
407    /// Note that inside a Schema Object — and nowhere else — extensions are
408    /// permitted to omit the `x-` prefix, so this map is not purely a
409    /// specification-extension container.
410    #[serde(flatten)]
411    pub unknown_keywords: Map<Value>,
412}