1use 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 name() -> std::borrow::Cow<'static, str> {
158 std::borrow::Cow::Borrowed("Unknown")
159 }
160
161 fn field_schemas(_ctx: &mut SchemaCtx) -> Option<BTreeMap<String, SchemaRef>> {
163 None
164 }
165}
166
167impl RustApiSchema for String {
169 fn schema(_: &mut SchemaCtx) -> SchemaRef {
170 SchemaRef::Schema(Box::new(JsonSchema2020::string()))
171 }
172 fn name() -> std::borrow::Cow<'static, str> {
173 std::borrow::Cow::Borrowed("String")
174 }
175}
176impl RustApiSchema for &str {
177 fn schema(_: &mut SchemaCtx) -> SchemaRef {
178 SchemaRef::Schema(Box::new(JsonSchema2020::string()))
179 }
180 fn name() -> std::borrow::Cow<'static, str> {
181 std::borrow::Cow::Borrowed("String")
182 }
183}
184impl RustApiSchema for bool {
185 fn schema(_: &mut SchemaCtx) -> SchemaRef {
186 SchemaRef::Schema(Box::new(JsonSchema2020::boolean()))
187 }
188 fn name() -> std::borrow::Cow<'static, str> {
189 std::borrow::Cow::Borrowed("Boolean")
190 }
191}
192impl RustApiSchema for i32 {
193 fn schema(_: &mut SchemaCtx) -> SchemaRef {
194 let mut s = JsonSchema2020::integer();
195 s.format = Some("int32".to_string());
196 SchemaRef::Schema(Box::new(s))
197 }
198 fn name() -> std::borrow::Cow<'static, str> {
199 std::borrow::Cow::Borrowed("Int32")
200 }
201}
202impl RustApiSchema for i64 {
203 fn schema(_: &mut SchemaCtx) -> SchemaRef {
204 let mut s = JsonSchema2020::integer();
205 s.format = Some("int64".to_string());
206 SchemaRef::Schema(Box::new(s))
207 }
208 fn name() -> std::borrow::Cow<'static, str> {
209 std::borrow::Cow::Borrowed("Int64")
210 }
211}
212impl RustApiSchema for f64 {
213 fn schema(_: &mut SchemaCtx) -> SchemaRef {
214 let mut s = JsonSchema2020::number();
215 s.format = Some("double".to_string());
216 SchemaRef::Schema(Box::new(s))
217 }
218 fn name() -> std::borrow::Cow<'static, str> {
219 std::borrow::Cow::Borrowed("Float64")
220 }
221}
222impl RustApiSchema for f32 {
223 fn schema(_: &mut SchemaCtx) -> SchemaRef {
224 let mut s = JsonSchema2020::number();
225 s.format = Some("float".to_string());
226 SchemaRef::Schema(Box::new(s))
227 }
228 fn name() -> std::borrow::Cow<'static, str> {
229 std::borrow::Cow::Borrowed("Float32")
230 }
231}
232
233impl RustApiSchema for i8 {
234 fn schema(_: &mut SchemaCtx) -> SchemaRef {
235 let mut s = JsonSchema2020::integer();
236 s.format = Some("int8".to_string());
237 SchemaRef::Schema(Box::new(s))
238 }
239 fn name() -> std::borrow::Cow<'static, str> {
240 std::borrow::Cow::Borrowed("Int8")
241 }
242}
243impl RustApiSchema for i16 {
244 fn schema(_: &mut SchemaCtx) -> SchemaRef {
245 let mut s = JsonSchema2020::integer();
246 s.format = Some("int16".to_string());
247 SchemaRef::Schema(Box::new(s))
248 }
249 fn name() -> std::borrow::Cow<'static, str> {
250 std::borrow::Cow::Borrowed("Int16")
251 }
252}
253impl RustApiSchema for isize {
254 fn schema(_: &mut SchemaCtx) -> SchemaRef {
255 let mut s = JsonSchema2020::integer();
256 s.format = Some("int64".to_string());
257 SchemaRef::Schema(Box::new(s))
258 }
259 fn name() -> std::borrow::Cow<'static, str> {
260 std::borrow::Cow::Borrowed("Int64")
261 }
262}
263impl RustApiSchema for u8 {
264 fn schema(_: &mut SchemaCtx) -> SchemaRef {
265 let mut s = JsonSchema2020::integer();
266 s.format = Some("uint8".to_string());
267 SchemaRef::Schema(Box::new(s))
268 }
269 fn name() -> std::borrow::Cow<'static, str> {
270 std::borrow::Cow::Borrowed("Uint8")
271 }
272}
273impl RustApiSchema for u16 {
274 fn schema(_: &mut SchemaCtx) -> SchemaRef {
275 let mut s = JsonSchema2020::integer();
276 s.format = Some("uint16".to_string());
277 SchemaRef::Schema(Box::new(s))
278 }
279 fn name() -> std::borrow::Cow<'static, str> {
280 std::borrow::Cow::Borrowed("Uint16")
281 }
282}
283impl RustApiSchema for u32 {
284 fn schema(_: &mut SchemaCtx) -> SchemaRef {
285 let mut s = JsonSchema2020::integer();
286 s.format = Some("uint32".to_string());
287 SchemaRef::Schema(Box::new(s))
288 }
289 fn name() -> std::borrow::Cow<'static, str> {
290 std::borrow::Cow::Borrowed("Uint32")
291 }
292}
293impl RustApiSchema for u64 {
294 fn schema(_: &mut SchemaCtx) -> SchemaRef {
295 let mut s = JsonSchema2020::integer();
296 s.format = Some("uint64".to_string());
297 SchemaRef::Schema(Box::new(s))
298 }
299 fn name() -> std::borrow::Cow<'static, str> {
300 std::borrow::Cow::Borrowed("Uint64")
301 }
302}
303impl RustApiSchema for usize {
304 fn schema(_: &mut SchemaCtx) -> SchemaRef {
305 let mut s = JsonSchema2020::integer();
306 s.format = Some("uint64".to_string());
307 SchemaRef::Schema(Box::new(s))
308 }
309 fn name() -> std::borrow::Cow<'static, str> {
310 std::borrow::Cow::Borrowed("Uint64")
311 }
312}
313
314impl<T: RustApiSchema> RustApiSchema for Vec<T> {
316 fn schema(ctx: &mut SchemaCtx) -> SchemaRef {
317 match T::schema(ctx) {
318 SchemaRef::Schema(s) => SchemaRef::Schema(Box::new(JsonSchema2020::array(*s))),
319 SchemaRef::Ref { reference } => {
320 let mut s = JsonSchema2020::new();
322 s.schema_type = Some(TypeArray::single("array"));
323 let mut ref_schema = JsonSchema2020::new();
324 ref_schema.reference = Some(reference);
325 s.items = Some(Box::new(ref_schema));
326 SchemaRef::Schema(Box::new(s))
327 }
328 SchemaRef::Inline(_) => SchemaRef::Schema(Box::new(JsonSchema2020 {
329 schema_type: Some(TypeArray::single("array")),
330 ..Default::default()
333 })),
334 }
335 }
336 fn name() -> std::borrow::Cow<'static, str> {
337 format!("Array_{}", T::name()).into()
338 }
339}
340
341impl<T: RustApiSchema> RustApiSchema for Option<T> {
343 fn schema(ctx: &mut SchemaCtx) -> SchemaRef {
344 let inner = T::schema(ctx);
345 match inner {
346 SchemaRef::Schema(mut s) => {
347 if let Some(t) = s.schema_type {
348 s.schema_type = Some(TypeArray::nullable(match t {
349 TypeArray::Single(v) => v,
350 TypeArray::Array(v) => v[0].clone(), }));
352 }
353 SchemaRef::Schema(s)
354 }
355 SchemaRef::Ref { reference } => {
356 let mut s = JsonSchema2020::new();
358 let mut ref_s = JsonSchema2020::new();
359 ref_s.reference = Some(reference);
360 let mut null_s = JsonSchema2020::new();
361 null_s.schema_type = Some(TypeArray::single("null"));
362 s.one_of = Some(vec![ref_s, null_s]);
363 SchemaRef::Schema(Box::new(s))
364 }
365 _ => inner,
366 }
367 }
368 fn name() -> std::borrow::Cow<'static, str> {
369 format!("Option_{}", T::name()).into()
370 }
371}
372
373impl<T: RustApiSchema> RustApiSchema for std::collections::HashMap<String, T> {
375 fn schema(ctx: &mut SchemaCtx) -> SchemaRef {
376 let inner = T::schema(ctx);
377 let mut s = JsonSchema2020::object();
378
379 let add_prop = match inner {
380 SchemaRef::Schema(is) => AdditionalProperties::Schema(is),
381 SchemaRef::Ref { reference } => {
382 let mut rs = JsonSchema2020::new();
383 rs.reference = Some(reference);
384 AdditionalProperties::Schema(Box::new(rs))
385 }
386 _ => AdditionalProperties::Bool(true),
387 };
388
389 s.additional_properties = Some(Box::new(add_prop));
390 SchemaRef::Schema(Box::new(s))
391 }
392 fn name() -> std::borrow::Cow<'static, str> {
393 format!("Map_{}", T::name()).into()
394 }
395}
396
397pub struct SchemaTransformer;
399impl SchemaTransformer {
400 pub fn transform_30_to_31(v: serde_json::Value) -> serde_json::Value {
401 v
402 }
403}