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        deserialize_with = "crate::model::number::float",
197        skip_serializing_if = "Option::is_none"
198    )]
199    pub multiple_of: Option<f64>,
200
201    /// The inclusive upper bound of a number.
202    #[serde(
203        default,
204        deserialize_with = "crate::model::number::float",
205        skip_serializing_if = "Option::is_none"
206    )]
207    pub maximum: Option<f64>,
208
209    /// The exclusive upper bound of a number.
210    ///
211    /// A number from OpenAPI 3.1 onward. In 3.0 this was a boolean modifying
212    /// `maximum`; that form must never be emitted.
213    #[serde(
214        rename = "exclusiveMaximum",
215        default,
216        deserialize_with = "crate::model::number::float",
217        skip_serializing_if = "Option::is_none"
218    )]
219    pub exclusive_maximum: Option<f64>,
220
221    /// The inclusive lower bound of a number.
222    #[serde(
223        default,
224        deserialize_with = "crate::model::number::float",
225        skip_serializing_if = "Option::is_none"
226    )]
227    pub minimum: Option<f64>,
228
229    /// The exclusive lower bound of a number.
230    #[serde(
231        rename = "exclusiveMinimum",
232        default,
233        deserialize_with = "crate::model::number::float",
234        skip_serializing_if = "Option::is_none"
235    )]
236    pub exclusive_minimum: Option<f64>,
237
238    /// The maximum length of a string, in Unicode scalar values.
239    #[serde(rename = "maxLength", default, skip_serializing_if = "Option::is_none")]
240    pub max_length: Option<u64>,
241
242    /// The minimum length of a string, in Unicode scalar values.
243    #[serde(rename = "minLength", default, skip_serializing_if = "Option::is_none")]
244    pub min_length: Option<u64>,
245
246    /// An ECMA-262 regular expression the string must match.
247    #[serde(default, skip_serializing_if = "Option::is_none")]
248    pub pattern: Option<String>,
249
250    /// The maximum number of array items.
251    #[serde(rename = "maxItems", default, skip_serializing_if = "Option::is_none")]
252    pub max_items: Option<u64>,
253
254    /// The minimum number of array items.
255    #[serde(rename = "minItems", default, skip_serializing_if = "Option::is_none")]
256    pub min_items: Option<u64>,
257
258    /// Whether array items must be pairwise distinct.
259    #[serde(
260        rename = "uniqueItems",
261        default,
262        skip_serializing_if = "Option::is_none"
263    )]
264    pub unique_items: Option<bool>,
265
266    /// The maximum number of items matching [`contains`](SchemaObject::contains).
267    #[serde(
268        rename = "maxContains",
269        default,
270        skip_serializing_if = "Option::is_none"
271    )]
272    pub max_contains: Option<u64>,
273
274    /// The minimum number of items matching [`contains`](SchemaObject::contains).
275    #[serde(
276        rename = "minContains",
277        default,
278        skip_serializing_if = "Option::is_none"
279    )]
280    pub min_contains: Option<u64>,
281
282    /// The maximum number of object properties.
283    #[serde(
284        rename = "maxProperties",
285        default,
286        skip_serializing_if = "Option::is_none"
287    )]
288    pub max_properties: Option<u64>,
289
290    /// The minimum number of object properties.
291    #[serde(
292        rename = "minProperties",
293        default,
294        skip_serializing_if = "Option::is_none"
295    )]
296    pub min_properties: Option<u64>,
297
298    /// The names of properties that must be present.
299    #[serde(default, skip_serializing_if = "Option::is_none")]
300    pub required: Option<Vec<String>>,
301
302    /// Properties required when a given property is present.
303    #[serde(
304        rename = "dependentRequired",
305        default,
306        skip_serializing_if = "Map::is_empty"
307    )]
308    pub dependent_required: Map<Vec<String>>,
309
310    // --- Format ----------------------------------------------------------
311    /// A semantic format annotation such as `date-time` or `uuid`.
312    ///
313    /// Non-validating by default. OpenAPI itself defines only `int32`, `int64`,
314    /// `float`, `double` and `password`; everything else comes from the OAI
315    /// Format Registry and support for it is optional.
316    #[serde(default, skip_serializing_if = "Option::is_none")]
317    pub format: Option<String>,
318
319    // --- Content ---------------------------------------------------------
320    /// How the string is encoded, such as `base64`.
321    ///
322    /// Together with [`content_media_type`](SchemaObject::content_media_type)
323    /// this replaces the OpenAPI 3.0 `format: binary`, which must never be
324    /// emitted.
325    #[serde(
326        rename = "contentEncoding",
327        default,
328        skip_serializing_if = "Option::is_none"
329    )]
330    pub content_encoding: Option<String>,
331
332    /// The media type of the string's decoded contents.
333    #[serde(
334        rename = "contentMediaType",
335        default,
336        skip_serializing_if = "Option::is_none"
337    )]
338    pub content_media_type: Option<String>,
339
340    /// A schema for the string's decoded contents.
341    #[serde(
342        rename = "contentSchema",
343        default,
344        skip_serializing_if = "Option::is_none"
345    )]
346    pub content_schema: Option<Box<Schema>>,
347
348    // --- Metadata --------------------------------------------------------
349    /// A short title for the schema.
350    #[serde(default, skip_serializing_if = "Option::is_none")]
351    pub title: Option<String>,
352
353    /// A description of the schema. [CommonMark] syntax may be used.
354    ///
355    /// [CommonMark]: https://spec.commonmark.org/
356    #[serde(default, skip_serializing_if = "Option::is_none")]
357    pub description: Option<String>,
358
359    /// The default value for the described instance.
360    #[serde(
361        default,
362        deserialize_with = "crate::model::nullable::some",
363        skip_serializing_if = "Option::is_none"
364    )]
365    pub default: Option<Value>,
366
367    /// Whether the described instance is deprecated.
368    #[serde(default, skip_serializing_if = "Option::is_none")]
369    pub deprecated: Option<bool>,
370
371    /// The instance is sent by the server but not accepted from the client.
372    #[serde(rename = "readOnly", default, skip_serializing_if = "Option::is_none")]
373    pub read_only: Option<bool>,
374
375    /// The instance is accepted from the client but not sent by the server.
376    #[serde(rename = "writeOnly", default, skip_serializing_if = "Option::is_none")]
377    pub write_only: Option<bool>,
378
379    /// Example instances.
380    ///
381    /// An array, per JSON Schema. This supersedes the OAS
382    /// [`example`](SchemaObject::example) field.
383    #[serde(default, skip_serializing_if = "Option::is_none")]
384    pub examples: Option<Vec<Value>>,
385
386    // --- OAS base vocabulary ---------------------------------------------
387    /// Polymorphism support for `oneOf`, `anyOf` and `allOf`.
388    #[serde(default, skip_serializing_if = "Option::is_none")]
389    pub discriminator: Option<Discriminator>,
390
391    /// Metadata describing the XML representation of this schema.
392    #[serde(default, skip_serializing_if = "Option::is_none")]
393    pub xml: Option<Xml>,
394
395    /// Additional external documentation for this schema.
396    #[serde(
397        rename = "externalDocs",
398        default,
399        skip_serializing_if = "Option::is_none"
400    )]
401    pub external_docs: Option<ExternalDocumentation>,
402
403    /// A single example instance.
404    ///
405    /// **Deprecated by the specification** in favour of
406    /// [`examples`](SchemaObject::examples). Present so that parsed
407    /// descriptions round-trip; Kynos does not emit it.
408    #[deprecated(note = "use `examples`, which the specification supersedes this with")]
409    #[serde(
410        default,
411        deserialize_with = "crate::model::nullable::some",
412        skip_serializing_if = "Option::is_none"
413    )]
414    pub example: Option<Value>,
415
416    /// Keywords not recognized by this model.
417    ///
418    /// Note that inside a Schema Object — and nowhere else — extensions are
419    /// permitted to omit the `x-` prefix, so this map is not purely a
420    /// specification-extension container.
421    #[serde(flatten)]
422    pub unknown_keywords: Map<Value>,
423}