1use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
8#[serde(untagged)]
9pub enum Schema {
10 Boolean(bool),
12 Ref(RefSchema),
14 Object(ObjectSchema),
16 Array(ArraySchema),
18 Primitive(PrimitiveSchema),
20 Enum(EnumSchema),
22 OneOf(OneOfSchema),
24}
25
26impl Schema {
27 pub fn string() -> Self {
29 Schema::Primitive(PrimitiveSchema::string())
30 }
31
32 pub fn integer(format: Option<&str>) -> Self {
34 Schema::Primitive(PrimitiveSchema::integer(format))
35 }
36
37 pub fn number(format: Option<&str>) -> Self {
39 Schema::Primitive(PrimitiveSchema::number(format))
40 }
41
42 pub fn boolean() -> Self {
44 Schema::Primitive(PrimitiveSchema::boolean())
45 }
46
47 pub fn reference(name: &str) -> Self {
49 Schema::Ref(RefSchema {
50 reference: format!("#/components/schemas/{name}"),
51 })
52 }
53
54 pub fn array(items: Schema) -> Self {
56 Schema::Array(ArraySchema {
57 items: Box::new(items),
58 min_items: None,
59 max_items: None,
60 })
61 }
62
63 pub fn object(properties: HashMap<String, Schema>, required: Vec<String>) -> Self {
65 Schema::Object(ObjectSchema {
66 title: None,
67 description: None,
68 properties,
69 required,
70 additional_properties: None,
71 })
72 }
73
74 #[must_use]
76 pub fn nullable(mut self) -> Self {
77 if let Schema::Primitive(ref mut p) = self {
78 p.nullable = true;
79 }
80 self
81 }
82
83 #[must_use]
85 pub fn with_title(mut self, title: impl Into<String>) -> Self {
86 if let Schema::Object(ref mut o) = self {
87 o.title = Some(title.into());
88 }
89 self
90 }
91
92 #[must_use]
94 pub fn with_description(mut self, description: impl Into<String>) -> Self {
95 if let Schema::Object(ref mut o) = self {
96 o.description = Some(description.into());
97 }
98 self
99 }
100
101 pub fn string_enum(values: Vec<String>) -> Self {
103 Schema::Enum(EnumSchema {
104 schema_type: SchemaType::String,
105 enum_values: values,
106 })
107 }
108
109 pub fn one_of(schemas: Vec<Schema>) -> Self {
111 Schema::OneOf(OneOfSchema { one_of: schemas })
112 }
113}
114
115#[derive(Debug, Clone, Serialize, Deserialize)]
117pub struct RefSchema {
118 #[serde(rename = "$ref")]
120 pub reference: String,
121}
122
123#[derive(Debug, Clone, Default, Serialize, Deserialize)]
125pub struct ObjectSchema {
126 #[serde(default, skip_serializing_if = "Option::is_none")]
128 pub title: Option<String>,
129 #[serde(default, skip_serializing_if = "Option::is_none")]
131 pub description: Option<String>,
132 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
134 pub properties: HashMap<String, Schema>,
135 #[serde(default, skip_serializing_if = "Vec::is_empty")]
137 pub required: Vec<String>,
138 #[serde(default, skip_serializing_if = "Option::is_none")]
140 pub additional_properties: Option<Box<Schema>>,
141}
142
143#[derive(Debug, Clone, Serialize, Deserialize)]
145pub struct ArraySchema {
146 pub items: Box<Schema>,
148 #[serde(default, skip_serializing_if = "Option::is_none")]
150 pub min_items: Option<usize>,
151 #[serde(default, skip_serializing_if = "Option::is_none")]
153 pub max_items: Option<usize>,
154}
155
156#[derive(Debug, Clone, Serialize, Deserialize)]
158pub struct EnumSchema {
159 #[serde(rename = "type")]
161 pub schema_type: SchemaType,
162 #[serde(rename = "enum")]
164 pub enum_values: Vec<String>,
165}
166
167#[derive(Debug, Clone, Serialize, Deserialize)]
169pub struct OneOfSchema {
170 #[serde(rename = "oneOf")]
172 pub one_of: Vec<Schema>,
173}
174
175#[derive(Debug, Clone, Serialize, Deserialize)]
177pub struct PrimitiveSchema {
178 #[serde(rename = "type")]
180 pub schema_type: SchemaType,
181 #[serde(default, skip_serializing_if = "Option::is_none")]
183 pub format: Option<String>,
184 #[serde(default, skip_serializing_if = "is_false")]
186 pub nullable: bool,
187}
188
189impl PrimitiveSchema {
190 pub fn string() -> Self {
192 Self {
193 schema_type: SchemaType::String,
194 format: None,
195 nullable: false,
196 }
197 }
198
199 pub fn integer(format: Option<&str>) -> Self {
201 Self {
202 schema_type: SchemaType::Integer,
203 format: format.map(String::from),
204 nullable: false,
205 }
206 }
207
208 pub fn number(format: Option<&str>) -> Self {
210 Self {
211 schema_type: SchemaType::Number,
212 format: format.map(String::from),
213 nullable: false,
214 }
215 }
216
217 pub fn boolean() -> Self {
219 Self {
220 schema_type: SchemaType::Boolean,
221 format: None,
222 nullable: false,
223 }
224 }
225}
226
227#[allow(clippy::trivially_copy_pass_by_ref)]
228fn is_false(b: &bool) -> bool {
229 !*b
230}
231
232#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
234#[serde(rename_all = "lowercase")]
235pub enum SchemaType {
236 String,
238 Number,
240 Integer,
242 Boolean,
244 Null,
246}
247
248pub trait JsonSchema {
250 fn schema() -> Schema;
252
253 #[must_use]
255 fn schema_name() -> Option<&'static str> {
256 None
257 }
258}
259
260impl JsonSchema for String {
262 fn schema() -> Schema {
263 Schema::Primitive(PrimitiveSchema {
264 schema_type: SchemaType::String,
265 format: None,
266 nullable: false,
267 })
268 }
269}
270
271impl JsonSchema for i64 {
272 fn schema() -> Schema {
273 Schema::Primitive(PrimitiveSchema {
274 schema_type: SchemaType::Integer,
275 format: Some("int64".to_string()),
276 nullable: false,
277 })
278 }
279}
280
281impl JsonSchema for i32 {
282 fn schema() -> Schema {
283 Schema::Primitive(PrimitiveSchema {
284 schema_type: SchemaType::Integer,
285 format: Some("int32".to_string()),
286 nullable: false,
287 })
288 }
289}
290
291impl JsonSchema for f64 {
292 fn schema() -> Schema {
293 Schema::Primitive(PrimitiveSchema {
294 schema_type: SchemaType::Number,
295 format: Some("double".to_string()),
296 nullable: false,
297 })
298 }
299}
300
301impl JsonSchema for bool {
302 fn schema() -> Schema {
303 Schema::Primitive(PrimitiveSchema {
304 schema_type: SchemaType::Boolean,
305 format: None,
306 nullable: false,
307 })
308 }
309}
310
311impl<T: JsonSchema> JsonSchema for Option<T> {
312 fn schema() -> Schema {
313 match T::schema() {
314 Schema::Primitive(mut p) => {
315 p.nullable = true;
316 Schema::Primitive(p)
317 }
318 other => other,
319 }
320 }
321}
322
323impl<T: JsonSchema> JsonSchema for Vec<T> {
324 fn schema() -> Schema {
325 Schema::Array(ArraySchema {
326 items: Box::new(T::schema()),
327 min_items: None,
328 max_items: None,
329 })
330 }
331}