1#[cfg(test)]
2use pretty_assertions::assert_eq;
3
4use std::{fmt::Display, fmt::Write};
5
6struct IndentationWriter<'a> {
7 indentation: usize,
8 writer: &'a mut dyn std::fmt::Write,
9}
10
11impl<'a> IndentationWriter<'a> {
12 fn new(indentation: usize, writer: &'a mut dyn std::fmt::Write) -> Self {
13 Self {
14 indentation,
15 writer,
16 }
17 }
18
19 fn with_indent<O>(&mut self, f: impl FnOnce(&mut Self) -> O) -> O {
20 self.indentation += 1;
21 let out = f(self);
22 self.indentation -= 1;
23 out
24 }
25}
26
27impl std::fmt::Write for IndentationWriter<'_> {
28 fn write_str(&mut self, s: &str) -> std::fmt::Result {
29 for char in s.chars() {
30 self.writer.write_char(char)?;
31 if char == '\n' {
32 for _ in 0..self.indentation {
33 self.writer.write_char('\t')?;
34 }
35 }
36 }
37 Ok(())
38 }
39}
40
41#[derive(Debug, Clone)]
43pub enum SchemaLiteral {
44 String(String),
46 Number(f64),
48 Boolean(bool),
50 Null,
52}
53
54impl Display for SchemaLiteral {
55 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
56 match self {
57 SchemaLiteral::String(string) => write!(f, "\"{}\"", string),
58 SchemaLiteral::Number(number) => write!(f, "{}", number),
59 SchemaLiteral::Boolean(boolean) => write!(f, "{}", boolean),
60 SchemaLiteral::Null => write!(f, "null"),
61 }
62 }
63}
64
65#[derive(Debug, Clone)]
67pub enum SchemaType {
68 String(StringSchema),
70 Number(NumberSchema),
72 Integer(IntegerSchema),
74 Boolean(BooleanSchema),
76 Array(ArraySchema),
78 Object(JsonObjectSchema),
80 Enum(EnumSchema),
82 AnyOf(AnyOfSchema),
84 OneOf(OneOfSchema),
86 Const(ConstSchema),
88 IfThen(IfThenSchema),
90 Null,
92}
93
94impl SchemaType {
95 fn display_with_description(
96 &self,
97 f: &mut std::fmt::Formatter<'_>,
98 description: Option<&str>,
99 ) -> std::fmt::Result {
100 match self {
101 SchemaType::String(schema) => schema.display_with_description(f, description),
102 SchemaType::Number(schema) => schema.display_with_description(f, description),
103 SchemaType::Integer(schema) => schema.display_with_description(f, description),
104 SchemaType::Boolean(schema) => schema.display_with_description(f, description),
105 SchemaType::Array(schema) => schema.display_with_description(f, description),
106 SchemaType::Object(schema) => schema.display_with_description(f, description),
107 SchemaType::Enum(schema) => schema.display_with_description(f, description),
108 SchemaType::AnyOf(schema) => schema.display_with_description(f, description),
109 SchemaType::OneOf(schema) => schema.display_with_description(f, description),
110 SchemaType::Const(schema) => schema.display_with_description(f, description),
111 SchemaType::IfThen(schema) => schema.display_with_description(f, description),
112 SchemaType::Null => match description {
113 Some(description) => f.write_fmt(format_args!(
114 "{{\n\t\"description\": \"{description}\",\n\t\"type\": \"null\"\n}}"
115 )),
116 None => f.write_str("{ \"type\": \"null\" }"),
117 },
118 }
119 }
120}
121
122impl Display for SchemaType {
123 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
124 self.display_with_description(f, None)
125 }
126}
127
128#[derive(Debug, Clone)]
130pub struct IfThenSchema {
131 if_schema: Box<SchemaType>,
132 then_schema: Box<SchemaType>,
133}
134
135impl IfThenSchema {
136 pub fn new(if_schema: SchemaType, then_schema: SchemaType) -> Self {
138 Self {
139 if_schema: Box::new(if_schema),
140 then_schema: Box::new(then_schema),
141 }
142 }
143
144 fn display_with_description(
145 &self,
146 f: &mut std::fmt::Formatter<'_>,
147 description: Option<&str>,
148 ) -> std::fmt::Result {
149 f.write_char('{')?;
150 {
151 let mut writer = IndentationWriter::new(1, f);
152 if let Some(description) = description {
153 write!(&mut writer, "\n\"description\": \"{description}\",")?;
154 }
155 writer.write_str("\n\"if\": ")?;
156 write!(&mut writer, "{}", self.if_schema)?;
157 writer.write_str(",\n\"then\": ")?;
158 write!(&mut writer, "{}", self.then_schema)?;
159 }
160 f.write_str("\n}")
161 }
162}
163
164impl Display for IfThenSchema {
165 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
166 self.display_with_description(f, None)
167 }
168}
169
170#[derive(Debug, Clone)]
172pub struct AnyOfSchema {
173 any_of: Vec<SchemaType>,
174}
175
176impl AnyOfSchema {
177 pub fn new(any_of: impl IntoIterator<Item = SchemaType>) -> Self {
179 Self {
180 any_of: any_of.into_iter().collect(),
181 }
182 }
183
184 fn display_with_description(
185 &self,
186 f: &mut std::fmt::Formatter<'_>,
187 description: Option<&str>,
188 ) -> std::fmt::Result {
189 f.write_char('{')?;
190 {
191 let mut writer = IndentationWriter::new(1, f);
192 if let Some(description) = description {
193 write!(&mut writer, "\n\"description\": \"{description}\",")?;
194 }
195 writer.write_str("\n\"anyOf\": [")?;
196 if !self.any_of.is_empty() {
197 writer.with_indent(|writer| {
198 for (i, schema) in self.any_of.iter().enumerate() {
199 if i > 0 {
200 writer.write_char(',')?;
201 }
202 write!(writer, "\n{}", schema)?;
203 }
204 Ok(())
205 })?;
206 writer.write_str("\n")?;
207 }
208 writer.write_str("]")?;
209 }
210 f.write_str("\n}")
211 }
212}
213
214impl Display for AnyOfSchema {
215 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
216 self.display_with_description(f, None)
217 }
218}
219
220#[derive(Debug, Clone)]
222pub struct OneOfSchema {
223 one_of: Vec<SchemaType>,
224}
225
226impl OneOfSchema {
227 pub fn new(one_of: impl IntoIterator<Item = SchemaType>) -> Self {
229 Self {
230 one_of: one_of.into_iter().collect(),
231 }
232 }
233
234 fn display_with_description(
235 &self,
236 f: &mut std::fmt::Formatter<'_>,
237 description: Option<&str>,
238 ) -> std::fmt::Result {
239 f.write_char('{')?;
240 {
241 let mut writer = IndentationWriter::new(1, f);
242 if let Some(description) = description {
243 write!(&mut writer, "\n\"description\": \"{description}\",")?;
244 }
245 writer.write_str("\n\"oneOf\": [")?;
246 if !self.one_of.is_empty() {
247 writer.with_indent(|writer| {
248 for (i, schema) in self.one_of.iter().enumerate() {
249 if i > 0 {
250 writer.write_char(',')?;
251 }
252 write!(writer, "\n{}", schema)?;
253 }
254 Ok(())
255 })?;
256 writer.write_str("\n")?;
257 }
258 writer.write_str("]")?;
259 }
260 f.write_str("\n}")
261 }
262}
263
264impl Display for OneOfSchema {
265 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
266 self.display_with_description(f, None)
267 }
268}
269
270#[derive(Debug, Clone)]
272pub struct ConstSchema {
273 value: SchemaLiteral,
274}
275
276impl ConstSchema {
277 pub fn new(value: impl Into<SchemaLiteral>) -> Self {
279 Self {
280 value: value.into(),
281 }
282 }
283
284 fn display_with_description(
285 &self,
286 f: &mut std::fmt::Formatter<'_>,
287 description: Option<&str>,
288 ) -> std::fmt::Result {
289 if let Some(description) = description {
290 write!(
291 f,
292 "{{\n\t\"descripiton\": \"{description}\"\n\t\"const\": {}\n}}",
293 self.value
294 )
295 } else {
296 write!(f, "{{ \"const\": {} }}", self.value)
297 }
298 }
299}
300
301impl Display for ConstSchema {
302 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
303 self.display_with_description(f, None)
304 }
305}
306
307#[test]
308fn test_const_schema() {
309 let schema = ConstSchema::new(SchemaLiteral::String("hello".to_string()));
310
311 assert_eq!(schema.to_string(), "{ \"const\": \"hello\" }");
312}
313
314#[derive(Debug, Clone)]
316pub struct EnumSchema {
317 variants: Vec<SchemaLiteral>,
318}
319
320impl EnumSchema {
321 pub fn new(variants: impl IntoIterator<Item = SchemaLiteral>) -> Self {
323 Self {
324 variants: variants.into_iter().collect(),
325 }
326 }
327
328 fn display_with_description(
329 &self,
330 f: &mut std::fmt::Formatter<'_>,
331 description: Option<&str>,
332 ) -> std::fmt::Result {
333 f.write_char('{')?;
334 {
335 let mut writer = IndentationWriter::new(1, f);
336 if let Some(description) = description {
337 write!(&mut writer, "\n\"description\": \"{description}\",")?;
338 }
339 writer.write_str("\n\"enum\": [")?;
340 {
341 for (i, variant) in self.variants.iter().enumerate() {
342 if i > 0 {
343 writer.write_str(", ")?;
344 }
345 write!(writer, "{}", variant)?;
346 }
347 }
348 writer.write_str("]\n")?;
349 }
350 f.write_str(" }")
351 }
352}
353
354impl Display for EnumSchema {
355 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
356 self.display_with_description(f, None)
357 }
358}
359
360#[derive(Debug, Clone)]
362pub struct StringSchema {
363 length: Option<std::ops::RangeInclusive<usize>>,
365 pattern: Option<String>,
367}
368
369impl Schema for String {
370 fn schema() -> SchemaType {
371 SchemaType::String(StringSchema::new())
372 }
373}
374
375impl Default for StringSchema {
376 fn default() -> Self {
377 Self::new()
378 }
379}
380
381impl StringSchema {
382 pub fn new() -> Self {
384 Self {
385 length: None,
386 pattern: None,
387 }
388 }
389
390 pub fn with_length(
392 mut self,
393 length: impl Into<Option<std::ops::RangeInclusive<usize>>>,
394 ) -> Self {
395 self.length = length.into();
396 self
397 }
398
399 pub fn with_pattern(mut self, pattern: impl ToString) -> Self {
401 self.pattern = Some(pattern.to_string());
402 self
403 }
404
405 fn display_with_description(
406 &self,
407 f: &mut std::fmt::Formatter<'_>,
408 description: Option<&str>,
409 ) -> std::fmt::Result {
410 f.write_char('{')?;
411 {
412 let mut writer = IndentationWriter::new(1, f);
413 if let Some(description) = description {
414 write!(&mut writer, "\n\"description\": \"{description}\",")?;
415 }
416 writer.write_str("\n\"type\": \"string\"")?;
417 if let Some(length) = &self.length {
418 if *length.start() > 0 {
419 writer.write_fmt(format_args!(",\n\"minLength\": {}", length.start()))?;
420 }
421 if *length.end() < usize::MAX {
422 writer.write_fmt(format_args!(",\n\"maxLength\": {}", length.end()))?;
423 }
424 }
425 if let Some(pattern) = &self.pattern {
426 writer.write_fmt(format_args!(",\n\"pattern\": \"{}\"", pattern))?;
427 }
428 }
429 f.write_str("\n}")
430 }
431}
432
433impl Display for StringSchema {
434 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
435 self.display_with_description(f, None)
436 }
437}
438
439#[derive(Debug, Clone)]
441pub struct NumberSchema {
442 range: Option<std::ops::RangeInclusive<f64>>,
444}
445
446macro_rules! impl_schema_for_number {
447 ($ty:ty) => {
448 impl Schema for $ty {
449 fn schema() -> SchemaType {
450 SchemaType::Number(NumberSchema::new())
451 }
452 }
453 };
454}
455
456impl_schema_for_number!(f64);
457impl_schema_for_number!(f32);
458
459impl Default for NumberSchema {
460 fn default() -> Self {
461 Self::new()
462 }
463}
464
465impl NumberSchema {
466 pub fn new() -> Self {
468 Self { range: None }
469 }
470
471 pub fn with_range(mut self, range: impl Into<Option<std::ops::RangeInclusive<f64>>>) -> Self {
473 self.range = range.into();
474 self
475 }
476
477 fn display_with_description(
478 &self,
479 f: &mut std::fmt::Formatter<'_>,
480 description: Option<&str>,
481 ) -> std::fmt::Result {
482 match &self.range {
483 Some(range) => {
484 f.write_char('{')?;
485 {
486 let mut writer = IndentationWriter::new(1, f);
487 if let Some(description) = description {
488 write!(&mut writer, "\n\"description\": \"{description}\",")?;
489 }
490 writer.write_str("\n\"type\": \"number\",")?;
491 writer.write_fmt(format_args!("\n\"minimum\": {},", range.start()))?;
492 writer.write_fmt(format_args!("\n\"maximum\": {}", range.end()))?;
493 }
494 f.write_str("\n}")
495 }
496 None => f.write_str("{ \"type\": \"number\" }"),
497 }
498 }
499}
500
501impl Display for NumberSchema {
502 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
503 self.display_with_description(f, None)
504 }
505}
506
507#[test]
508fn test_number_schema() {
509 let schema = NumberSchema {
510 range: Some(0.0..=100.0),
511 };
512
513 assert_eq!(
514 schema.to_string(),
515 "{\n\t\"type\": \"number\",\n\t\"minimum\": 0,\n\t\"maximum\": 100\n}"
516 );
517
518 let schema = NumberSchema { range: None };
519
520 assert_eq!(schema.to_string(), "{ \"type\": \"number\" }");
521}
522
523#[derive(Debug, Clone, Default)]
525
526pub struct IntegerSchema;
527
528impl IntegerSchema {
529 pub fn new() -> Self {
531 Self
532 }
533}
534
535macro_rules! impl_schema_for_integer {
536 ($ty:ty) => {
537 impl Schema for $ty {
538 fn schema() -> SchemaType {
539 SchemaType::Number(NumberSchema::new())
540 }
541 }
542 };
543}
544
545impl_schema_for_integer!(i128);
546impl_schema_for_integer!(i64);
547impl_schema_for_integer!(i32);
548impl_schema_for_integer!(i16);
549impl_schema_for_integer!(i8);
550impl_schema_for_integer!(isize);
551
552impl_schema_for_integer!(u128);
553impl_schema_for_integer!(u64);
554impl_schema_for_integer!(u32);
555impl_schema_for_integer!(u16);
556impl_schema_for_integer!(u8);
557impl_schema_for_integer!(usize);
558
559impl IntegerSchema {
560 fn display_with_description(
561 &self,
562 f: &mut std::fmt::Formatter<'_>,
563 description: Option<&str>,
564 ) -> std::fmt::Result {
565 if let Some(description) = description {
566 write!(
567 f,
568 "{{\n\t\"description\": \"{description}\",\n\t\"type\": \"integer\"\n}}"
569 )
570 } else {
571 f.write_str("{ \"type\": \"integer\" }")
572 }
573 }
574}
575
576impl Display for IntegerSchema {
577 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
578 self.display_with_description(f, None)
579 }
580}
581
582#[test]
583fn test_integer_schema() {
584 let schema = IntegerSchema;
585
586 assert_eq!(schema.to_string(), "{ \"type\": \"integer\" }");
587}
588
589#[derive(Debug, Clone, Default)]
591pub struct BooleanSchema;
592
593impl BooleanSchema {
594 pub fn new() -> Self {
596 Self
597 }
598
599 fn display_with_description(
600 &self,
601 f: &mut std::fmt::Formatter<'_>,
602 description: Option<&str>,
603 ) -> std::fmt::Result {
604 if let Some(description) = description {
605 write!(
606 f,
607 "{{\n\t\"description\": \"{description}\",\n\t\"type\": \"boolean\"\n}}"
608 )
609 } else {
610 f.write_str("{ \"type\": \"boolean\" }")
611 }
612 }
613}
614
615impl Display for BooleanSchema {
616 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
617 self.display_with_description(f, None)
618 }
619}
620
621#[test]
622fn test_boolean_schema() {
623 let schema = BooleanSchema;
624
625 assert_eq!(schema.to_string(), "{ \"type\": \"boolean\" }");
626}
627
628#[derive(Debug, Clone)]
630pub struct ArraySchema {
631 items: Box<SchemaType>,
632 length: Option<std::ops::RangeInclusive<usize>>,
633}
634
635impl<T: Schema> Schema for Vec<T> {
636 fn schema() -> SchemaType {
637 SchemaType::Array(ArraySchema::new(T::schema()))
638 }
639}
640
641impl<const N: usize, T: Schema> Schema for [T; N] {
642 fn schema() -> SchemaType {
643 SchemaType::Array(ArraySchema::new(T::schema()).with_length(N..=N))
644 }
645}
646
647impl ArraySchema {
648 pub fn new(items: SchemaType) -> Self {
650 Self {
651 items: Box::new(items),
652 length: None,
653 }
654 }
655
656 pub fn with_length(
658 mut self,
659 length: impl Into<Option<std::ops::RangeInclusive<usize>>>,
660 ) -> Self {
661 self.length = length.into();
662 self
663 }
664
665 fn display_with_description(
666 &self,
667 f: &mut std::fmt::Formatter<'_>,
668 description: Option<&str>,
669 ) -> std::fmt::Result {
670 f.write_char('{')?;
671 {
672 let mut writer = IndentationWriter::new(1, f);
673 if let Some(description) = description {
674 write!(&mut writer, "\n\"description\": \"{description}\",")?;
675 }
676 writer.write_str("\n\"type\": \"array\"")?;
677 writer.write_str(",\n\"items\": ")?;
678 write!(&mut writer, "{}", self.items)?;
679 if let Some(length) = &self.length {
680 if *length.start() > 0 {
681 writer.write_str(",\n\"minItems\": ")?;
682 write!(&mut writer, "{}", length.start())?;
683 }
684 if *length.end() < usize::MAX {
685 writer.write_str(",\n\"maxItems\": ")?;
686 write!(&mut writer, "{}", length.end())?;
687 }
688 }
689 writer.write_str(",\n\"unevaluatedItems\": false")?;
690 }
691 f.write_str("\n}")
692 }
693}
694
695impl Display for ArraySchema {
696 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
697 self.display_with_description(f, None)
698 }
699}
700
701#[test]
702fn test_array_schema() {
703 let schema = ArraySchema {
704 items: Box::new(SchemaType::String(StringSchema {
705 length: Some(1..=10),
706 pattern: None,
707 })),
708 length: Some(0..=10),
709 };
710
711 assert_eq!(schema.to_string(), "{\n\t\"type\": \"array\",\n\t\"items\": {\n\t\t\"type\": \"string\",\n\t\t\"minLength\": 1,\n\t\t\"maxLength\": 10\n\t},\n\t\"maxItems\": 10,\n\t\"unevaluatedItems\": false\n}");
712
713 let schema = ArraySchema {
714 items: Box::new(SchemaType::String(StringSchema {
715 length: None,
716 pattern: None,
717 })),
718 length: Some(1..=usize::MAX),
719 };
720
721 assert_eq!(schema.to_string(), "{\n\t\"type\": \"array\",\n\t\"items\": {\n\t\t\"type\": \"string\"\n\t},\n\t\"minItems\": 1,\n\t\"unevaluatedItems\": false\n}");
722 let schema = ArraySchema {
723 items: Box::new(SchemaType::String(StringSchema {
724 length: None,
725 pattern: None,
726 })),
727 length: None,
728 };
729
730 assert_eq!(schema.to_string(), "{\n\t\"type\": \"array\",\n\t\"items\": {\n\t\t\"type\": \"string\"\n\t},\n\t\"unevaluatedItems\": false\n}");
731}
732
733#[derive(Debug, Clone)]
735pub struct JsonObjectSchema {
736 title: Option<String>,
737 description: Option<&'static str>,
738 properties: Vec<JsonPropertySchema>,
739}
740
741impl JsonObjectSchema {
742 pub fn new(properties: impl IntoIterator<Item = JsonPropertySchema>) -> Self {
744 Self {
745 title: None,
746 description: None,
747 properties: properties.into_iter().collect(),
748 }
749 }
750
751 pub fn with_title(mut self, title: impl ToString) -> Self {
753 self.title = Some(title.to_string());
754 self
755 }
756
757 pub fn with_description(mut self, description: impl Into<Option<&'static str>>) -> Self {
759 self.description = description.into();
760 self
761 }
762
763 fn display_with_description(
764 &self,
765 f: &mut std::fmt::Formatter<'_>,
766 description: Option<&str>,
767 ) -> std::fmt::Result {
768 f.write_char('{')?;
769 {
770 let mut writer = IndentationWriter::new(1, f);
771 writer.write_char('\n')?;
772 if let Some(description) = description {
773 writeln!(&mut writer, "\"description\": \"{description}\",")?;
774 }
775 if let Some(title) = &self.title {
776 writer.write_str("\"title\": \"")?;
777 writer.write_str(title)?;
778 writer.write_str("\",\n")?;
779 }
780 if let Some(description) = &self.description {
781 writer.write_fmt(format_args!("\"description\": \"{}\",\n", description))?;
782 }
783 writer.write_str("\"type\": \"object\",\n")?;
784 writer.write_str("\"properties\": {")?;
785 if !self.properties.is_empty() {
786 writer.with_indent(|writer| {
787 for (i, property) in self.properties.iter().enumerate() {
788 if i > 0 {
789 writer.write_char(',')?;
790 }
791 write!(writer, "\n{}", property)?;
792 }
793 Ok(())
794 })?;
795 writer.write_str("\n")?;
796 }
797 writer.write_str("}")?;
798 let required = self
799 .properties
800 .iter()
801 .filter_map(|property| (property.required).then_some(property.name.clone()))
802 .collect::<Vec<_>>();
803 if !required.is_empty() {
804 writer.write_str(",\n\"required\": [")?;
805 {
806 for (i, required) in required.iter().enumerate() {
807 if i > 0 {
808 writer.write_str(", ")?;
809 }
810 write!(writer, "\"{}\"", required)?;
811 }
812 }
813 writer.write_str("]")?;
814 }
815 writer.write_str(",\n\"additionalProperties\": false")?;
816 }
817 f.write_str("\n}")
818 }
819}
820
821impl Display for JsonObjectSchema {
822 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
823 self.display_with_description(f, None)
824 }
825}
826
827#[test]
828fn test_object_schema() {
829 let schema = JsonObjectSchema {
830 title: Some("Person".to_string()),
831 description: Some("A person"),
832 properties: vec![
833 JsonPropertySchema {
834 name: "name".to_string(),
835 description: None,
836 required: true,
837 ty: SchemaType::String(StringSchema {
838 length: Some(1..=10),
839 pattern: None,
840 }),
841 },
842 JsonPropertySchema {
843 name: "age".to_string(),
844 description: None,
845 required: true,
846 ty: SchemaType::Number(NumberSchema {
847 range: Some(0.0..=100.0),
848 }),
849 },
850 JsonPropertySchema {
851 name: "height".to_string(),
852 description: None,
853 required: false,
854 ty: SchemaType::Number(NumberSchema {
855 range: Some(0.0..=500.0),
856 }),
857 },
858 ],
859 };
860
861 assert_eq!(schema.to_string(), "{\n\t\"title\": \"Person\",\n\t\"description\": \"A person\",\n\t\"type\": \"object\",\n\t\"properties\": {\n\t\t\"name\": {\n\t\t\t\"type\": \"string\",\n\t\t\t\"minLength\": 1,\n\t\t\t\"maxLength\": 10\n\t\t},\n\t\t\"age\": {\n\t\t\t\"type\": \"number\",\n\t\t\t\"minimum\": 0,\n\t\t\t\"maximum\": 100\n\t\t},\n\t\t\"height\": {\n\t\t\t\"type\": \"number\",\n\t\t\t\"minimum\": 0,\n\t\t\t\"maximum\": 500\n\t\t}\n\t},\n\t\"required\": [\"name\", \"age\"],\n\t\"additionalProperties\": false\n}");
862}
863
864#[derive(Debug, Clone)]
866pub struct JsonPropertySchema {
867 name: String,
868 description: Option<&'static str>,
869 required: bool,
870 ty: SchemaType,
871}
872
873impl JsonPropertySchema {
874 pub fn new(name: impl ToString, ty: SchemaType) -> Self {
876 Self {
877 name: name.to_string(),
878 description: None,
879 required: false,
880 ty,
881 }
882 }
883
884 pub fn with_description(mut self, description: impl Into<Option<&'static str>>) -> Self {
886 self.description = description.into();
887 self
888 }
889
890 pub fn with_required(mut self, required: bool) -> Self {
892 self.required = required;
893 self
894 }
895}
896
897impl Display for JsonPropertySchema {
898 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
899 f.write_fmt(format_args!("\"{}\": ", self.name))?;
900 self.ty.display_with_description(f, self.description)
901 }
902}
903
904pub trait Schema {
906 fn schema() -> SchemaType;
908}
909
910impl<T: Schema> Schema for Option<T> {
911 fn schema() -> SchemaType {
912 SchemaType::OneOf(OneOfSchema::new([SchemaType::Null, T::schema()]))
913 }
914}
915
916impl<T: Schema> Schema for Box<T> {
917 fn schema() -> SchemaType {
918 T::schema()
919 }
920}