1use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
12#[serde(rename_all = "camelCase")]
13pub struct JsonSchema {
14 #[serde(rename = "$schema", skip_serializing_if = "Option::is_none")]
15 pub schema: Option<String>,
16
17 #[serde(rename = "$id", skip_serializing_if = "Option::is_none")]
18 pub id: Option<String>,
19
20 #[serde(skip_serializing_if = "Option::is_none")]
21 pub title: Option<String>,
22
23 #[serde(skip_serializing_if = "Option::is_none")]
24 pub description: Option<String>,
25
26 #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
27 pub schema_type: Option<JsonSchemaType>,
28
29 #[serde(skip_serializing_if = "Option::is_none")]
30 pub format: Option<String>,
31
32 #[serde(skip_serializing_if = "Option::is_none")]
33 pub properties: Option<HashMap<String, JsonSchema>>,
34
35 #[serde(skip_serializing_if = "Option::is_none")]
36 pub required: Option<Vec<String>>,
37
38 #[serde(skip_serializing_if = "Option::is_none")]
39 pub items: Option<Box<JsonSchema>>,
40
41 #[serde(
42 rename = "additionalProperties",
43 skip_serializing_if = "Option::is_none"
44 )]
45 pub additional_properties: Option<AdditionalProperties>,
46
47 #[serde(rename = "minLength", skip_serializing_if = "Option::is_none")]
49 pub min_length: Option<usize>,
50
51 #[serde(rename = "maxLength", skip_serializing_if = "Option::is_none")]
52 pub max_length: Option<usize>,
53
54 #[serde(skip_serializing_if = "Option::is_none")]
55 pub pattern: Option<String>,
56
57 #[serde(skip_serializing_if = "Option::is_none")]
59 pub minimum: Option<f64>,
60
61 #[serde(skip_serializing_if = "Option::is_none")]
62 pub maximum: Option<f64>,
63
64 #[serde(rename = "exclusiveMinimum", skip_serializing_if = "Option::is_none")]
65 pub exclusive_minimum: Option<f64>,
66
67 #[serde(rename = "exclusiveMaximum", skip_serializing_if = "Option::is_none")]
68 pub exclusive_maximum: Option<f64>,
69
70 #[serde(rename = "multipleOf", skip_serializing_if = "Option::is_none")]
71 pub multiple_of: Option<f64>,
72
73 #[serde(rename = "minItems", skip_serializing_if = "Option::is_none")]
75 pub min_items: Option<usize>,
76
77 #[serde(rename = "maxItems", skip_serializing_if = "Option::is_none")]
78 pub max_items: Option<usize>,
79
80 #[serde(rename = "uniqueItems", skip_serializing_if = "Option::is_none")]
81 pub unique_items: Option<bool>,
82
83 #[serde(rename = "enum", skip_serializing_if = "Option::is_none")]
85 pub r#enum: Option<Vec<serde_json::Value>>,
86
87 #[serde(rename = "const", skip_serializing_if = "Option::is_none")]
88 pub r#const: Option<serde_json::Value>,
89
90 #[serde(rename = "$ref", skip_serializing_if = "Option::is_none")]
92 pub reference: Option<String>,
93
94 #[serde(rename = "anyOf", skip_serializing_if = "Option::is_none")]
96 pub any_of: Option<Vec<JsonSchema>>,
97
98 #[serde(rename = "allOf", skip_serializing_if = "Option::is_none")]
99 pub all_of: Option<Vec<JsonSchema>>,
100
101 #[serde(rename = "oneOf", skip_serializing_if = "Option::is_none")]
102 pub one_of: Option<Vec<JsonSchema>>,
103
104 #[serde(skip_serializing_if = "Option::is_none")]
105 pub not: Option<Box<JsonSchema>>,
106
107 #[serde(skip_serializing_if = "Option::is_none")]
109 pub default: Option<serde_json::Value>,
110
111 #[serde(skip_serializing_if = "Option::is_none")]
112 pub examples: Option<Vec<serde_json::Value>>,
113
114 #[serde(skip_serializing_if = "Option::is_none")]
115 pub deprecated: Option<bool>,
116
117 #[serde(rename = "readOnly", skip_serializing_if = "Option::is_none")]
118 pub read_only: Option<bool>,
119
120 #[serde(rename = "writeOnly", skip_serializing_if = "Option::is_none")]
121 pub write_only: Option<bool>,
122
123 #[serde(rename = "$defs", skip_serializing_if = "Option::is_none")]
125 pub defs: Option<HashMap<String, JsonSchema>>,
126}
127
128#[derive(Debug, Clone, Serialize, Deserialize)]
130#[serde(rename_all = "lowercase")]
131pub enum JsonSchemaType {
132 String,
133 Number,
134 Integer,
135 Boolean,
136 Array,
137 Object,
138 Null,
139}
140
141#[derive(Debug, Clone, Serialize, Deserialize)]
143#[serde(untagged)]
144pub enum AdditionalProperties {
145 Bool(bool),
146 Schema(Box<JsonSchema>),
147}
148
149impl JsonSchema {
150 pub fn new() -> Self {
152 Self {
153 schema: None,
154 id: None,
155 title: None,
156 description: None,
157 schema_type: None,
158 format: None,
159 properties: None,
160 required: None,
161 items: None,
162 additional_properties: None,
163 min_length: None,
164 max_length: None,
165 pattern: None,
166 minimum: None,
167 maximum: None,
168 exclusive_minimum: None,
169 exclusive_maximum: None,
170 multiple_of: None,
171 min_items: None,
172 max_items: None,
173 unique_items: None,
174 r#enum: None,
175 r#const: None,
176 reference: None,
177 any_of: None,
178 all_of: None,
179 one_of: None,
180 not: None,
181 default: None,
182 examples: None,
183 deprecated: None,
184 read_only: None,
185 write_only: None,
186 defs: None,
187 }
188 }
189
190 pub fn string() -> Self {
192 Self {
193 schema_type: Some(JsonSchemaType::String),
194 ..Self::new()
195 }
196 }
197
198 pub fn integer() -> Self {
200 Self {
201 schema_type: Some(JsonSchemaType::Integer),
202 ..Self::new()
203 }
204 }
205
206 pub fn number() -> Self {
208 Self {
209 schema_type: Some(JsonSchemaType::Number),
210 ..Self::new()
211 }
212 }
213
214 pub fn boolean() -> Self {
216 Self {
217 schema_type: Some(JsonSchemaType::Boolean),
218 ..Self::new()
219 }
220 }
221
222 pub fn array(items: JsonSchema) -> Self {
224 Self {
225 schema_type: Some(JsonSchemaType::Array),
226 items: Some(Box::new(items)),
227 ..Self::new()
228 }
229 }
230
231 pub fn object() -> Self {
233 Self {
234 schema_type: Some(JsonSchemaType::Object),
235 properties: Some(HashMap::new()),
236 additional_properties: Some(AdditionalProperties::Bool(false)),
237 ..Self::new()
238 }
239 }
240
241 pub fn null() -> Self {
243 Self {
244 schema_type: Some(JsonSchemaType::Null),
245 ..Self::new()
246 }
247 }
248
249 pub fn reference(name: &str) -> Self {
251 Self {
252 reference: Some(format!("#/$defs/{}", name)),
253 ..Self::new()
254 }
255 }
256
257 pub fn title(mut self, title: impl Into<String>) -> Self {
259 self.title = Some(title.into());
260 self
261 }
262
263 pub fn description(mut self, desc: impl Into<String>) -> Self {
265 self.description = Some(desc.into());
266 self
267 }
268
269 pub fn format(mut self, format: impl Into<String>) -> Self {
271 self.format = Some(format.into());
272 self
273 }
274
275 pub fn property(mut self, name: impl Into<String>, schema: JsonSchema) -> Self {
277 self.properties
278 .get_or_insert_with(HashMap::new)
279 .insert(name.into(), schema);
280 self
281 }
282
283 pub fn required(mut self, fields: &[&str]) -> Self {
285 self.required = Some(fields.iter().map(|s| s.to_string()).collect());
286 self
287 }
288
289 pub fn min_length(mut self, min: usize) -> Self {
291 self.min_length = Some(min);
292 self
293 }
294
295 pub fn max_length(mut self, max: usize) -> Self {
297 self.max_length = Some(max);
298 self
299 }
300
301 pub fn pattern(mut self, pattern: impl Into<String>) -> Self {
303 self.pattern = Some(pattern.into());
304 self
305 }
306
307 pub fn minimum(mut self, min: impl Into<f64>) -> Self {
309 self.minimum = Some(min.into());
310 self
311 }
312
313 pub fn maximum(mut self, max: impl Into<f64>) -> Self {
315 self.maximum = Some(max.into());
316 self
317 }
318
319 pub fn exclusive_minimum(mut self, min: impl Into<f64>) -> Self {
321 self.exclusive_minimum = Some(min.into());
322 self
323 }
324
325 pub fn exclusive_maximum(mut self, max: impl Into<f64>) -> Self {
327 self.exclusive_maximum = Some(max.into());
328 self
329 }
330
331 pub fn multiple_of(mut self, divisor: impl Into<f64>) -> Self {
333 self.multiple_of = Some(divisor.into());
334 self
335 }
336
337 pub fn min_items(mut self, min: usize) -> Self {
339 self.min_items = Some(min);
340 self
341 }
342
343 pub fn max_items(mut self, max: usize) -> Self {
345 self.max_items = Some(max);
346 self
347 }
348
349 pub fn unique_items(mut self, unique: bool) -> Self {
351 self.unique_items = Some(unique);
352 self
353 }
354
355 pub fn enum_values<T: Serialize>(mut self, values: &[T]) -> Self {
357 self.r#enum = Some(
358 values
359 .iter()
360 .map(|v| serde_json::to_value(v).expect("Failed to serialize enum value"))
361 .collect(),
362 );
363 self
364 }
365
366 pub fn const_value<T: Serialize>(mut self, value: T) -> Self {
368 self.r#const = Some(serde_json::to_value(value).expect("Failed to serialize const value"));
369 self
370 }
371
372 pub fn default<T: Serialize>(mut self, value: T) -> Self {
374 self.default =
375 Some(serde_json::to_value(value).expect("Failed to serialize default value"));
376 self
377 }
378
379 pub fn examples<T: Serialize>(mut self, values: Vec<T>) -> Self {
381 self.examples = Some(
382 values
383 .into_iter()
384 .map(|v| serde_json::to_value(v).expect("Failed to serialize example value"))
385 .collect(),
386 );
387 self
388 }
389
390 pub fn deprecated(mut self, deprecated: bool) -> Self {
392 self.deprecated = Some(deprecated);
393 self
394 }
395
396 pub fn read_only(mut self, read_only: bool) -> Self {
398 self.read_only = Some(read_only);
399 self
400 }
401
402 pub fn write_only(mut self, write_only: bool) -> Self {
404 self.write_only = Some(write_only);
405 self
406 }
407
408 pub fn any_of(schemas: Vec<JsonSchema>) -> Self {
410 Self {
411 any_of: Some(schemas),
412 ..Self::new()
413 }
414 }
415
416 pub fn all_of(schemas: Vec<JsonSchema>) -> Self {
418 Self {
419 all_of: Some(schemas),
420 ..Self::new()
421 }
422 }
423
424 pub fn one_of(schemas: Vec<JsonSchema>) -> Self {
426 Self {
427 one_of: Some(schemas),
428 ..Self::new()
429 }
430 }
431
432 pub fn negation(schema: JsonSchema) -> Self {
434 Self {
435 not: Some(Box::new(schema)),
436 ..Self::new()
437 }
438 }
439}
440
441impl Default for JsonSchema {
442 fn default() -> Self {
443 Self::new()
444 }
445}
446
447pub trait ToJsonSchema {
476 fn schema_name() -> &'static str;
478
479 fn json_schema() -> JsonSchema;
481}
482
483impl ToJsonSchema for String {
485 fn schema_name() -> &'static str {
486 "string"
487 }
488
489 fn json_schema() -> JsonSchema {
490 JsonSchema::string()
491 }
492}
493
494impl ToJsonSchema for str {
495 fn schema_name() -> &'static str {
496 "string"
497 }
498
499 fn json_schema() -> JsonSchema {
500 JsonSchema::string()
501 }
502}
503
504impl ToJsonSchema for u8 {
505 fn schema_name() -> &'static str {
506 "integer"
507 }
508
509 fn json_schema() -> JsonSchema {
510 JsonSchema::integer().minimum(0).maximum(255)
511 }
512}
513
514impl ToJsonSchema for u16 {
515 fn schema_name() -> &'static str {
516 "integer"
517 }
518
519 fn json_schema() -> JsonSchema {
520 JsonSchema::integer().minimum(0).maximum(65535)
521 }
522}
523
524impl ToJsonSchema for u32 {
525 fn schema_name() -> &'static str {
526 "integer"
527 }
528
529 fn json_schema() -> JsonSchema {
530 JsonSchema::integer().minimum(0)
531 }
532}
533
534impl ToJsonSchema for i32 {
535 fn schema_name() -> &'static str {
536 "integer"
537 }
538
539 fn json_schema() -> JsonSchema {
540 JsonSchema::integer()
541 }
542}
543
544impl ToJsonSchema for i64 {
545 fn schema_name() -> &'static str {
546 "integer"
547 }
548
549 fn json_schema() -> JsonSchema {
550 JsonSchema::integer()
551 }
552}
553
554impl ToJsonSchema for f32 {
555 fn schema_name() -> &'static str {
556 "number"
557 }
558
559 fn json_schema() -> JsonSchema {
560 JsonSchema::number()
561 }
562}
563
564impl ToJsonSchema for f64 {
565 fn schema_name() -> &'static str {
566 "number"
567 }
568
569 fn json_schema() -> JsonSchema {
570 JsonSchema::number()
571 }
572}
573
574impl ToJsonSchema for bool {
575 fn schema_name() -> &'static str {
576 "boolean"
577 }
578
579 fn json_schema() -> JsonSchema {
580 JsonSchema::boolean()
581 }
582}
583
584impl<T: ToJsonSchema> ToJsonSchema for Vec<T> {
585 fn schema_name() -> &'static str {
586 "array"
587 }
588
589 fn json_schema() -> JsonSchema {
590 JsonSchema::array(T::json_schema())
591 }
592}
593
594impl<T: ToJsonSchema> ToJsonSchema for Option<T> {
595 fn schema_name() -> &'static str {
596 T::schema_name()
597 }
598
599 fn json_schema() -> JsonSchema {
600 T::json_schema()
601 }
602}
603
604pub struct JsonSchemaBuilder {
606 id: Option<String>,
607 title: Option<String>,
608 description: Option<String>,
609 defs: HashMap<String, JsonSchema>,
610}
611
612impl JsonSchemaBuilder {
613 pub fn new() -> Self {
615 Self {
616 id: None,
617 title: None,
618 description: None,
619 defs: HashMap::new(),
620 }
621 }
622
623 pub fn id(mut self, id: impl Into<String>) -> Self {
625 self.id = Some(id.into());
626 self
627 }
628
629 pub fn title(mut self, title: impl Into<String>) -> Self {
631 self.title = Some(title.into());
632 self
633 }
634
635 pub fn description(mut self, desc: impl Into<String>) -> Self {
637 self.description = Some(desc.into());
638 self
639 }
640
641 pub fn register<T: ToJsonSchema>(mut self) -> Self {
643 self.defs
644 .insert(T::schema_name().to_string(), T::json_schema());
645 self
646 }
647
648 pub fn add_schema(mut self, name: impl Into<String>, schema: JsonSchema) -> Self {
650 self.defs.insert(name.into(), schema);
651 self
652 }
653
654 pub fn build(self) -> JsonSchema {
656 JsonSchema {
657 schema: Some("https://json-schema.org/draft/2020-12/schema".to_string()),
658 id: self.id,
659 title: self.title,
660 description: self.description,
661 defs: if self.defs.is_empty() {
662 None
663 } else {
664 Some(self.defs)
665 },
666 ..JsonSchema::new()
667 }
668 }
669
670 pub fn to_json(&self) -> Result<String, serde_json::Error> {
672 let schema = JsonSchema {
673 schema: Some("https://json-schema.org/draft/2020-12/schema".to_string()),
674 id: self.id.clone(),
675 title: self.title.clone(),
676 description: self.description.clone(),
677 defs: if self.defs.is_empty() {
678 None
679 } else {
680 Some(self.defs.clone())
681 },
682 ..JsonSchema::new()
683 };
684 serde_json::to_string_pretty(&schema)
685 }
686}
687
688impl Default for JsonSchemaBuilder {
689 fn default() -> Self {
690 Self::new()
691 }
692}
693
694#[cfg(test)]
695mod tests {
696 use super::*;
697
698 #[test]
699 fn test_string_schema() {
700 let schema = JsonSchema::string().min_length(3).max_length(50);
701 assert!(matches!(schema.schema_type, Some(JsonSchemaType::String)));
702 assert_eq!(schema.min_length, Some(3));
703 assert_eq!(schema.max_length, Some(50));
704 }
705
706 #[test]
707 fn test_integer_schema() {
708 let schema = JsonSchema::integer().minimum(0).maximum(100);
709 assert!(matches!(schema.schema_type, Some(JsonSchemaType::Integer)));
710 assert_eq!(schema.minimum, Some(0.0));
711 assert_eq!(schema.maximum, Some(100.0));
712 }
713
714 #[test]
715 fn test_object_schema() {
716 let schema = JsonSchema::object()
717 .property("name", JsonSchema::string())
718 .property("age", JsonSchema::integer())
719 .required(&["name"]);
720
721 assert!(matches!(schema.schema_type, Some(JsonSchemaType::Object)));
722 assert_eq!(schema.properties.as_ref().unwrap().len(), 2);
723 assert!(schema
724 .required
725 .as_ref()
726 .unwrap()
727 .contains(&"name".to_string()));
728 }
729
730 #[test]
731 fn test_array_schema() {
732 let schema = JsonSchema::array(JsonSchema::string())
733 .min_items(1)
734 .max_items(10);
735 assert!(matches!(schema.schema_type, Some(JsonSchemaType::Array)));
736 assert!(schema.items.is_some());
737 assert_eq!(schema.min_items, Some(1));
738 }
739
740 #[test]
741 fn test_reference() {
742 let schema = JsonSchema::reference("User");
743 assert_eq!(schema.reference, Some("#/$defs/User".to_string()));
744 }
745
746 #[test]
747 fn test_any_of() {
748 let schema = JsonSchema::any_of(vec![JsonSchema::string(), JsonSchema::integer()]);
749 assert!(schema.any_of.is_some());
750 assert_eq!(schema.any_of.as_ref().unwrap().len(), 2);
751 }
752
753 #[test]
754 fn test_builder() {
755 struct User;
756 impl ToJsonSchema for User {
757 fn schema_name() -> &'static str {
758 "User"
759 }
760
761 fn json_schema() -> JsonSchema {
762 JsonSchema::object()
763 .property("email", JsonSchema::string().format("email"))
764 .required(&["email"])
765 }
766 }
767
768 let doc = JsonSchemaBuilder::new()
769 .title("My Schema")
770 .register::<User>()
771 .build();
772
773 assert!(doc.schema.is_some());
774 assert_eq!(doc.title, Some("My Schema".to_string()));
775 assert!(doc.defs.as_ref().unwrap().contains_key("User"));
776 }
777
778 #[test]
779 fn test_serialization() {
780 let schema = JsonSchema::object()
781 .property("email", JsonSchema::string().format("email"))
782 .property("age", JsonSchema::integer().minimum(0))
783 .required(&["email", "age"]);
784
785 let json = serde_json::to_string(&schema).unwrap();
786 assert!(json.contains("\"type\":\"object\""));
787 assert!(json.contains("\"email\""));
788 }
789}