openapi_nexus_spec/oas31/spec/
schema.rs1use std::{collections::BTreeMap, fmt};
4
5use serde::{Deserialize, Deserializer, Serialize};
6use snafu::Snafu;
7
8use super::{
9 ErrorRef, FromRef, ObjectOrReference, OpenApiV31Spec, Ref, RefType,
10 discriminator::Discriminator, spec_extensions,
11};
12
13#[derive(Debug, Clone, PartialEq, Snafu)]
15#[snafu(visibility(pub))]
16pub enum ErrorSchema {
17 #[snafu(display("Missing type field"))]
18 NoType,
19
20 #[snafu(display("Unknown type: {}", type_name))]
21 UnknownType { type_name: String },
22
23 #[snafu(display("Required property list specified for a non-object schema"))]
24 RequiredSpecifiedOnNonObject,
25}
26
27#[derive(Debug, Clone, Copy, PartialEq, Deserialize, Serialize)]
29#[serde(rename_all = "snake_case")]
30pub enum Type {
31 Boolean,
32 Integer,
33 Number,
34 String,
35 Array,
36 Object,
37 Null,
38}
39
40#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
42#[serde(untagged)]
43pub enum TypeSet {
44 Single(Type),
45 Multiple(Vec<Type>),
46}
47
48impl TypeSet {
49 pub fn contains(&self, type_: Type) -> bool {
51 match self {
52 TypeSet::Single(single_type) => *single_type == type_,
53 TypeSet::Multiple(type_set) => type_set.contains(&type_),
54 }
55 }
56
57 pub fn is_object_or_nullable_object(&self) -> bool {
59 match self {
60 TypeSet::Single(Type::Object) => true,
61 TypeSet::Multiple(set) if set == &[Type::Object] => true,
62 TypeSet::Multiple(set) if set == &[Type::Object, Type::Null] => true,
63 TypeSet::Multiple(set) if set == &[Type::Null, Type::Object] => true,
64 _ => false,
65 }
66 }
67
68 pub fn is_array_or_nullable_array(&self) -> bool {
70 match self {
71 TypeSet::Single(Type::Array) => true,
72 TypeSet::Multiple(set) if set == &[Type::Array] => true,
73 TypeSet::Multiple(set) if set == &[Type::Array, Type::Null] => true,
74 TypeSet::Multiple(set) if set == &[Type::Null, Type::Array] => true,
75 _ => false,
76 }
77 }
78}
79
80#[derive(Debug, Clone, PartialEq, Default, Deserialize, Serialize)]
82pub struct ObjectSchema {
83 #[serde(rename = "allOf", default, skip_serializing_if = "Vec::is_empty")]
84 pub all_of: Vec<ObjectOrReference<ObjectSchema>>,
85
86 #[serde(rename = "anyOf", default, skip_serializing_if = "Vec::is_empty")]
87 pub any_of: Vec<ObjectOrReference<ObjectSchema>>,
88
89 #[serde(rename = "oneOf", default, skip_serializing_if = "Vec::is_empty")]
90 pub one_of: Vec<ObjectOrReference<ObjectSchema>>,
91
92 #[serde(skip_serializing_if = "Option::is_none")]
93 pub items: Option<Box<Schema>>,
94
95 #[serde(rename = "prefixItems", default, skip_serializing_if = "Vec::is_empty")]
96 pub prefix_items: Vec<ObjectOrReference<ObjectSchema>>,
97
98 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
99 pub properties: BTreeMap<String, ObjectOrReference<ObjectSchema>>,
100
101 #[serde(
102 rename = "additionalProperties",
103 skip_serializing_if = "Option::is_none"
104 )]
105 pub additional_properties: Option<Schema>,
106
107 #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
108 pub schema_type: Option<TypeSet>,
109
110 #[serde(rename = "enum", default, skip_serializing_if = "Vec::is_empty")]
111 pub enum_values: Vec<serde_json::Value>,
112
113 #[serde(rename = "const", skip_serializing_if = "Option::is_none")]
114 pub const_value: Option<serde_json::Value>,
115
116 #[serde(rename = "multipleOf", skip_serializing_if = "Option::is_none")]
117 pub multiple_of: Option<serde_json::Number>,
118
119 #[serde(skip_serializing_if = "Option::is_none")]
120 pub maximum: Option<serde_json::Number>,
121
122 #[serde(rename = "exclusiveMaximum", skip_serializing_if = "Option::is_none")]
123 pub exclusive_maximum: Option<serde_json::Number>,
124
125 #[serde(skip_serializing_if = "Option::is_none")]
126 pub minimum: Option<serde_json::Number>,
127
128 #[serde(rename = "exclusiveMinimum", skip_serializing_if = "Option::is_none")]
129 pub exclusive_minimum: Option<serde_json::Number>,
130
131 #[serde(rename = "maxLength", skip_serializing_if = "Option::is_none")]
132 pub max_length: Option<u64>,
133
134 #[serde(rename = "minLength", skip_serializing_if = "Option::is_none")]
135 pub min_length: Option<u64>,
136
137 #[serde(skip_serializing_if = "Option::is_none")]
138 pub pattern: Option<String>,
139
140 #[serde(rename = "maxItems", skip_serializing_if = "Option::is_none")]
141 pub max_items: Option<u64>,
142
143 #[serde(rename = "minItems", skip_serializing_if = "Option::is_none")]
144 pub min_items: Option<u64>,
145
146 #[serde(rename = "uniqueItems", skip_serializing_if = "Option::is_none")]
147 pub unique_items: Option<bool>,
148
149 #[serde(rename = "maxProperties", skip_serializing_if = "Option::is_none")]
150 pub max_properties: Option<u64>,
151
152 #[serde(rename = "minProperties", skip_serializing_if = "Option::is_none")]
153 pub min_properties: Option<u64>,
154
155 #[serde(default, skip_serializing_if = "Vec::is_empty")]
156 pub required: Vec<String>,
157
158 #[serde(skip_serializing_if = "Option::is_none")]
159 pub format: Option<String>,
160
161 #[serde(skip_serializing_if = "Option::is_none")]
162 pub title: Option<String>,
163
164 #[serde(skip_serializing_if = "Option::is_none")]
165 pub description: Option<String>,
166
167 #[serde(skip_serializing_if = "Option::is_none")]
168 pub default: Option<serde_json::Value>,
169
170 #[serde(skip_serializing_if = "Option::is_none")]
171 pub deprecated: Option<bool>,
172
173 #[serde(rename = "readOnly", skip_serializing_if = "Option::is_none")]
174 pub read_only: Option<bool>,
175
176 #[serde(rename = "writeOnly", skip_serializing_if = "Option::is_none")]
177 pub write_only: Option<bool>,
178
179 #[serde(default, skip_serializing_if = "Vec::is_empty")]
180 pub examples: Vec<serde_json::Value>,
181
182 #[serde(default, skip_serializing_if = "Option::is_none")]
183 pub discriminator: Option<Discriminator>,
184
185 #[serde(
186 default,
187 deserialize_with = "distinguish_missing_and_null",
188 skip_serializing_if = "Option::is_none"
189 )]
190 pub example: Option<serde_json::Value>,
191
192 #[serde(flatten, with = "spec_extensions")]
193 pub extensions: BTreeMap<String, serde_json::Value>,
194}
195
196impl ObjectSchema {
197 pub fn is_nullable(&self) -> Option<bool> {
199 Some(match self.schema_type.as_ref()? {
200 TypeSet::Single(type_) => *type_ == Type::Null,
201 TypeSet::Multiple(set) => set.contains(&Type::Null),
202 })
203 }
204}
205
206impl FromRef for ObjectSchema {
207 fn from_ref(spec: &OpenApiV31Spec, path: &str) -> Result<Self, ErrorRef> {
208 let refpath = path.parse::<Ref>()?;
209
210 match refpath.kind {
211 RefType::Schema => spec
212 .components
213 .as_ref()
214 .and_then(|cs| cs.schemas.get(&refpath.name))
215 .ok_or_else(|| ErrorRef::Unresolvable {
216 path: path.to_owned(),
217 })
218 .and_then(|oor| oor.resolve(spec)),
219
220 typ => Err(ErrorRef::MismatchedType {
221 expected: typ,
222 actual: RefType::Schema,
223 }),
224 }
225 }
226}
227
228#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
230#[serde(transparent)]
231pub struct BooleanSchema(pub bool);
232
233#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
235#[serde(untagged)]
236pub enum Schema {
237 Boolean(BooleanSchema),
238 Object(Box<ObjectOrReference<ObjectSchema>>),
239}
240
241fn distinguish_missing_and_null<'de, T, D>(de: D) -> Result<Option<T>, D::Error>
243where
244 T: Deserialize<'de> + fmt::Debug,
245 D: Deserializer<'de>,
246{
247 T::deserialize(de).map(Some)
248}
249
250#[cfg(test)]
251mod tests {
252 use super::{ObjectSchema, Type};
253
254 #[test]
255 fn type_set_contains() {
256 let spec = r#"{"type": "integer"}"#;
257 let schema = serde_json::from_str::<ObjectSchema>(spec).unwrap();
258 let schema_type = schema.schema_type.unwrap();
259 assert!(schema_type.contains(Type::Integer));
260
261 let spec = r#"{"type": ["integer", "null"]}"#;
262 let schema = serde_json::from_str::<ObjectSchema>(spec).unwrap();
263 let schema_type = schema.schema_type.unwrap();
264 assert!(schema_type.contains(Type::Integer));
265
266 let spec = r#"{"type": ["object", "null"]}"#;
267 let schema = serde_json::from_str::<ObjectSchema>(spec).unwrap();
268 let schema_type = schema.schema_type.unwrap();
269 assert!(schema_type.contains(Type::Object));
270 assert!(schema_type.is_object_or_nullable_object());
271 }
272
273 #[test]
274 fn example_can_be_explicit_null() {
275 let spec = r#"{"type": ["string", "null"]}"#;
276 let schema = serde_json::from_str::<ObjectSchema>(spec).unwrap();
277 assert_eq!(schema.example, None);
278
279 let spec = r#"{"type": ["string", "null"], "example": null}"#;
280 let schema = serde_json::from_str::<ObjectSchema>(spec).unwrap();
281 assert_eq!(schema.example, Some(serde_json::Value::Null));
282 }
283}