kynos_openapi/model/schema/mod.rs
1//! The Schema Object: JSON Schema 2020-12 plus the OAS base vocabulary.
2
3pub mod dialect;
4pub mod discriminator;
5pub mod object;
6pub mod types;
7pub mod xml;
8
9use serde::{Deserialize, Serialize};
10
11use crate::model::schema::{
12 object::SchemaObject,
13 types::{SchemaType, TypeSet},
14};
15
16/// A JSON Schema.
17///
18/// A boolean is a valid schema in JSON Schema 2020-12: `true` accepts every
19/// instance and `false` accepts none. That is why this is an enum rather than a
20/// struct.
21///
22/// [`Schema::Bool(true)`](Schema::Bool) is how a genuinely unconstrained
23/// payload is represented. Kynos never produces it by accident — a Rust type
24/// that cannot describe itself has no `Schema` implementation at all, and the
25/// permissive schema is reachable only by naming it in the handler signature.
26#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
27#[serde(untagged)]
28pub enum Schema {
29 /// The trivially true (`true`) or trivially false (`false`) schema.
30 Bool(bool),
31 /// A schema with keywords.
32 Object(Box<SchemaObject>),
33}
34
35impl Default for Schema {
36 fn default() -> Self {
37 Self::Object(Box::default())
38 }
39}
40
41impl Schema {
42 /// The schema that accepts any instance.
43 #[must_use]
44 pub fn any() -> Self {
45 Self::Bool(true)
46 }
47
48 /// The schema that accepts no instance.
49 #[must_use]
50 pub fn never() -> Self {
51 Self::Bool(false)
52 }
53
54 /// A schema constrained to a single primitive type.
55 #[must_use]
56 pub fn of_type(ty: SchemaType) -> Self {
57 Self::Object(Box::new(SchemaObject {
58 ty: Some(TypeSet::One(ty)),
59 ..SchemaObject::default()
60 }))
61 }
62
63 /// A schema that is `ty` or `null`.
64 ///
65 /// This is how nullability is expressed from OpenAPI 3.1 onward. The 3.0
66 /// `nullable: true` keyword does not exist and must never be emitted.
67 #[must_use]
68 pub fn nullable(ty: SchemaType) -> Self {
69 Self::Object(Box::new(SchemaObject {
70 ty: Some(TypeSet::Many(vec![ty, SchemaType::Null])),
71 ..SchemaObject::default()
72 }))
73 }
74
75 /// A `$ref` to another schema.
76 ///
77 /// Unlike a [`Ref`](crate::Ref), sibling keywords on a schema `$ref` are
78 /// applied rather than ignored.
79 #[must_use]
80 pub fn reference(uri: impl Into<String>) -> Self {
81 Self::Object(Box::new(SchemaObject {
82 reference: Some(uri.into()),
83 ..SchemaObject::default()
84 }))
85 }
86
87 /// A `$ref` to a named entry under `#/components/schemas`.
88 #[must_use]
89 pub fn component(name: &str) -> Self {
90 Self::reference(format!("#/components/schemas/{name}"))
91 }
92
93 /// Returns the keyword-carrying form, if this is not a boolean schema.
94 #[must_use]
95 pub fn as_object(&self) -> Option<&SchemaObject> {
96 match self {
97 Self::Object(object) => Some(object),
98 Self::Bool(_) => None,
99 }
100 }
101}
102
103#[cfg(test)]
104mod tests;