openapiv3/
schema.rs

1use crate::*;
2use indexmap::IndexMap;
3use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
6#[serde(rename_all = "camelCase")]
7pub struct SchemaData {
8    #[serde(default, skip_serializing_if = "is_false")]
9    pub nullable: bool,
10    #[serde(default, skip_serializing_if = "is_false")]
11    pub read_only: bool,
12    #[serde(default, skip_serializing_if = "is_false")]
13    pub write_only: bool,
14    #[serde(default, skip_serializing_if = "is_false")]
15    pub deprecated: bool,
16    #[serde(skip_serializing_if = "Option::is_none")]
17    pub external_docs: Option<ExternalDocumentation>,
18    #[serde(skip_serializing_if = "Option::is_none")]
19    pub example: Option<serde_json::Value>,
20    #[serde(skip_serializing_if = "Option::is_none")]
21    pub title: Option<String>,
22    #[serde(skip_serializing_if = "Option::is_none")]
23    pub description: Option<String>,
24    #[serde(skip_serializing_if = "Option::is_none")]
25    pub discriminator: Option<Discriminator>,
26    #[serde(skip_serializing_if = "Option::is_none")]
27    pub default: Option<serde_json::Value>,
28    /// Inline extensions to this object.
29    #[serde(flatten, deserialize_with = "crate::util::deserialize_extensions")]
30    pub extensions: IndexMap<String, serde_json::Value>,
31}
32
33#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
34pub struct Schema {
35    #[serde(flatten)]
36    pub schema_data: SchemaData,
37    #[serde(flatten)]
38    pub schema_kind: SchemaKind,
39}
40
41#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
42#[serde(untagged)]
43pub enum SchemaKind {
44    Type(Type),
45    OneOf {
46        #[serde(rename = "oneOf")]
47        one_of: Vec<ReferenceOr<Schema>>,
48    },
49    AllOf {
50        #[serde(rename = "allOf")]
51        all_of: Vec<ReferenceOr<Schema>>,
52    },
53    AnyOf {
54        #[serde(rename = "anyOf")]
55        any_of: Vec<ReferenceOr<Schema>>,
56    },
57    Not {
58        not: Box<ReferenceOr<Schema>>,
59    },
60    Any(AnySchema),
61}
62
63#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
64#[serde(tag = "type", rename_all = "lowercase")]
65pub enum Type {
66    String(StringType),
67    Number(NumberType),
68    Integer(IntegerType),
69    Object(ObjectType),
70    Array(ArrayType),
71    Boolean {},
72}
73
74#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
75#[serde(untagged)]
76pub enum AdditionalProperties {
77    Any(bool),
78    Schema(Box<ReferenceOr<Schema>>),
79}
80
81/// Catch-all for any combination of properties that doesn't correspond to one
82/// of the predefined subsets.
83#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
84#[serde(rename_all = "camelCase")]
85pub struct AnySchema {
86    #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
87    pub typ: Option<String>,
88    #[serde(skip_serializing_if = "Option::is_none")]
89    pub pattern: Option<String>,
90    #[serde(skip_serializing_if = "Option::is_none")]
91    pub multiple_of: Option<f64>,
92    #[serde(skip_serializing_if = "Option::is_none")]
93    pub exclusive_minimum: Option<bool>,
94    #[serde(skip_serializing_if = "Option::is_none")]
95    pub exclusive_maximum: Option<bool>,
96    #[serde(skip_serializing_if = "Option::is_none")]
97    pub minimum: Option<f64>,
98    #[serde(skip_serializing_if = "Option::is_none")]
99    pub maximum: Option<f64>,
100    #[serde(default, skip_serializing_if = "IndexMap::is_empty")]
101    pub properties: IndexMap<String, ReferenceOr<Box<Schema>>>,
102    #[serde(default, skip_serializing_if = "Vec::is_empty")]
103    pub required: Vec<String>,
104    #[serde(skip_serializing_if = "Option::is_none")]
105    pub additional_properties: Option<AdditionalProperties>,
106    #[serde(skip_serializing_if = "Option::is_none")]
107    pub min_properties: Option<usize>,
108    #[serde(skip_serializing_if = "Option::is_none")]
109    pub max_properties: Option<usize>,
110    #[serde(skip_serializing_if = "Option::is_none")]
111    pub items: Option<ReferenceOr<Box<Schema>>>,
112    #[serde(skip_serializing_if = "Option::is_none")]
113    pub min_items: Option<usize>,
114    #[serde(skip_serializing_if = "Option::is_none")]
115    pub max_items: Option<usize>,
116    #[serde(skip_serializing_if = "Option::is_none")]
117    pub unique_items: Option<bool>,
118    #[serde(rename = "enum", default, skip_serializing_if = "Vec::is_empty")]
119    pub enumeration: Vec<serde_json::Value>,
120    #[serde(skip_serializing_if = "Option::is_none")]
121    pub format: Option<String>,
122    #[serde(skip_serializing_if = "Option::is_none")]
123    pub min_length: Option<usize>,
124    #[serde(skip_serializing_if = "Option::is_none")]
125    pub max_length: Option<usize>,
126    #[serde(default, skip_serializing_if = "Vec::is_empty")]
127    pub one_of: Vec<ReferenceOr<Schema>>,
128    #[serde(default, skip_serializing_if = "Vec::is_empty")]
129    pub all_of: Vec<ReferenceOr<Schema>>,
130    #[serde(default, skip_serializing_if = "Vec::is_empty")]
131    pub any_of: Vec<ReferenceOr<Schema>>,
132    #[serde(skip_serializing_if = "Option::is_none")]
133    pub not: Option<Box<ReferenceOr<Schema>>>,
134}
135
136#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
137#[serde(rename_all = "camelCase")]
138pub struct StringType {
139    #[serde(default, skip_serializing_if = "VariantOrUnknownOrEmpty::is_empty")]
140    pub format: VariantOrUnknownOrEmpty<StringFormat>,
141    #[serde(skip_serializing_if = "Option::is_none")]
142    pub pattern: Option<String>,
143    #[serde(rename = "enum", default, skip_serializing_if = "Vec::is_empty")]
144    pub enumeration: Vec<Option<String>>,
145    #[serde(skip_serializing_if = "Option::is_none")]
146    pub min_length: Option<usize>,
147    #[serde(skip_serializing_if = "Option::is_none")]
148    pub max_length: Option<usize>,
149}
150
151#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
152#[serde(rename_all = "camelCase")]
153pub struct NumberType {
154    #[serde(default, skip_serializing_if = "VariantOrUnknownOrEmpty::is_empty")]
155    pub format: VariantOrUnknownOrEmpty<NumberFormat>,
156    #[serde(skip_serializing_if = "Option::is_none")]
157    pub multiple_of: Option<f64>,
158    #[serde(default, skip_serializing_if = "is_false")]
159    pub exclusive_minimum: bool,
160    #[serde(default, skip_serializing_if = "is_false")]
161    pub exclusive_maximum: bool,
162    #[serde(skip_serializing_if = "Option::is_none")]
163    pub minimum: Option<f64>,
164    #[serde(skip_serializing_if = "Option::is_none")]
165    pub maximum: Option<f64>,
166    #[serde(rename = "enum", default, skip_serializing_if = "Vec::is_empty")]
167    pub enumeration: Vec<Option<f64>>,
168}
169
170#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
171#[serde(rename_all = "camelCase")]
172pub struct IntegerType {
173    #[serde(default, skip_serializing_if = "VariantOrUnknownOrEmpty::is_empty")]
174    pub format: VariantOrUnknownOrEmpty<IntegerFormat>,
175    #[serde(skip_serializing_if = "Option::is_none")]
176    pub multiple_of: Option<i64>,
177    #[serde(default, skip_serializing_if = "is_false")]
178    pub exclusive_minimum: bool,
179    #[serde(default, skip_serializing_if = "is_false")]
180    pub exclusive_maximum: bool,
181    #[serde(skip_serializing_if = "Option::is_none")]
182    pub minimum: Option<i64>,
183    #[serde(skip_serializing_if = "Option::is_none")]
184    pub maximum: Option<i64>,
185    #[serde(rename = "enum", default, skip_serializing_if = "Vec::is_empty")]
186    pub enumeration: Vec<Option<i64>>,
187}
188
189#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
190#[serde(rename_all = "camelCase")]
191pub struct ObjectType {
192    #[serde(default, skip_serializing_if = "IndexMap::is_empty")]
193    pub properties: IndexMap<String, ReferenceOr<Box<Schema>>>,
194    #[serde(default, skip_serializing_if = "Vec::is_empty")]
195    pub required: Vec<String>,
196    #[serde(skip_serializing_if = "Option::is_none")]
197    pub additional_properties: Option<AdditionalProperties>,
198    #[serde(skip_serializing_if = "Option::is_none")]
199    pub min_properties: Option<usize>,
200    #[serde(skip_serializing_if = "Option::is_none")]
201    pub max_properties: Option<usize>,
202}
203
204#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
205#[serde(rename_all = "camelCase")]
206pub struct ArrayType {
207    #[serde(skip_serializing_if = "Option::is_none")]
208    pub items: Option<ReferenceOr<Box<Schema>>>,
209    #[serde(skip_serializing_if = "Option::is_none")]
210    pub min_items: Option<usize>,
211    #[serde(skip_serializing_if = "Option::is_none")]
212    pub max_items: Option<usize>,
213    #[serde(default, skip_serializing_if = "is_false")]
214    pub unique_items: bool,
215}
216
217#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
218#[serde(rename_all = "lowercase")]
219pub enum NumberFormat {
220    Float,
221    Double,
222}
223
224#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
225#[serde(rename_all = "lowercase")]
226pub enum IntegerFormat {
227    Int32,
228    Int64,
229}
230
231#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
232#[serde(rename_all = "lowercase")]
233pub enum StringFormat {
234    Date,
235    #[serde(rename = "date-time")]
236    DateTime,
237    Password,
238    Byte,
239    Binary,
240}
241
242#[cfg(test)]
243mod tests {
244    use serde_json::json;
245
246    use crate::{AnySchema, Schema, SchemaData, SchemaKind};
247
248    #[test]
249    fn test_schema_with_extensions() {
250        let schema = serde_json::from_str::<Schema>(
251            r#"{
252                "type": "boolean",
253                "x-foo": "bar"
254            }"#,
255        )
256        .unwrap();
257
258        assert_eq!(
259            schema.schema_data.extensions.get("x-foo"),
260            Some(&json!("bar"))
261        );
262    }
263
264    #[test]
265    fn test_any() {
266        let value = json! { {} };
267        serde_json::from_value::<AnySchema>(value).unwrap();
268    }
269
270    #[test]
271    fn test_not() {
272        let value = json! {
273            {
274                "not": {}
275            }
276        };
277
278        let schema = serde_json::from_value::<Schema>(value).unwrap();
279        assert!(matches!(schema.schema_kind, SchemaKind::Not { not: _ }));
280    }
281
282    #[test]
283    fn test_null() {
284        let value = json! {
285            {
286                "nullable": true,
287                "enum": [ null ],
288            }
289        };
290
291        let schema = serde_json::from_value::<Schema>(value).unwrap();
292        assert!(matches!(
293            &schema.schema_data,
294            SchemaData { nullable: true, .. }
295        ));
296        assert!(matches!(
297            &schema.schema_kind,
298            SchemaKind::Any(AnySchema { enumeration, .. }) if enumeration[0] == json!(null)));
299    }
300}