1#![allow(clippy::derive_partial_eq_without_eq)]
7
8use crate::components::Components;
9use crate::info::Info;
10use crate::paths::{ExternalDocumentation, Paths};
11use crate::security::SecurityRequirement;
12use crate::server::Server;
13use crate::tag::Tag;
14use indexmap::IndexMap;
15use serde::Serialize;
16use serde_json::Value;
17
18pub mod components;
19pub mod info;
20pub mod paths;
21pub mod reference_or;
22pub mod security;
23pub mod server;
24pub mod tag;
25
26pub use schemars::schema::*;
27
28#[derive(Serialize, Clone, Debug)]
29#[cfg_attr(any(test, feature = "deserialize"), derive(serde::Deserialize, PartialEq))]
30pub enum OpenApiVersion {
31 #[serde(rename = "3.0.3")]
32 OAS3_0,
33}
34
35impl Default for OpenApiVersion {
36 fn default() -> Self {
37 Self::OAS3_0
38 }
39}
40
41#[derive(Serialize, Clone, Debug, Default)]
43#[cfg_attr(any(test, feature = "deserialize"), derive(serde::Deserialize, PartialEq))]
44#[serde(rename_all = "camelCase")]
45pub struct OpenApi {
46 pub openapi: OpenApiVersion,
48 pub info: Info,
50 pub servers: Vec<Server>,
52 pub paths: Paths,
54 #[serde(skip_serializing_if = "Option::is_none")]
56 pub components: Option<Components>,
57 #[serde(skip_serializing_if = "Vec::is_empty", default)]
59 pub security: Vec<SecurityRequirement>,
60 #[serde(skip_serializing_if = "Vec::is_empty", default)]
62 pub tags: Vec<Tag>,
63 #[serde(skip_serializing_if = "Option::is_none")]
65 pub external_docs: Option<ExternalDocumentation>,
66 #[serde(flatten, skip_serializing_if = "IndexMap::is_empty", skip_deserializing)]
68 pub extensions: IndexMap<String, Value>,
69}
70
71#[cfg(test)]
72mod test {
73 #![allow(clippy::expect_used)]
74
75 use crate::info::Info;
76 use crate::paths::{Operation, OperationType, PathItem, Paths, Response, Responses};
77 use crate::reference_or::ReferenceOr;
78 use crate::server::Server;
79 use crate::tag::Tag;
80 use crate::{OpenApi, OpenApiVersion};
81 use indexmap::IndexMap;
82 use std::collections::BTreeMap;
83
84 #[test]
85 fn empty_openapi_properly_generated() {
86 let oas = OpenApi {
87 openapi: OpenApiVersion::OAS3_0,
88 info: Info {
89 title: "Test".to_string(),
90 description: Some("Description".to_string()),
91 version: "1.0.0".to_string(),
92 ..Default::default()
93 },
94 paths: Paths::default(),
95 ..Default::default()
96 };
97
98 let oas_json = serde_json::to_string_pretty(&oas).expect("Error generating json for oas");
99 assert_eq!(oas_json, include_str!("../test-assets/empty-openapi.json"));
100 }
101
102 #[test]
103 fn openapi_properly_generated() {
104 let oas = OpenApi {
105 openapi: OpenApiVersion::OAS3_0,
106 info: Info {
107 title: "Test".to_string(),
108 description: Some("Description".to_string()),
109 version: "1.0.0".to_string(),
110 ..Default::default()
111 },
112 servers: vec![Server {
113 url: "https://google.com".to_string(),
114 description: Some("A big search server".to_string()),
115 ..Default::default()
116 }],
117 paths: Paths {
118 paths: IndexMap::from_iter(vec![(
119 "/search".to_string(),
120 PathItem {
121 operations: IndexMap::from_iter(vec![(
122 OperationType::Get,
123 Operation {
124 tags: vec!["Search".to_string()],
125 summary: Some("I don't know what this do".to_string()),
126 operation_id: Some("get_search".to_string()),
127 responses: Responses {
128 responses: BTreeMap::from_iter(vec![(
129 "200".to_string(),
130 ReferenceOr::Object(Response {
131 description: "A search thingy".to_string(),
132 ..Default::default()
133 }),
134 )]),
135 ..Default::default()
136 },
137 ..Default::default()
138 },
139 )]),
140 ..Default::default()
141 },
142 )]),
143 ..Default::default()
144 },
145 tags: vec![Tag {
146 name: "Search".to_string(),
147 ..Default::default()
148 }],
149 ..Default::default()
150 };
151
152 let oas_json = serde_json::to_string_pretty(&oas).expect("Error generating json for oas");
153 assert_eq!(oas_json, include_str!("../test-assets/openapi.json"));
154 }
155}