1#![warn(missing_docs)]
8
9use serde::{Deserialize, Serialize};
10
11pub trait ToSchema {
15 fn schema() -> Schema;
17}
18
19#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
21pub enum SchemaType {
22 String,
24 Integer,
26 Number,
28 Boolean,
30 Array,
32 Object,
34 Null,
36}
37
38impl Default for SchemaType {
39 fn default() -> Self {
40 Self::Object
41 }
42}
43
44#[derive(Debug, Clone, Serialize, Deserialize)]
46pub struct Schema {
47 pub title: Option<String>,
49 pub description: Option<String>,
51 #[serde(rename = "type")]
53 pub schema_type: SchemaType,
54 pub properties: Option<std::collections::BTreeMap<String, Schema>>,
56 pub required: Option<Vec<String>>,
58 pub items: Option<Box<Schema>>,
60 #[serde(rename = "enum")]
62 pub enum_values: Option<Vec<serde_json::Value>>,
63 pub default: Option<serde_json::Value>,
65 pub example: Option<serde_json::Value>,
67 #[serde(default)]
69 pub nullable: bool,
70 #[serde(default)]
72 pub read_only: bool,
73 #[serde(default)]
75 pub write_only: bool,
76 pub minimum: Option<f64>,
78 pub maximum: Option<f64>,
80 pub min_length: Option<usize>,
82 pub max_length: Option<usize>,
84 pub pattern: Option<String>,
86 pub format: Option<String>,
88}
89
90impl Default for Schema {
91 fn default() -> Self {
92 Self {
93 title: None,
94 description: None,
95 schema_type: SchemaType::Object,
96 properties: None,
97 required: None,
98 items: None,
99 enum_values: None,
100 default: None,
101 example: None,
102 nullable: false,
103 read_only: false,
104 write_only: false,
105 minimum: None,
106 maximum: None,
107 min_length: None,
108 max_length: None,
109 pattern: None,
110 format: None,
111 }
112 }
113}
114
115impl Schema {
116 pub fn string() -> Self {
118 Self { schema_type: SchemaType::String, ..Default::default() }
119 }
120
121 pub fn integer() -> Self {
123 Self { schema_type: SchemaType::Integer, ..Default::default() }
124 }
125
126 pub fn number() -> Self {
128 Self { schema_type: SchemaType::Number, ..Default::default() }
129 }
130
131 pub fn boolean() -> Self {
133 Self { schema_type: SchemaType::Boolean, ..Default::default() }
134 }
135
136 pub fn array(items: Schema) -> Self {
138 Self { schema_type: SchemaType::Array, items: Some(Box::new(items)), ..Default::default() }
139 }
140
141 pub fn object() -> Self {
143 Self { schema_type: SchemaType::Object, properties: Some(std::collections::BTreeMap::new()), ..Default::default() }
144 }
145
146 pub fn title(mut self, title: impl Into<String>) -> Self {
148 self.title = Some(title.into());
149 self
150 }
151
152 pub fn description(mut self, desc: impl Into<String>) -> Self {
154 self.description = Some(desc.into());
155 self
156 }
157
158 pub fn property(mut self, name: impl Into<String>, schema: Schema) -> Self {
160 let properties = self.properties.get_or_insert_with(std::collections::BTreeMap::new);
161 properties.insert(name.into(), schema);
162 self
163 }
164
165 pub fn required(mut self, fields: Vec<&str>) -> Self {
167 self.required = Some(fields.into_iter().map(String::from).collect());
168 self
169 }
170
171 pub fn enum_values(mut self, values: Vec<serde_json::Value>) -> Self {
173 self.enum_values = Some(values);
174 self
175 }
176
177 pub fn with_default(mut self, value: serde_json::Value) -> Self {
179 self.default = Some(value);
180 self
181 }
182
183 pub fn example(mut self, value: serde_json::Value) -> Self {
185 self.example = Some(value);
186 self
187 }
188
189 pub fn format(mut self, format: impl Into<String>) -> Self {
191 self.format = Some(format.into());
192 self
193 }
194
195 pub fn min_length(mut self, len: usize) -> Self {
197 self.min_length = Some(len);
198 self
199 }
200
201 pub fn max_length(mut self, len: usize) -> Self {
203 self.max_length = Some(len);
204 self
205 }
206
207 pub fn minimum(mut self, min: f64) -> Self {
209 self.minimum = Some(min);
210 self
211 }
212
213 pub fn maximum(mut self, max: f64) -> Self {
215 self.maximum = Some(max);
216 self
217 }
218
219 pub fn nullable(mut self, nullable: bool) -> Self {
221 self.nullable = nullable;
222 self
223 }
224
225 pub fn read_only(mut self, read_only: bool) -> Self {
227 self.read_only = read_only;
228 self
229 }
230
231 pub fn write_only(mut self, write_only: bool) -> Self {
233 self.write_only = write_only;
234 self
235 }
236
237 pub fn to_json_schema(&self) -> serde_json::Value {
239 serde_json::to_value(self).unwrap_or(serde_json::Value::Null)
240 }
241}
242
243pub struct SchemaBuilder {
245 schema: Schema,
246}
247
248impl SchemaBuilder {
249 pub fn new() -> Self {
251 Self { schema: <Schema as Default>::default() }
252 }
253
254 pub fn string() -> Self {
256 Self { schema: Schema::string() }
257 }
258
259 pub fn integer() -> Self {
261 Self { schema: Schema::integer() }
262 }
263
264 pub fn number() -> Self {
266 Self { schema: Schema::number() }
267 }
268
269 pub fn boolean() -> Self {
271 Self { schema: Schema::boolean() }
272 }
273
274 pub fn array(items: Schema) -> Self {
276 Self { schema: Schema::array(items) }
277 }
278
279 pub fn object() -> Self {
281 Self { schema: Schema::object() }
282 }
283
284 pub fn title(mut self, title: impl Into<String>) -> Self {
286 self.schema.title = Some(title.into());
287 self
288 }
289
290 pub fn description(mut self, desc: impl Into<String>) -> Self {
292 self.schema.description = Some(desc.into());
293 self
294 }
295
296 pub fn property(mut self, name: impl Into<String>, schema: Schema) -> Self {
298 let properties = self.schema.properties.get_or_insert_with(std::collections::BTreeMap::new);
299 properties.insert(name.into(), schema);
300 self
301 }
302
303 pub fn required(mut self, fields: Vec<&str>) -> Self {
305 self.schema.required = Some(fields.into_iter().map(String::from).collect());
306 self
307 }
308
309 pub fn build(self) -> Schema {
311 self.schema
312 }
313}
314
315impl Default for SchemaBuilder {
316 fn default() -> Self {
317 Self::new()
318 }
319}
320
321#[derive(Debug, Clone, Serialize, Deserialize)]
323pub struct OpenApiInfo {
324 pub title: String,
326 pub version: String,
328 pub description: Option<String>,
330 pub terms_of_service: Option<String>,
332 pub contact: Option<Contact>,
334 pub license: Option<License>,
336}
337
338impl Default for OpenApiInfo {
339 fn default() -> Self {
340 Self {
341 title: "API".to_string(),
342 version: "1.0.0".to_string(),
343 description: None,
344 terms_of_service: None,
345 contact: None,
346 license: None,
347 }
348 }
349}
350
351#[derive(Debug, Clone, Serialize, Deserialize)]
353pub struct Contact {
354 pub name: Option<String>,
356 pub email: Option<String>,
358 pub url: Option<String>,
360}
361
362#[derive(Debug, Clone, Serialize, Deserialize)]
364pub struct License {
365 pub name: String,
367 pub url: Option<String>,
369}
370
371#[derive(Debug, Clone, Serialize, Deserialize)]
373pub struct OpenApiDoc {
374 pub openapi: String,
376 pub info: OpenApiInfo,
378 pub paths: std::collections::BTreeMap<String, PathItem>,
380 pub components: Option<Components>,
382 pub servers: Option<Vec<Server>>,
384}
385
386impl Default for OpenApiDoc {
387 fn default() -> Self {
388 Self {
389 openapi: "3.1.0".to_string(),
390 info: OpenApiInfo::default(),
391 paths: std::collections::BTreeMap::new(),
392 components: None,
393 servers: None,
394 }
395 }
396}
397
398impl OpenApiDoc {
399 pub fn new(title: impl Into<String>, version: impl Into<String>) -> Self {
401 Self { info: OpenApiInfo { title: title.into(), version: version.into(), ..Default::default() }, ..Default::default() }
402 }
403
404 pub fn description(mut self, desc: impl Into<String>) -> Self {
406 self.info.description = Some(desc.into());
407 self
408 }
409
410 pub fn path(mut self, path: impl Into<String>, item: PathItem) -> Self {
412 self.paths.insert(path.into(), item);
413 self
414 }
415
416 pub fn schema(mut self, name: impl Into<String>, schema: Schema) -> Self {
418 let components = self.components.get_or_insert_with(Components::default);
419 components.schemas.insert(name.into(), schema);
420 self
421 }
422
423 pub fn server(mut self, url: impl Into<String>, description: Option<String>) -> Self {
425 let servers = self.servers.get_or_insert_with(Vec::new);
426 servers.push(Server { url: url.into(), description });
427 self
428 }
429
430 pub fn to_json(&self) -> serde_json::Value {
432 serde_json::to_value(self).unwrap_or(serde_json::Value::Null)
433 }
434
435 #[cfg(feature = "yaml")]
437 pub fn to_yaml(&self) -> Result<String, serde_yaml::Error> {
438 serde_yaml::to_string(self)
439 }
440}
441
442#[derive(Debug, Clone, Serialize, Deserialize)]
444pub struct PathItem {
445 #[serde(skip_serializing_if = "Option::is_none")]
447 pub get: Option<Operation>,
448 #[serde(skip_serializing_if = "Option::is_none")]
450 pub post: Option<Operation>,
451 #[serde(skip_serializing_if = "Option::is_none")]
453 pub put: Option<Operation>,
454 #[serde(skip_serializing_if = "Option::is_none")]
456 pub delete: Option<Operation>,
457 #[serde(skip_serializing_if = "Option::is_none")]
459 pub patch: Option<Operation>,
460 #[serde(skip_serializing_if = "Option::is_none")]
462 pub description: Option<String>,
463 #[serde(skip_serializing_if = "Option::is_none")]
465 pub summary: Option<String>,
466}
467
468impl Default for PathItem {
469 fn default() -> Self {
470 Self { get: None, post: None, put: None, delete: None, patch: None, description: None, summary: None }
471 }
472}
473
474impl PathItem {
475 pub fn new() -> Self {
477 Self::default()
478 }
479
480 pub fn get(mut self, op: Operation) -> Self {
482 self.get = Some(op);
483 self
484 }
485
486 pub fn post(mut self, op: Operation) -> Self {
488 self.post = Some(op);
489 self
490 }
491
492 pub fn put(mut self, op: Operation) -> Self {
494 self.put = Some(op);
495 self
496 }
497
498 pub fn delete(mut self, op: Operation) -> Self {
500 self.delete = Some(op);
501 self
502 }
503
504 pub fn patch(mut self, op: Operation) -> Self {
506 self.patch = Some(op);
507 self
508 }
509}
510
511#[derive(Debug, Clone, Serialize, Deserialize)]
513pub struct Operation {
514 #[serde(skip_serializing_if = "Option::is_none")]
516 pub tags: Option<Vec<String>>,
517 #[serde(skip_serializing_if = "Option::is_none")]
519 pub summary: Option<String>,
520 #[serde(skip_serializing_if = "Option::is_none")]
522 pub description: Option<String>,
523 #[serde(skip_serializing_if = "Option::is_none")]
525 pub operation_id: Option<String>,
526 #[serde(skip_serializing_if = "Option::is_none")]
528 pub parameters: Option<Vec<Parameter>>,
529 #[serde(skip_serializing_if = "Option::is_none")]
531 pub request_body: Option<RequestBody>,
532 pub responses: std::collections::BTreeMap<String, Response>,
534}
535
536impl Operation {
537 pub fn new() -> Self {
539 Self {
540 tags: None,
541 summary: None,
542 description: None,
543 operation_id: None,
544 parameters: None,
545 request_body: None,
546 responses: std::collections::BTreeMap::new(),
547 }
548 }
549
550 pub fn summary(mut self, summary: impl Into<String>) -> Self {
552 self.summary = Some(summary.into());
553 self
554 }
555
556 pub fn description(mut self, desc: impl Into<String>) -> Self {
558 self.description = Some(desc.into());
559 self
560 }
561
562 pub fn operation_id(mut self, id: impl Into<String>) -> Self {
564 self.operation_id = Some(id.into());
565 self
566 }
567
568 pub fn tag(mut self, tag: impl Into<String>) -> Self {
570 let tags = self.tags.get_or_insert_with(Vec::new);
571 tags.push(tag.into());
572 self
573 }
574
575 pub fn parameter(mut self, param: Parameter) -> Self {
577 let params = self.parameters.get_or_insert_with(Vec::new);
578 params.push(param);
579 self
580 }
581
582 pub fn request_body(mut self, body: RequestBody) -> Self {
584 self.request_body = Some(body);
585 self
586 }
587
588 pub fn response(mut self, status: impl Into<String>, resp: Response) -> Self {
590 self.responses.insert(status.into(), resp);
591 self
592 }
593}
594
595impl Default for Operation {
596 fn default() -> Self {
597 Self::new()
598 }
599}
600
601#[derive(Debug, Clone, Serialize, Deserialize)]
603pub struct Parameter {
604 pub name: String,
606 #[serde(rename = "in")]
608 pub location: ParameterLocation,
609 #[serde(skip_serializing_if = "Option::is_none")]
611 pub description: Option<String>,
612 #[serde(default)]
614 pub required: bool,
615 #[serde(skip_serializing_if = "Option::is_none")]
617 pub schema: Option<Schema>,
618}
619
620#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
622#[serde(rename_all = "lowercase")]
623pub enum ParameterLocation {
624 Path,
626 Query,
628 Header,
630 Cookie,
632}
633
634impl Parameter {
635 pub fn path(name: impl Into<String>) -> Self {
637 Self { name: name.into(), location: ParameterLocation::Path, description: None, required: true, schema: None }
638 }
639
640 pub fn query(name: impl Into<String>) -> Self {
642 Self { name: name.into(), location: ParameterLocation::Query, description: None, required: false, schema: None }
643 }
644
645 pub fn header(name: impl Into<String>) -> Self {
647 Self { name: name.into(), location: ParameterLocation::Header, description: None, required: false, schema: None }
648 }
649
650 pub fn description(mut self, desc: impl Into<String>) -> Self {
652 self.description = Some(desc.into());
653 self
654 }
655
656 pub fn required(mut self, required: bool) -> Self {
658 self.required = required;
659 self
660 }
661
662 pub fn schema(mut self, schema: Schema) -> Self {
664 self.schema = Some(schema);
665 self
666 }
667}
668
669#[derive(Debug, Clone, Serialize, Deserialize)]
671pub struct RequestBody {
672 #[serde(skip_serializing_if = "Option::is_none")]
674 pub description: Option<String>,
675 pub content: std::collections::BTreeMap<String, MediaType>,
677 #[serde(default)]
679 pub required: bool,
680}
681
682impl RequestBody {
683 pub fn json(schema: Schema) -> Self {
685 let mut content = std::collections::BTreeMap::new();
686 content.insert("application/json".to_string(), MediaType { schema: Some(schema), example: None });
687 Self { description: None, content, required: true }
688 }
689
690 pub fn description(mut self, desc: impl Into<String>) -> Self {
692 self.description = Some(desc.into());
693 self
694 }
695}
696
697#[derive(Debug, Clone, Serialize, Deserialize)]
699pub struct MediaType {
700 #[serde(skip_serializing_if = "Option::is_none")]
702 pub schema: Option<Schema>,
703 #[serde(skip_serializing_if = "Option::is_none")]
705 pub example: Option<serde_json::Value>,
706}
707
708#[derive(Debug, Clone, Serialize, Deserialize)]
710pub struct Response {
711 pub description: String,
713 #[serde(skip_serializing_if = "Option::is_none")]
715 pub content: Option<std::collections::BTreeMap<String, MediaType>>,
716}
717
718impl Response {
719 pub fn new(description: impl Into<String>) -> Self {
721 Self { description: description.into(), content: None }
722 }
723
724 pub fn json(mut self, schema: Schema) -> Self {
726 let content = self.content.get_or_insert_with(std::collections::BTreeMap::new);
727 content.insert("application/json".to_string(), MediaType { schema: Some(schema), example: None });
728 self
729 }
730}
731
732#[derive(Debug, Clone, Serialize, Deserialize, Default)]
734pub struct Components {
735 #[serde(skip_serializing_if = "std::collections::BTreeMap::is_empty")]
737 pub schemas: std::collections::BTreeMap<String, Schema>,
738}
739
740#[derive(Debug, Clone, Serialize, Deserialize)]
742pub struct Server {
743 pub url: String,
745 #[serde(skip_serializing_if = "Option::is_none")]
747 pub description: Option<String>,
748}
749
750impl ToSchema for String {
751 fn schema() -> Schema {
752 Schema::string()
753 }
754}
755
756impl ToSchema for i64 {
757 fn schema() -> Schema {
758 Schema::integer()
759 }
760}
761
762impl ToSchema for i32 {
763 fn schema() -> Schema {
764 Schema::integer()
765 }
766}
767
768impl ToSchema for i16 {
769 fn schema() -> Schema {
770 Schema::integer()
771 }
772}
773
774impl ToSchema for i8 {
775 fn schema() -> Schema {
776 Schema::integer()
777 }
778}
779
780impl ToSchema for u64 {
781 fn schema() -> Schema {
782 Schema::integer()
783 }
784}
785
786impl ToSchema for u32 {
787 fn schema() -> Schema {
788 Schema::integer()
789 }
790}
791
792impl ToSchema for u16 {
793 fn schema() -> Schema {
794 Schema::integer()
795 }
796}
797
798impl ToSchema for u8 {
799 fn schema() -> Schema {
800 Schema::integer()
801 }
802}
803
804impl ToSchema for f64 {
805 fn schema() -> Schema {
806 Schema::number()
807 }
808}
809
810impl ToSchema for f32 {
811 fn schema() -> Schema {
812 Schema::number()
813 }
814}
815
816impl ToSchema for bool {
817 fn schema() -> Schema {
818 Schema::boolean()
819 }
820}
821
822impl<T: ToSchema> ToSchema for Vec<T> {
823 fn schema() -> Schema {
824 Schema::array(T::schema())
825 }
826}
827
828impl<T: ToSchema> ToSchema for Option<T> {
829 fn schema() -> Schema {
830 T::schema().nullable(true)
831 }
832}
833
834impl ToSchema for serde_json::Value {
835 fn schema() -> Schema {
836 <Schema as Default>::default()
837 }
838}