1use serde::{de::Error, de::Visitor, Deserialize, Deserializer, Serialize, Serializer};
4use std::fmt::Formatter;
5
6pub use self::{
7 content::{Content, ContentBuilder},
8 external_docs::ExternalDocs,
9 header::{Header, HeaderBuilder},
10 info::{Contact, ContactBuilder, Info, InfoBuilder, License, LicenseBuilder},
11 path::{PathItem, PathItemType, Paths, PathsBuilder},
12 response::{Response, ResponseBuilder, Responses, ResponsesBuilder},
13 schema::{
14 AllOf, AllOfBuilder, Array, ArrayBuilder, Components, ComponentsBuilder, Discriminator,
15 KnownFormat, Object, ObjectBuilder, OneOf, OneOfBuilder, Ref, Schema, SchemaFormat,
16 SchemaType, ToArray,
17 },
18 security::SecurityRequirement,
19 server::{Server, ServerBuilder, ServerVariable, ServerVariableBuilder},
20 tag::Tag,
21};
22
23pub mod content;
24pub mod encoding;
25pub mod example;
26pub mod external_docs;
27pub mod header;
28pub mod info;
29pub mod path;
30pub mod request_body;
31pub mod response;
32pub mod schema;
33pub mod security;
34pub mod server;
35pub mod tag;
36pub mod xml;
37
38builder! {
39 OpenApiBuilder;
53
54 #[non_exhaustive]
63 #[derive(Serialize, Deserialize, Default, Clone, PartialEq)]
64 #[cfg_attr(feature = "debug", derive(Debug))]
65 #[serde(rename_all = "camelCase")]
66 pub struct OpenApi {
67 pub openapi: OpenApiVersion,
69
70 pub info: Info,
74
75 #[serde(skip_serializing_if = "Option::is_none")]
81 pub servers: Option<Vec<Server>>,
82
83 #[serde(flatten)]
87 pub paths: Paths,
88
89 #[serde(skip_serializing_if = "Option::is_none")]
95 pub components: Option<Components>,
96
97 #[serde(skip_serializing_if = "Option::is_none")]
103 pub security: Option<Vec<SecurityRequirement>>,
104
105 #[serde(skip_serializing_if = "Option::is_none")]
109 pub tags: Option<Vec<Tag>>,
110
111 #[serde(skip_serializing_if = "Option::is_none")]
115 pub external_docs: Option<ExternalDocs>,
116 }
117}
118
119impl OpenApi {
120 pub fn new<P: Into<Paths>>(info: Info, paths: P) -> Self {
133 Self {
134 info,
135 paths: paths.into(),
136 ..Default::default()
137 }
138 }
139
140 pub fn to_json(&self) -> Result<String, serde_json::Error> {
142 serde_json::to_string(self)
143 }
144
145 pub fn to_pretty_json(&self) -> Result<String, serde_json::Error> {
147 serde_json::to_string_pretty(self)
148 }
149
150 #[cfg(feature = "yaml")]
152 #[cfg_attr(doc_cfg, doc(cfg(feature = "yaml")))]
153 pub fn to_yaml(&self) -> Result<String, serde_yaml::Error> {
154 serde_yaml::to_string(self)
155 }
156
157 pub fn merge(&mut self, mut other: OpenApi) {
172 if let Some(other_servers) = &mut other.servers {
173 let servers = self.servers.get_or_insert(Vec::new());
174 other_servers.retain(|server| !servers.contains(server));
175 servers.append(other_servers);
176 }
177
178 if !other.paths.paths.is_empty() {
179 other
180 .paths
181 .paths
182 .retain(|path, _| self.paths.get_path_item(path).is_none());
183 self.paths.paths.extend(&mut other.paths.paths.into_iter());
184 };
185
186 if let Some(other_components) = &mut other.components {
187 let components = self.components.get_or_insert(Components::default());
188
189 other_components
190 .schemas
191 .retain(|name, _| !components.schemas.contains_key(name));
192 components.schemas.append(&mut other_components.schemas);
193
194 other_components
195 .responses
196 .retain(|name, _| !components.responses.contains_key(name));
197 components.responses.append(&mut other_components.responses);
198
199 other_components
200 .security_schemes
201 .retain(|name, _| !components.security_schemes.contains_key(name));
202 components
203 .security_schemes
204 .append(&mut other_components.security_schemes);
205 }
206
207 if let Some(other_security) = &mut other.security {
208 let security = self.security.get_or_insert(Vec::new());
209 other_security.retain(|requirement| !security.contains(requirement));
210 security.append(other_security);
211 }
212
213 if let Some(other_tags) = &mut other.tags {
214 let tags = self.tags.get_or_insert(Vec::new());
215 other_tags.retain(|tag| !tags.contains(tag));
216 tags.append(other_tags);
217 }
218 }
219}
220
221impl OpenApiBuilder {
222 pub fn info<I: Into<Info>>(mut self, info: I) -> Self {
224 set_value!(self info info.into())
225 }
226
227 pub fn servers<I: IntoIterator<Item = Server>>(mut self, servers: Option<I>) -> Self {
229 set_value!(self servers servers.map(|servers| servers.into_iter().collect()))
230 }
231
232 pub fn paths<P: Into<Paths>>(mut self, paths: P) -> Self {
234 set_value!(self paths paths.into())
235 }
236
237 pub fn components(mut self, components: Option<Components>) -> Self {
239 set_value!(self components components)
240 }
241
242 pub fn security<I: IntoIterator<Item = SecurityRequirement>>(
244 mut self,
245 security: Option<I>,
246 ) -> Self {
247 set_value!(self security security.map(|security| security.into_iter().collect()))
248 }
249
250 pub fn tags<I: IntoIterator<Item = Tag>>(mut self, tags: Option<I>) -> Self {
252 set_value!(self tags tags.map(|tags| tags.into_iter().collect()))
253 }
254
255 pub fn external_docs(mut self, external_docs: Option<ExternalDocs>) -> Self {
257 set_value!(self external_docs external_docs)
258 }
259}
260
261#[derive(Serialize, Clone, PartialEq, Eq)]
265#[cfg_attr(feature = "debug", derive(Debug))]
266pub enum OpenApiVersion {
267 #[serde(rename = "3.0.3")]
269 Version3,
270}
271
272impl Default for OpenApiVersion {
273 fn default() -> Self {
274 Self::Version3
275 }
276}
277
278impl<'de> Deserialize<'de> for OpenApiVersion {
279 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
280 where
281 D: Deserializer<'de>,
282 {
283 struct VersionVisitor;
284
285 impl<'v> Visitor<'v> for VersionVisitor {
286 type Value = OpenApiVersion;
287
288 fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
289 formatter.write_str("a version string in 3, 3.0, or 3.0.x format")
290 }
291
292 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
293 where
294 E: Error,
295 {
296 self.visit_string(v.to_string())
297 }
298
299 fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
300 where
301 E: Error,
302 {
303 let parts = v.split('.').collect::<Vec<_>>();
304 if parts.len() > 3 || parts.is_empty() {
305 return Err(E::custom(format!(
306 "Invalid format of OpenAPI version: {}",
307 v,
308 )));
309 }
310
311 Ok(match (parts[0], parts.get(1).copied().unwrap_or("0")) {
312 ("3", "0") => OpenApiVersion::Version3,
313 _ => return Err(E::custom(format!("Unsupported version: {}", &v))),
314 })
315 }
316 }
317
318 deserializer.deserialize_string(VersionVisitor)
319 }
320}
321
322#[derive(PartialEq, Eq, Clone)]
326#[cfg_attr(feature = "debug", derive(Debug))]
327pub enum Deprecated {
328 True,
329 False,
330}
331
332impl Serialize for Deprecated {
333 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
334 where
335 S: Serializer,
336 {
337 serializer.serialize_bool(matches!(self, Self::True))
338 }
339}
340
341impl<'de> Deserialize<'de> for Deprecated {
342 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
343 where
344 D: serde::Deserializer<'de>,
345 {
346 struct BoolVisitor;
347 impl<'de> Visitor<'de> for BoolVisitor {
348 type Value = Deprecated;
349
350 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
351 formatter.write_str("a bool true or false")
352 }
353
354 fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E>
355 where
356 E: serde::de::Error,
357 {
358 match v {
359 true => Ok(Deprecated::True),
360 false => Ok(Deprecated::False),
361 }
362 }
363 }
364 deserializer.deserialize_bool(BoolVisitor)
365 }
366}
367
368impl Default for Deprecated {
369 fn default() -> Self {
370 Deprecated::False
371 }
372}
373
374#[derive(PartialEq, Eq, Clone)]
378#[cfg_attr(feature = "debug", derive(Debug))]
379pub enum Required {
380 True,
381 False,
382}
383
384impl Serialize for Required {
385 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
386 where
387 S: Serializer,
388 {
389 serializer.serialize_bool(matches!(self, Self::True))
390 }
391}
392
393impl<'de> Deserialize<'de> for Required {
394 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
395 where
396 D: serde::Deserializer<'de>,
397 {
398 struct BoolVisitor;
399 impl<'de> Visitor<'de> for BoolVisitor {
400 type Value = Required;
401
402 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
403 formatter.write_str("a bool true or false")
404 }
405
406 fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E>
407 where
408 E: serde::de::Error,
409 {
410 match v {
411 true => Ok(Required::True),
412 false => Ok(Required::False),
413 }
414 }
415 }
416 deserializer.deserialize_bool(BoolVisitor)
417 }
418}
419
420impl Default for Required {
421 fn default() -> Self {
422 Required::False
423 }
424}
425
426#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
431#[cfg_attr(feature = "debug", derive(Debug))]
432#[serde(untagged)]
433pub enum RefOr<T> {
434 Ref(Ref),
435 T(T),
436}
437
438macro_rules! build_fn {
439 ( $vis:vis $name:ident $( $field:ident ),+ ) => {
440 #[doc = concat!("Constructs a new [`", stringify!($name),"`] taking all fields values from this object.")]
441 $vis fn build(self) -> $name {
442 $name {
443 $(
444 $field: self.$field,
445 )*
446 }
447 }
448 };
449}
450pub(crate) use build_fn;
451
452macro_rules! set_value {
453 ( $self:ident $field:ident $value:expr ) => {{
454 $self.$field = $value;
455
456 $self
457 }};
458}
459pub(crate) use set_value;
460
461macro_rules! new {
462 ( $vis:vis $name:ident ) => {
463 #[doc = concat!("Constructs a new [`", stringify!($name),"`].")]
464 $vis fn new() -> $name {
465 $name {
466 ..Default::default()
467 }
468 }
469 };
470}
471pub(crate) use new;
472
473macro_rules! from {
474 ( $name:ident $to:ident $( $field:ident ),+ ) => {
475 impl From<$name> for $to {
476 fn from(value: $name) -> Self {
477 Self {
478 $( $field: value.$field, )*
479 }
480 }
481 }
482
483 impl From<$to> for $name {
484 fn from(value: $to) -> Self {
485 value.build()
486 }
487 }
488 };
489}
490pub(crate) use from;
491
492macro_rules! builder {
493 ( $( #[$builder_meta:meta] )* $builder_name:ident; $(#[$meta:meta])* $vis:vis $key:ident $name:ident $( $tt:tt )* ) => {
494 builder!( @type_impl $( #[$meta] )* $vis $key $name $( $tt )* );
495 builder!( @builder_impl $( #[$builder_meta] )* $builder_name $( #[$meta] )* $vis $key $name $( $tt )* );
496 };
497
498 ( @type_impl $( #[$meta:meta] )* $vis:vis $key:ident $name:ident
499 { $( $( #[$field_meta:meta] )* $field_vis:vis $field:ident: $field_ty:ty, )* }
500 ) => {
501
502 $( #[$meta] )*
503 $vis $key $name {
504 $( $( #[$field_meta] )* $field_vis $field: $field_ty, )*
505 }
506 };
507
508 ( @builder_impl $( #[$builder_meta:meta] )* $builder_name:ident $( #[$meta:meta] )* $vis:vis $key:ident $name:ident
509 { $( $( #[$field_meta:meta] )* $field_vis:vis $field:ident: $field_ty:ty, )* }
510 ) => {
511 #[doc = concat!("Builder for [`", stringify!($name),
512 "`] with chainable configuration methods to create a new [`", stringify!($name) , "`].")]
513 $( #[$builder_meta] )*
514 #[cfg_attr(feature = "debug", derive(Debug))]
515 $vis $key $builder_name {
516 $( $field: $field_ty, )*
517 }
518
519 impl Default for $builder_name {
520 fn default() -> Self {
521 let meta_default: $name = $name::default();
522 Self {
523 $( $field: meta_default.$field, )*
524 }
525 }
526 }
527
528 impl $builder_name {
529 crate::openapi::new!($vis $builder_name);
530 crate::openapi::build_fn!($vis $name $( $field ),* );
531 }
532
533 crate::openapi::from!($name $builder_name $( $field ),* );
534 };
535}
536pub(crate) use builder;
537
538#[cfg(test)]
539mod tests {
540 use serde_json::json;
541
542 use crate::openapi::{
543 info::InfoBuilder,
544 path::{OperationBuilder, PathsBuilder},
545 };
546
547 use super::{response::Response, *};
548
549 #[test]
550 fn serialize_deserialize_openapi_version_success() -> Result<(), serde_json::Error> {
551 assert_eq!(serde_json::to_value(&OpenApiVersion::Version3)?, "3.0.3");
552 Ok(())
553 }
554
555 #[test]
556 fn serialize_openapi_json_minimal_success() -> Result<(), serde_json::Error> {
557 let raw_json = include_str!("openapi/testdata/expected_openapi_minimal.json");
558 let openapi = OpenApi::new(
559 InfoBuilder::new()
560 .title("My api")
561 .version("1.0.0")
562 .description(Some("My api description"))
563 .license(Some(
564 LicenseBuilder::new()
565 .name("MIT")
566 .url(Some("http://mit.licence"))
567 .build(),
568 ))
569 .build(),
570 Paths::new(),
571 );
572 let serialized = serde_json::to_string_pretty(&openapi)?;
573
574 assert_eq!(
575 serialized, raw_json,
576 "expected serialized json to match raw: \nserialized: \n{serialized} \nraw: \n{raw_json}"
577 );
578 Ok(())
579 }
580
581 #[test]
582 fn serialize_openapi_json_with_paths_success() -> Result<(), serde_json::Error> {
583 let openapi = OpenApi::new(
584 Info::new("My big api", "1.1.0"),
585 PathsBuilder::new()
586 .path(
587 "/api/v1/users",
588 PathItem::new(
589 PathItemType::Get,
590 OperationBuilder::new().response("200", Response::new("Get users list")),
591 ),
592 )
593 .path(
594 "/api/v1/users",
595 PathItem::new(
596 PathItemType::Post,
597 OperationBuilder::new().response("200", Response::new("Post new user")),
598 ),
599 )
600 .path(
601 "/api/v1/users/{id}",
602 PathItem::new(
603 PathItemType::Get,
604 OperationBuilder::new().response("200", Response::new("Get user by id")),
605 ),
606 ),
607 );
608
609 let serialized = serde_json::to_string_pretty(&openapi)?;
610 let expected = include_str!("./openapi/testdata/expected_openapi_with_paths.json");
611
612 assert_eq!(
613 serialized, expected,
614 "expected serialized json to match raw: \nserialized: \n{serialized} \nraw: \n{expected}"
615 );
616 Ok(())
617 }
618
619 #[test]
620 fn merge_2_openapi_documents() {
621 let mut api_1 = OpenApi::new(
622 Info::new("Api", "v1"),
623 PathsBuilder::new()
624 .path(
625 "/api/v1/user",
626 PathItem::new(
627 PathItemType::Get,
628 OperationBuilder::new().response("200", Response::new("Get user success")),
629 ),
630 )
631 .build(),
632 );
633
634 let api_2 = OpenApiBuilder::new()
635 .info(Info::new("Api", "v2"))
636 .paths(
637 PathsBuilder::new()
638 .path(
639 "/api/v1/user",
640 PathItem::new(
641 PathItemType::Get,
642 OperationBuilder::new()
643 .response("200", Response::new("This will not get added")),
644 ),
645 )
646 .path(
647 "/ap/v2/user",
648 PathItem::new(
649 PathItemType::Get,
650 OperationBuilder::new()
651 .response("200", Response::new("Get user success 2")),
652 ),
653 )
654 .path(
655 "/api/v2/user",
656 PathItem::new(
657 PathItemType::Post,
658 OperationBuilder::new()
659 .response("200", Response::new("Get user success")),
660 ),
661 )
662 .build(),
663 )
664 .components(Some(
665 ComponentsBuilder::new()
666 .schema(
667 "User2",
668 ObjectBuilder::new()
669 .schema_type(SchemaType::Object)
670 .property(
671 "name",
672 ObjectBuilder::new().schema_type(SchemaType::String).build(),
673 ),
674 )
675 .build(),
676 ))
677 .build();
678
679 api_1.merge(api_2);
680 let value = serde_json::to_value(&api_1).unwrap();
681
682 assert_eq!(
683 value,
684 json!(
685 {
686 "openapi": "3.0.3",
687 "info": {
688 "title": "Api",
689 "version": "v1"
690 },
691 "paths": {
692 "/ap/v2/user": {
693 "get": {
694 "responses": {
695 "200": {
696 "description": "Get user success 2"
697 }
698 }
699 }
700 },
701 "/api/v1/user": {
702 "get": {
703 "responses": {
704 "200": {
705 "description": "Get user success"
706 }
707 }
708 }
709 },
710 "/api/v2/user": {
711 "post": {
712 "responses": {
713 "200": {
714 "description": "Get user success"
715 }
716 }
717 }
718 }
719 },
720 "components": {
721 "schemas": {
722 "User2": {
723 "type": "object",
724 "properties": {
725 "name": {
726 "type": "string"
727 }
728 }
729 }
730 }
731 }
732 }
733 )
734 )
735 }
736
737 #[test]
738 fn deserialize_other_versions() {
739 [r#""3.0.3""#, r#""3.0.0""#, r#""3.0""#, r#""3""#]
740 .iter()
741 .for_each(|v| {
742 assert!(matches!(
743 serde_json::from_str::<OpenApiVersion>(v).unwrap(),
744 OpenApiVersion::Version3,
745 ));
746 });
747 }
748}