rustapi_openapi/
schema.rs1use serde::{Deserialize, Serialize};
4use std::collections::BTreeMap;
5
6#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
8#[serde(untagged)]
9pub enum TypeArray {
10 Single(String),
11 Array(Vec<String>),
12}
13
14impl TypeArray {
15 pub fn single(ty: impl Into<String>) -> Self {
16 Self::Single(ty.into())
17 }
18
19 pub fn nullable(ty: impl Into<String>) -> Self {
20 Self::Array(vec![ty.into(), "null".to_string()])
21 }
22}
23
24#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
26#[serde(rename_all = "camelCase")]
27pub struct JsonSchema2020 {
28 #[serde(rename = "$schema", skip_serializing_if = "Option::is_none")]
29 pub schema: Option<String>,
30 #[serde(rename = "$id", skip_serializing_if = "Option::is_none")]
31 pub id: Option<String>,
32 #[serde(rename = "$ref", skip_serializing_if = "Option::is_none")]
33 pub reference: Option<String>,
34 #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
35 pub schema_type: Option<TypeArray>,
36 #[serde(skip_serializing_if = "Option::is_none")]
37 pub title: Option<String>,
38 #[serde(skip_serializing_if = "Option::is_none")]
39 pub description: Option<String>,
40 #[serde(skip_serializing_if = "Option::is_none")]
41 pub default: Option<serde_json::Value>,
42 #[serde(rename = "const", skip_serializing_if = "Option::is_none")]
43 pub const_value: Option<serde_json::Value>,
44 #[serde(rename = "enum", skip_serializing_if = "Option::is_none")]
45 pub enum_values: Option<Vec<serde_json::Value>>,
46 #[serde(skip_serializing_if = "Option::is_none")]
47 pub format: Option<String>,
48
49 #[serde(skip_serializing_if = "Option::is_none")]
50 pub items: Option<Box<JsonSchema2020>>,
51 #[serde(skip_serializing_if = "Option::is_none")]
52 pub properties: Option<BTreeMap<String, JsonSchema2020>>,
53 #[serde(skip_serializing_if = "Option::is_none")]
54 pub required: Option<Vec<String>>,
55 #[serde(skip_serializing_if = "Option::is_none")]
56 pub additional_properties: Option<Box<AdditionalProperties>>,
57
58 #[serde(skip_serializing_if = "Option::is_none")]
59 pub one_of: Option<Vec<JsonSchema2020>>,
60 #[serde(skip_serializing_if = "Option::is_none")]
61 pub any_of: Option<Vec<JsonSchema2020>>,
62 #[serde(skip_serializing_if = "Option::is_none")]
63 pub all_of: Option<Vec<JsonSchema2020>>,
64
65 #[serde(skip_serializing_if = "Option::is_none")]
66 pub example: Option<serde_json::Value>,
67}
68
69impl JsonSchema2020 {
70 pub fn new() -> Self {
71 Self::default()
72 }
73 pub fn string() -> Self {
74 Self {
75 schema_type: Some(TypeArray::single("string")),
76 ..Default::default()
77 }
78 }
79 pub fn integer() -> Self {
80 Self {
81 schema_type: Some(TypeArray::single("integer")),
82 ..Default::default()
83 }
84 }
85 pub fn number() -> Self {
86 Self {
87 schema_type: Some(TypeArray::single("number")),
88 ..Default::default()
89 }
90 }
91 pub fn boolean() -> Self {
92 Self {
93 schema_type: Some(TypeArray::single("boolean")),
94 ..Default::default()
95 }
96 }
97 pub fn array(items: JsonSchema2020) -> Self {
98 Self {
99 schema_type: Some(TypeArray::single("array")),
100 items: Some(Box::new(items)),
101 ..Default::default()
102 }
103 }
104 pub fn object() -> Self {
105 Self {
106 schema_type: Some(TypeArray::single("object")),
107 properties: Some(BTreeMap::new()),
108 ..Default::default()
109 }
110 }
111}
112
113#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
114#[serde(untagged)]
115pub enum AdditionalProperties {
116 Bool(bool),
117 Schema(Box<JsonSchema2020>),
118}
119
120#[derive(Debug, Clone, Serialize, Deserialize)]
121#[serde(untagged)]
122pub enum SchemaRef {
123 Ref {
124 #[serde(rename = "$ref")]
125 reference: String,
126 },
127 Schema(Box<JsonSchema2020>),
128 Inline(serde_json::Value),
129}
130
131pub struct SchemaCtx {
132 pub components: BTreeMap<String, JsonSchema2020>,
133}
134
135impl Default for SchemaCtx {
136 fn default() -> Self {
137 Self::new()
138 }
139}
140
141impl SchemaCtx {
142 pub fn new() -> Self {
143 Self {
144 components: BTreeMap::new(),
145 }
146 }
147}
148
149pub trait RustApiSchema {
150 fn schema(ctx: &mut SchemaCtx) -> SchemaRef;
151 fn component_name() -> Option<&'static str> {
152 None
153 }
154
155 fn field_schemas(_ctx: &mut SchemaCtx) -> Option<BTreeMap<String, SchemaRef>> {
157 None
158 }
159}
160
161impl RustApiSchema for String {
163 fn schema(_: &mut SchemaCtx) -> SchemaRef {
164 SchemaRef::Schema(Box::new(JsonSchema2020::string()))
165 }
166}
167impl RustApiSchema for &str {
168 fn schema(_: &mut SchemaCtx) -> SchemaRef {
169 SchemaRef::Schema(Box::new(JsonSchema2020::string()))
170 }
171}
172impl RustApiSchema for bool {
173 fn schema(_: &mut SchemaCtx) -> SchemaRef {
174 SchemaRef::Schema(Box::new(JsonSchema2020::boolean()))
175 }
176}
177impl RustApiSchema for i32 {
178 fn schema(_: &mut SchemaCtx) -> SchemaRef {
179 let mut s = JsonSchema2020::integer();
180 s.format = Some("int32".to_string());
181 SchemaRef::Schema(Box::new(s))
182 }
183}
184impl RustApiSchema for i64 {
185 fn schema(_: &mut SchemaCtx) -> SchemaRef {
186 let mut s = JsonSchema2020::integer();
187 s.format = Some("int64".to_string());
188 SchemaRef::Schema(Box::new(s))
189 }
190}
191impl RustApiSchema for f64 {
192 fn schema(_: &mut SchemaCtx) -> SchemaRef {
193 let mut s = JsonSchema2020::number();
194 s.format = Some("double".to_string());
195 SchemaRef::Schema(Box::new(s))
196 }
197}
198impl RustApiSchema for f32 {
199 fn schema(_: &mut SchemaCtx) -> SchemaRef {
200 let mut s = JsonSchema2020::number();
201 s.format = Some("float".to_string());
202 SchemaRef::Schema(Box::new(s))
203 }
204}
205
206impl RustApiSchema for i8 {
207 fn schema(_: &mut SchemaCtx) -> SchemaRef {
208 let mut s = JsonSchema2020::integer();
209 s.format = Some("int8".to_string());
210 SchemaRef::Schema(Box::new(s))
211 }
212}
213impl RustApiSchema for i16 {
214 fn schema(_: &mut SchemaCtx) -> SchemaRef {
215 let mut s = JsonSchema2020::integer();
216 s.format = Some("int16".to_string());
217 SchemaRef::Schema(Box::new(s))
218 }
219}
220impl RustApiSchema for isize {
221 fn schema(_: &mut SchemaCtx) -> SchemaRef {
222 let mut s = JsonSchema2020::integer();
223 s.format = Some("int64".to_string());
224 SchemaRef::Schema(Box::new(s))
225 }
226}
227impl RustApiSchema for u8 {
228 fn schema(_: &mut SchemaCtx) -> SchemaRef {
229 let mut s = JsonSchema2020::integer();
230 s.format = Some("uint8".to_string());
231 SchemaRef::Schema(Box::new(s))
232 }
233}
234impl RustApiSchema for u16 {
235 fn schema(_: &mut SchemaCtx) -> SchemaRef {
236 let mut s = JsonSchema2020::integer();
237 s.format = Some("uint16".to_string());
238 SchemaRef::Schema(Box::new(s))
239 }
240}
241impl RustApiSchema for u32 {
242 fn schema(_: &mut SchemaCtx) -> SchemaRef {
243 let mut s = JsonSchema2020::integer();
244 s.format = Some("uint32".to_string());
245 SchemaRef::Schema(Box::new(s))
246 }
247}
248impl RustApiSchema for u64 {
249 fn schema(_: &mut SchemaCtx) -> SchemaRef {
250 let mut s = JsonSchema2020::integer();
251 s.format = Some("uint64".to_string());
252 SchemaRef::Schema(Box::new(s))
253 }
254}
255impl RustApiSchema for usize {
256 fn schema(_: &mut SchemaCtx) -> SchemaRef {
257 let mut s = JsonSchema2020::integer();
258 s.format = Some("uint64".to_string());
259 SchemaRef::Schema(Box::new(s))
260 }
261}
262
263impl<T: RustApiSchema> RustApiSchema for Vec<T> {
265 fn schema(ctx: &mut SchemaCtx) -> SchemaRef {
266 match T::schema(ctx) {
267 SchemaRef::Schema(s) => SchemaRef::Schema(Box::new(JsonSchema2020::array(*s))),
268 SchemaRef::Ref { reference } => {
269 let mut s = JsonSchema2020::new();
271 s.schema_type = Some(TypeArray::single("array"));
272 let mut ref_schema = JsonSchema2020::new();
273 ref_schema.reference = Some(reference);
274 s.items = Some(Box::new(ref_schema));
275 SchemaRef::Schema(Box::new(s))
276 }
277 SchemaRef::Inline(_) => SchemaRef::Schema(Box::new(JsonSchema2020 {
278 schema_type: Some(TypeArray::single("array")),
279 ..Default::default()
282 })),
283 }
284 }
285}
286
287impl<T: RustApiSchema> RustApiSchema for Option<T> {
289 fn schema(ctx: &mut SchemaCtx) -> SchemaRef {
290 let inner = T::schema(ctx);
291 match inner {
292 SchemaRef::Schema(mut s) => {
293 if let Some(t) = s.schema_type {
294 s.schema_type = Some(TypeArray::nullable(match t {
295 TypeArray::Single(v) => v,
296 TypeArray::Array(v) => v[0].clone(), }));
298 }
299 SchemaRef::Schema(s)
300 }
301 SchemaRef::Ref { reference } => {
302 let mut s = JsonSchema2020::new();
304 let mut ref_s = JsonSchema2020::new();
305 ref_s.reference = Some(reference);
306 let mut null_s = JsonSchema2020::new();
307 null_s.schema_type = Some(TypeArray::single("null"));
308 s.one_of = Some(vec![ref_s, null_s]);
309 SchemaRef::Schema(Box::new(s))
310 }
311 _ => inner,
312 }
313 }
314}
315
316impl<T: RustApiSchema> RustApiSchema for std::collections::HashMap<String, T> {
318 fn schema(ctx: &mut SchemaCtx) -> SchemaRef {
319 let inner = T::schema(ctx);
320 let mut s = JsonSchema2020::object();
321
322 let add_prop = match inner {
323 SchemaRef::Schema(is) => AdditionalProperties::Schema(is),
324 SchemaRef::Ref { reference } => {
325 let mut rs = JsonSchema2020::new();
326 rs.reference = Some(reference);
327 AdditionalProperties::Schema(Box::new(rs))
328 }
329 _ => AdditionalProperties::Bool(true),
330 };
331
332 s.additional_properties = Some(Box::new(add_prop));
333 SchemaRef::Schema(Box::new(s))
334 }
335}
336
337pub struct SchemaTransformer;
339impl SchemaTransformer {
340 pub fn transform_30_to_31(v: serde_json::Value) -> serde_json::Value {
341 v
342 }
343}