1use crate::{OpenApiError, Result};
6use serde_json::Value;
7use utoipa::openapi::{InfoBuilder, OpenApi, OpenApiBuilder, PathItem, PathsBuilder};
8
9#[derive(Clone)]
13pub struct OpenApiDoc {
14 openapi: OpenApi,
16}
17
18impl OpenApiDoc {
19 pub fn new(title: &str, version: &str) -> Self {
34 let openapi = OpenApiBuilder::new()
35 .info(InfoBuilder::new().title(title).version(version).build())
36 .build();
37
38 Self { openapi }
39 }
40
41 pub fn from_openapi(openapi: OpenApi) -> Self {
43 Self { openapi }
44 }
45
46 pub fn description<S: Into<String>>(mut self, description: S) -> Self {
48 if let Some(ref mut info) = self.openapi.info.description {
49 *info = description.into();
50 } else {
51 self.openapi.info.description = Some(description.into());
52 }
53 self
54 }
55
56 pub fn add_server(mut self, url: &str, description: Option<&str>) -> Self {
63 use utoipa::openapi::ServerBuilder;
64
65 let mut server_builder = ServerBuilder::new().url(url);
66 if let Some(desc) = description {
67 server_builder = server_builder.description(Some(desc));
68 }
69
70 let server = server_builder.build();
71
72 if self.openapi.servers.is_none() {
73 self.openapi.servers = Some(Vec::new());
74 }
75
76 if let Some(ref mut servers) = self.openapi.servers {
77 servers.push(server);
78 }
79
80 self
81 }
82
83 pub fn add_path(mut self, path: &str, path_item: PathItem) -> Self {
90 let mut paths_builder = PathsBuilder::new();
92 paths_builder = paths_builder.path(path, path_item);
93 self.openapi.paths = paths_builder.build();
94 self
95 }
96
97 pub fn add_paths(mut self, paths: Vec<(String, PathItem)>) -> Self {
103 let mut paths_builder = PathsBuilder::new();
104 for (path, path_item) in paths {
105 paths_builder = paths_builder.path(&path, path_item);
106 }
107 self.openapi.paths = paths_builder.build();
108 self
109 }
110
111 pub fn add_placeholder_schemas(mut self, type_names: &[&str]) -> Self {
113 use utoipa::openapi::ComponentsBuilder;
114 use utoipa::openapi::schema::{ObjectBuilder, Schema};
115 let mut components = self
116 .openapi
117 .components
118 .unwrap_or_else(|| ComponentsBuilder::new().build());
119 for name in type_names {
120 components
121 .schemas
122 .entry((*name).to_string())
123 .or_insert_with(|| {
124 utoipa::openapi::RefOr::T(Schema::Object(ObjectBuilder::new().build()))
125 });
126 }
127 self.openapi.components = Some(components);
128 self
129 }
130
131 pub fn apply_registered_schemas(mut self) -> Self {
133 crate::doc::apply_registered_schemas(&mut self.openapi);
134 self
135 }
136
137 pub fn add_bearer_auth(mut self, scheme_name: &str, description: Option<&str>) -> Self {
139 use utoipa::openapi::ComponentsBuilder;
140 use utoipa::openapi::security::{HttpAuthScheme, HttpBuilder, SecurityScheme};
141
142 let http = HttpBuilder::new()
143 .scheme(HttpAuthScheme::Bearer)
144 .bearer_format("JWT");
145 if let Some(_desc) = description {
146 }
148 let scheme = SecurityScheme::Http(http.build());
149
150 let mut components = self
151 .openapi
152 .components
153 .unwrap_or_else(|| ComponentsBuilder::new().build());
154 components
155 .security_schemes
156 .insert(scheme_name.to_string(), scheme);
157 self.openapi.components = Some(components);
158 self
159 }
160
161 pub fn set_global_security(mut self, scheme_name: &str, scopes: &[&str]) -> Self {
163 use utoipa::openapi::security::SecurityRequirement;
164
165 let scopes_vec: Vec<String> = scopes.iter().map(|s| s.to_string()).collect();
166 let requirement = SecurityRequirement::new(scheme_name.to_string(), scopes_vec);
167
168 match self.openapi.security {
169 Some(ref mut list) => list.push(requirement),
170 None => self.openapi.security = Some(vec![requirement]),
171 }
172 self
173 }
174
175 pub fn openapi(&self) -> &OpenApi {
177 &self.openapi
178 }
179
180 pub fn into_openapi(self) -> OpenApi {
182 self.openapi
183 }
184
185 pub fn to_json(&self) -> Result<String> {
187 serde_json::to_string(&self.openapi).map_err(OpenApiError::Json)
188 }
189
190 pub fn to_pretty_json(&self) -> Result<String> {
192 serde_json::to_string_pretty(&self.openapi).map_err(OpenApiError::Json)
193 }
194
195 pub fn to_json_value(&self) -> Result<Value> {
197 serde_json::to_value(&self.openapi).map_err(OpenApiError::Json)
198 }
199}
200
201#[derive(Debug, Clone)]
205pub struct PathInfo {
206 pub method: http::Method,
208 pub path: String,
210 pub operation_id: Option<String>,
212 pub summary: Option<String>,
214 pub description: Option<String>,
216 pub tags: Vec<String>,
218}
219
220impl PathInfo {
221 pub fn new(method: http::Method, path: &str) -> Self {
223 Self {
224 method,
225 path: path.to_string(),
226 operation_id: None,
227 summary: None,
228 description: None,
229 tags: Vec::new(),
230 }
231 }
232
233 pub fn operation_id<S: Into<String>>(mut self, id: S) -> Self {
235 self.operation_id = Some(id.into());
236 self
237 }
238
239 pub fn summary<S: Into<String>>(mut self, summary: S) -> Self {
241 self.summary = Some(summary.into());
242 self
243 }
244
245 pub fn description<S: Into<String>>(mut self, description: S) -> Self {
247 self.description = Some(description.into());
248 self
249 }
250
251 pub fn tag<S: Into<String>>(mut self, tag: S) -> Self {
253 self.tags.push(tag.into());
254 self
255 }
256
257 pub fn tags<I, S>(mut self, tags: I) -> Self
259 where
260 I: IntoIterator<Item = S>,
261 S: Into<String>,
262 {
263 self.tags = tags.into_iter().map(|s| s.into()).collect();
264 self
265 }
266}
267
268pub fn create_success_response(description: &str) -> utoipa::openapi::Response {
270 use utoipa::openapi::ResponseBuilder;
271
272 ResponseBuilder::new().description(description).build()
273}
274
275pub fn create_json_response(
277 description: &str,
278 _schema_ref: Option<&str>,
279) -> utoipa::openapi::Response {
280 use utoipa::openapi::ResponseBuilder;
281
282 ResponseBuilder::new().description(description).build()
284}
285
286#[cfg(test)]
287mod tests {
288 use super::*;
289
290 #[test]
291 fn test_openapi_doc_creation() {
292 let doc = OpenApiDoc::new("Test API", "1.0.0")
293 .description("A test API")
294 .add_server("http://localhost:8080", Some("Development server"));
295
296 let openapi = doc.openapi();
297 assert_eq!(openapi.info.title, "Test API");
298 assert_eq!(openapi.info.version, "1.0.0");
299 assert_eq!(openapi.info.description, Some("A test API".to_string()));
300 assert!(openapi.servers.is_some());
301 }
302
303 #[test]
304 fn test_path_info() {
305 let path_info = PathInfo::new(http::Method::GET, "/users/{id}")
306 .operation_id("get_user")
307 .summary("Get user by ID")
308 .description("Retrieve a user by their unique identifier")
309 .tag("users");
310
311 assert_eq!(path_info.method, http::Method::GET);
312 assert_eq!(path_info.path, "/users/{id}");
313 assert_eq!(path_info.operation_id, Some("get_user".to_string()));
314 assert_eq!(path_info.tags, vec!["users"]);
315 }
316
317 #[test]
318 fn test_json_serialization() {
319 let doc = OpenApiDoc::new("Test API", "1.0.0");
320 let json = doc.to_json().unwrap();
321 assert!(json.contains("Test API"));
322 assert!(json.contains("1.0.0"));
323 }
324
325 #[test]
326 fn test_add_server_and_security() {
327 let doc = OpenApiDoc::new("T", "1")
328 .add_server("https://api.example.com", Some("prod"))
329 .add_bearer_auth("bearerAuth", Some("jwt"))
330 .set_global_security("bearerAuth", &[]);
331 let json_value = doc.to_json_value().unwrap();
332 assert!(json_value["servers"].is_array());
334 assert!(json_value["components"]["securitySchemes"]["bearerAuth"].is_object());
335 assert!(json_value["security"].is_array());
336 }
337
338 #[test]
339 fn test_add_paths_multiple_and_pretty_json() {
340 let pi = PathItem::default();
341 let doc = OpenApiDoc::new("T", "1").add_paths(vec![("/ping".into(), pi)]);
342 let pretty = doc.to_pretty_json().unwrap();
343 assert!(pretty.contains("/ping"));
344 }
345}