1use super::definition::RouteDefinition;
2#[cfg(feature = "openapi-schemas")]
3use crate::{openapi_schema_name, BootError};
4use crate::{
5 OpenApiApiKeyLocation, OpenApiExample, OpenApiHeader, OpenApiOAuthFlows, OpenApiParameter,
6 OpenApiParameterLocation, OpenApiRef, OpenApiReferenceOr, OpenApiRequestBody, OpenApiResponse,
7 OpenApiRouteMetadata, OpenApiSchema, OpenApiSecurityRequirement, OpenApiSecurityScheme,
8 OpenApiServer, Result,
9};
10use serde::Serialize;
11use serde_json::Value;
12
13impl RouteDefinition {
14 pub fn with_openapi(mut self, metadata: OpenApiRouteMetadata) -> Self {
15 self.openapi = metadata;
16 self
17 }
18
19 pub fn with_tag(mut self, tag: impl Into<String>) -> Self {
20 let tag = tag.into();
21 if !self.openapi.tags.contains(&tag) {
22 self.openapi.tags.push(tag);
23 }
24 self
25 }
26
27 pub fn with_operation_id(mut self, operation_id: impl Into<String>) -> Self {
28 self.openapi.operation_id = Some(operation_id.into());
29 self
30 }
31
32 pub fn with_summary(mut self, summary: impl Into<String>) -> Self {
33 self.openapi.summary = Some(summary.into());
34 self
35 }
36
37 pub fn with_description(mut self, description: impl Into<String>) -> Self {
38 self.openapi.description = Some(description.into());
39 self
40 }
41
42 pub fn with_deprecated(mut self) -> Self {
43 self.openapi.deprecated = true;
44 self
45 }
46
47 pub fn with_openapi_server(mut self, url: impl Into<String>) -> Self {
48 self.openapi.servers.push(OpenApiServer::new(url));
49 self
50 }
51
52 pub fn with_openapi_server_description(
53 mut self,
54 url: impl Into<String>,
55 description: impl Into<String>,
56 ) -> Self {
57 self.openapi
58 .servers
59 .push(OpenApiServer::new(url).with_description(description));
60 self
61 }
62
63 pub fn with_openapi_external_docs(
64 mut self,
65 description: impl Into<String>,
66 url: impl Into<String>,
67 ) -> Self {
68 self.openapi.external_docs = Some(crate::OpenApiExternalDocs::new(description, url));
69 self
70 }
71
72 pub fn with_openapi_extension_value(mut self, name: impl Into<String>, value: Value) -> Self {
73 self.openapi.extensions.insert(name.into(), value);
74 self
75 }
76
77 pub fn with_openapi_extension_default_value(
78 mut self,
79 name: impl Into<String>,
80 value: Value,
81 ) -> Self {
82 self.openapi.extensions.entry(name.into()).or_insert(value);
83 self
84 }
85
86 pub fn try_with_openapi_extension<T>(self, name: impl Into<String>, value: T) -> Result<Self>
87 where
88 T: Serialize,
89 {
90 let name = name.into();
91 let value = serde_json::to_value(value).map_err(|error| {
92 crate::BootError::Internal(format!(
93 "OpenAPI extension `{name}` could not be serialized: {error}"
94 ))
95 })?;
96 Ok(self.with_openapi_extension_value(name, value))
97 }
98
99 pub fn hide_from_openapi(mut self) -> Self {
100 self.openapi.hidden = true;
101 self
102 }
103
104 pub fn with_parameter(mut self, parameter: OpenApiParameter) -> Self {
105 upsert_parameter(
106 &mut self.openapi.parameters,
107 OpenApiReferenceOr::value(parameter),
108 );
109 self
110 }
111
112 pub fn with_parameter_ref(
113 mut self,
114 location: OpenApiParameterLocation,
115 name: impl Into<String>,
116 component_name: impl AsRef<str>,
117 ) -> Self {
118 let name = name.into();
119 let reference =
120 OpenApiRef::parameter(component_name).with_parameter_metadata(location, name);
121 upsert_parameter(
122 &mut self.openapi.parameters,
123 OpenApiReferenceOr::reference(reference),
124 );
125 self
126 }
127
128 pub fn with_path_parameter(self, name: impl Into<String>, schema: OpenApiSchema) -> Self {
129 self.with_parameter(OpenApiParameter::path(name, schema))
130 }
131
132 pub fn with_path_parameter_ref(
133 self,
134 name: impl Into<String>,
135 component_name: impl AsRef<str>,
136 ) -> Self {
137 self.with_parameter_ref(OpenApiParameterLocation::Path, name, component_name)
138 }
139
140 pub fn with_query_parameter(
141 self,
142 name: impl Into<String>,
143 required: bool,
144 schema: OpenApiSchema,
145 ) -> Self {
146 self.with_parameter(OpenApiParameter::query(name, required, schema))
147 }
148
149 pub fn with_query_parameter_ref(
150 self,
151 name: impl Into<String>,
152 component_name: impl AsRef<str>,
153 ) -> Self {
154 self.with_parameter_ref(OpenApiParameterLocation::Query, name, component_name)
155 }
156
157 pub fn with_header_parameter(
158 self,
159 name: impl Into<String>,
160 required: bool,
161 schema: OpenApiSchema,
162 ) -> Self {
163 self.with_parameter(OpenApiParameter::header(name, required, schema))
164 }
165
166 pub fn with_header_parameter_ref(
167 self,
168 name: impl Into<String>,
169 component_name: impl AsRef<str>,
170 ) -> Self {
171 self.with_parameter_ref(OpenApiParameterLocation::Header, name, component_name)
172 }
173
174 pub fn with_cookie_parameter(
175 self,
176 name: impl Into<String>,
177 required: bool,
178 schema: OpenApiSchema,
179 ) -> Self {
180 self.with_parameter(OpenApiParameter::cookie(name, required, schema))
181 }
182
183 pub fn with_cookie_parameter_ref(
184 self,
185 name: impl Into<String>,
186 component_name: impl AsRef<str>,
187 ) -> Self {
188 self.with_parameter_ref(OpenApiParameterLocation::Cookie, name, component_name)
189 }
190
191 pub fn with_request_body(mut self, request_body: OpenApiRequestBody) -> Self {
192 self.openapi.request_body = Some(OpenApiReferenceOr::value(request_body));
193 self
194 }
195
196 pub fn with_request_body_ref(mut self, component_name: impl AsRef<str>) -> Self {
197 self.openapi.request_body = Some(OpenApiReferenceOr::reference(OpenApiRef::request_body(
198 component_name,
199 )));
200 self
201 }
202
203 pub fn with_request_body_content_type(
204 self,
205 content_type: impl Into<String>,
206 schema: OpenApiSchema,
207 ) -> Self {
208 self.with_request_body(OpenApiRequestBody::content(content_type, schema))
209 }
210
211 pub fn with_json_request_body(self, schema: OpenApiSchema) -> Self {
212 self.with_request_body(OpenApiRequestBody::json(schema))
213 }
214
215 pub fn try_with_request_body_content_type_example<T>(
216 self,
217 content_type: impl Into<String>,
218 schema: OpenApiSchema,
219 example: T,
220 ) -> Result<Self>
221 where
222 T: Serialize,
223 {
224 Ok(
225 self.with_request_body(OpenApiRequestBody::try_content_example(
226 content_type,
227 schema,
228 example,
229 )?),
230 )
231 }
232
233 pub fn try_with_json_request_body_example<T>(
234 self,
235 schema: OpenApiSchema,
236 example: T,
237 ) -> Result<Self>
238 where
239 T: Serialize,
240 {
241 Ok(self.with_request_body(OpenApiRequestBody::try_json_example(schema, example)?))
242 }
243
244 pub fn try_with_request_body_content_type_named_example<T>(
245 self,
246 content_type: impl Into<String>,
247 schema: OpenApiSchema,
248 name: impl Into<String>,
249 example: T,
250 ) -> Result<Self>
251 where
252 T: Serialize,
253 {
254 let content_type = content_type.into();
255 let request_body = OpenApiRequestBody::content(content_type.clone(), schema)
256 .try_with_content_named_example(content_type, name, example)?;
257 Ok(self.with_request_body(request_body))
258 }
259
260 pub fn try_with_json_request_body_named_example<T>(
261 self,
262 schema: OpenApiSchema,
263 name: impl Into<String>,
264 example: T,
265 ) -> Result<Self>
266 where
267 T: Serialize,
268 {
269 Ok(self.with_request_body(
270 OpenApiRequestBody::json(schema).try_with_json_named_example(name, example)?,
271 ))
272 }
273
274 pub fn with_request_body_content_type_named_example_ref(
275 self,
276 content_type: impl Into<String>,
277 schema: OpenApiSchema,
278 name: impl Into<String>,
279 component_name: impl AsRef<str>,
280 ) -> Self {
281 let content_type = content_type.into();
282 let request_body = OpenApiRequestBody::content(content_type.clone(), schema)
283 .with_content_named_example_ref(content_type, name, component_name);
284 self.with_request_body(request_body)
285 }
286
287 pub fn with_json_request_body_named_example_ref(
288 self,
289 schema: OpenApiSchema,
290 name: impl Into<String>,
291 component_name: impl AsRef<str>,
292 ) -> Self {
293 self.with_request_body(
294 OpenApiRequestBody::json(schema).with_json_named_example_ref(name, component_name),
295 )
296 }
297
298 pub fn with_response(mut self, status: u16, response: OpenApiResponse) -> Self {
299 self.openapi
300 .responses
301 .insert(status.to_string(), OpenApiReferenceOr::value(response));
302 self
303 }
304
305 pub fn with_response_ref(mut self, status: u16, component_name: impl AsRef<str>) -> Self {
306 self.openapi.responses.insert(
307 status.to_string(),
308 OpenApiReferenceOr::reference(OpenApiRef::response(component_name)),
309 );
310 self
311 }
312
313 pub fn with_default_response(mut self, response: OpenApiResponse) -> Self {
314 self.openapi
315 .responses
316 .insert("default".to_string(), OpenApiReferenceOr::value(response));
317 self
318 }
319
320 pub fn with_default_response_ref(mut self, component_name: impl AsRef<str>) -> Self {
321 self.openapi.responses.insert(
322 "default".to_string(),
323 OpenApiReferenceOr::reference(OpenApiRef::response(component_name)),
324 );
325 self
326 }
327
328 pub fn with_openapi_response_header(
329 mut self,
330 status: u16,
331 name: impl Into<String>,
332 header: OpenApiHeader,
333 ) -> Self {
334 if let Some(response) = self
335 .openapi
336 .responses
337 .entry(status.to_string())
338 .or_insert_with(|| OpenApiReferenceOr::value(OpenApiResponse::description("Success")))
339 .value_mut()
340 {
341 response
342 .headers
343 .insert(name.into(), OpenApiReferenceOr::value(header));
344 }
345 self
346 }
347
348 pub fn with_openapi_response_header_ref(
349 mut self,
350 status: u16,
351 name: impl Into<String>,
352 component_name: impl AsRef<str>,
353 ) -> Self {
354 if let Some(response) = self
355 .openapi
356 .responses
357 .entry(status.to_string())
358 .or_insert_with(|| OpenApiReferenceOr::value(OpenApiResponse::description("Success")))
359 .value_mut()
360 {
361 response.headers.insert(
362 name.into(),
363 OpenApiReferenceOr::reference(OpenApiRef::header(component_name)),
364 );
365 }
366 self
367 }
368
369 pub fn with_default_openapi_response_header(
370 mut self,
371 name: impl Into<String>,
372 header: OpenApiHeader,
373 ) -> Self {
374 if let Some(response) = self
375 .openapi
376 .responses
377 .entry("default".to_string())
378 .or_insert_with(|| {
379 OpenApiReferenceOr::value(OpenApiResponse::description("Default response"))
380 })
381 .value_mut()
382 {
383 response
384 .headers
385 .insert(name.into(), OpenApiReferenceOr::value(header));
386 }
387 self
388 }
389
390 pub fn with_default_openapi_response_header_ref(
391 mut self,
392 name: impl Into<String>,
393 component_name: impl AsRef<str>,
394 ) -> Self {
395 if let Some(response) = self
396 .openapi
397 .responses
398 .entry("default".to_string())
399 .or_insert_with(|| {
400 OpenApiReferenceOr::value(OpenApiResponse::description("Default response"))
401 })
402 .value_mut()
403 {
404 response.headers.insert(
405 name.into(),
406 OpenApiReferenceOr::reference(OpenApiRef::header(component_name)),
407 );
408 }
409 self
410 }
411
412 pub fn with_response_content_type(
413 self,
414 status: u16,
415 description: impl Into<String>,
416 content_type: impl Into<String>,
417 schema: OpenApiSchema,
418 ) -> Self {
419 self.with_response(
420 status,
421 OpenApiResponse::content(description, content_type, schema),
422 )
423 }
424
425 pub fn with_json_response(
426 self,
427 status: u16,
428 description: impl Into<String>,
429 schema: OpenApiSchema,
430 ) -> Self {
431 self.with_response(status, OpenApiResponse::json(description, schema))
432 }
433
434 pub fn try_with_response_content_type_example<T>(
435 self,
436 status: u16,
437 description: impl Into<String>,
438 content_type: impl Into<String>,
439 schema: OpenApiSchema,
440 example: T,
441 ) -> Result<Self>
442 where
443 T: Serialize,
444 {
445 Ok(self.with_response(
446 status,
447 OpenApiResponse::try_content_example(description, content_type, schema, example)?,
448 ))
449 }
450
451 pub fn try_with_json_response_example<T>(
452 self,
453 status: u16,
454 description: impl Into<String>,
455 schema: OpenApiSchema,
456 example: T,
457 ) -> Result<Self>
458 where
459 T: Serialize,
460 {
461 Ok(self.with_response(
462 status,
463 OpenApiResponse::try_json_example(description, schema, example)?,
464 ))
465 }
466
467 pub fn try_with_response_content_type_named_example<T>(
468 self,
469 status: u16,
470 description: impl Into<String>,
471 content_type: impl Into<String>,
472 schema: OpenApiSchema,
473 name: impl Into<String>,
474 example: T,
475 ) -> Result<Self>
476 where
477 T: Serialize,
478 {
479 let content_type = content_type.into();
480 let response = OpenApiResponse::content(description, content_type.clone(), schema)
481 .try_with_content_named_example(content_type, name, example)?;
482 Ok(self.with_response(status, response))
483 }
484
485 pub fn try_with_json_response_named_example<T>(
486 self,
487 status: u16,
488 description: impl Into<String>,
489 schema: OpenApiSchema,
490 name: impl Into<String>,
491 example: T,
492 ) -> Result<Self>
493 where
494 T: Serialize,
495 {
496 Ok(self.with_response(
497 status,
498 OpenApiResponse::json(description, schema)
499 .try_with_json_named_example(name, example)?,
500 ))
501 }
502
503 pub fn with_response_content_type_named_example_ref(
504 self,
505 status: u16,
506 description: impl Into<String>,
507 content_type: impl Into<String>,
508 schema: OpenApiSchema,
509 name: impl Into<String>,
510 component_name: impl AsRef<str>,
511 ) -> Self {
512 let content_type = content_type.into();
513 let response = OpenApiResponse::content(description, content_type.clone(), schema)
514 .with_content_named_example_ref(content_type, name, component_name);
515 self.with_response(status, response)
516 }
517
518 pub fn with_json_response_named_example_ref(
519 self,
520 status: u16,
521 description: impl Into<String>,
522 schema: OpenApiSchema,
523 name: impl Into<String>,
524 component_name: impl AsRef<str>,
525 ) -> Self {
526 self.with_response(
527 status,
528 OpenApiResponse::json(description, schema)
529 .with_json_named_example_ref(name, component_name),
530 )
531 }
532
533 pub fn with_security_requirement(mut self, requirement: OpenApiSecurityRequirement) -> Self {
534 self.openapi.security.push(requirement);
535 self
536 }
537
538 pub fn with_api_security<I, S>(self, name: impl Into<String>, scopes: I) -> Self
539 where
540 I: IntoIterator<Item = S>,
541 S: Into<String>,
542 {
543 let mut requirement = OpenApiSecurityRequirement::new();
544 requirement.insert(
545 name.into(),
546 scopes.into_iter().map(Into::into).collect::<Vec<_>>(),
547 );
548 self.with_security_requirement(requirement)
549 }
550
551 pub fn with_security_scheme(
552 mut self,
553 name: impl Into<String>,
554 scheme: OpenApiSecurityScheme,
555 ) -> Self {
556 self.openapi.security_schemes.insert(name.into(), scheme);
557 self
558 }
559
560 pub fn with_bearer_auth(self) -> Self {
561 self.with_bearer_auth_named("bearerAuth")
562 }
563
564 pub fn with_bearer_auth_named(self, name: impl Into<String>) -> Self {
565 let name = name.into();
566 self.with_security_scheme(name.clone(), OpenApiSecurityScheme::http_bearer())
567 .with_api_security(name, Vec::<String>::new())
568 }
569
570 pub fn with_api_key_auth(
571 self,
572 scheme_name: impl Into<String>,
573 location: OpenApiApiKeyLocation,
574 key_name: impl Into<String>,
575 ) -> Self {
576 let scheme_name = scheme_name.into();
577 self.with_security_scheme(
578 scheme_name.clone(),
579 OpenApiSecurityScheme::api_key(location, key_name),
580 )
581 .with_api_security(scheme_name, Vec::<String>::new())
582 }
583
584 pub fn with_header_api_key_auth(
585 self,
586 scheme_name: impl Into<String>,
587 header_name: impl Into<String>,
588 ) -> Self {
589 self.with_api_key_auth(scheme_name, OpenApiApiKeyLocation::Header, header_name)
590 }
591
592 pub fn with_query_api_key_auth(
593 self,
594 scheme_name: impl Into<String>,
595 query_name: impl Into<String>,
596 ) -> Self {
597 self.with_api_key_auth(scheme_name, OpenApiApiKeyLocation::Query, query_name)
598 }
599
600 pub fn with_cookie_auth(
601 self,
602 scheme_name: impl Into<String>,
603 cookie_name: impl Into<String>,
604 ) -> Self {
605 self.with_api_key_auth(scheme_name, OpenApiApiKeyLocation::Cookie, cookie_name)
606 }
607
608 pub fn with_oauth2_auth<I, S>(
609 self,
610 scheme_name: impl Into<String>,
611 flows: OpenApiOAuthFlows,
612 scopes: I,
613 ) -> Self
614 where
615 I: IntoIterator<Item = S>,
616 S: Into<String>,
617 {
618 let scheme_name = scheme_name.into();
619 self.with_security_scheme(scheme_name.clone(), OpenApiSecurityScheme::oauth2(flows))
620 .with_api_security(scheme_name, scopes)
621 }
622
623 pub fn with_open_id_connect_auth<I, S>(
624 self,
625 scheme_name: impl Into<String>,
626 url: impl Into<String>,
627 scopes: I,
628 ) -> Self
629 where
630 I: IntoIterator<Item = S>,
631 S: Into<String>,
632 {
633 let scheme_name = scheme_name.into();
634 self.with_security_scheme(
635 scheme_name.clone(),
636 OpenApiSecurityScheme::open_id_connect(url),
637 )
638 .with_api_security(scheme_name, scopes)
639 }
640
641 pub fn with_schema_component(mut self, name: impl Into<String>, schema: OpenApiSchema) -> Self {
642 self.openapi.schema_components.insert(name.into(), schema);
643 self
644 }
645
646 pub fn with_response_component(
647 mut self,
648 name: impl Into<String>,
649 response: OpenApiResponse,
650 ) -> Self {
651 self.openapi
652 .response_components
653 .insert(name.into(), response);
654 self
655 }
656
657 pub fn with_parameter_component(
658 mut self,
659 name: impl Into<String>,
660 parameter: OpenApiParameter,
661 ) -> Self {
662 self.openapi
663 .parameter_components
664 .insert(name.into(), parameter);
665 self
666 }
667
668 pub fn with_example_component(
669 mut self,
670 name: impl Into<String>,
671 example: OpenApiExample,
672 ) -> Self {
673 self.openapi.example_components.insert(name.into(), example);
674 self
675 }
676
677 pub fn try_with_example_component<T>(self, name: impl Into<String>, value: T) -> Result<Self>
678 where
679 T: Serialize,
680 {
681 Ok(self.with_example_component(name, OpenApiExample::try_value(value)?))
682 }
683
684 pub fn with_request_body_component(
685 mut self,
686 name: impl Into<String>,
687 request_body: OpenApiRequestBody,
688 ) -> Self {
689 self.openapi
690 .request_body_components
691 .insert(name.into(), request_body);
692 self
693 }
694
695 pub fn with_header_component(mut self, name: impl Into<String>, header: OpenApiHeader) -> Self {
696 self.openapi.header_components.insert(name.into(), header);
697 self
698 }
699
700 #[cfg(feature = "openapi-schemas")]
701 pub fn try_with_json_schema_component<T>(self) -> Result<Self>
702 where
703 T: schemars::JsonSchema,
704 {
705 let schema = OpenApiSchema::json_schema::<T>()
706 .map_err(|error| BootError::Internal(error.to_string()))?;
707 Ok(self.with_schema_component(openapi_schema_name::<T>(), schema))
708 }
709}
710
711fn upsert_parameter(
712 parameters: &mut Vec<OpenApiReferenceOr<OpenApiParameter>>,
713 parameter: OpenApiReferenceOr<OpenApiParameter>,
714) {
715 if let Some((location, name)) = parameter.parameter_identity() {
716 if let Some(existing) = parameters.iter_mut().find(|existing| {
717 existing
718 .parameter_identity()
719 .is_some_and(|(existing_location, existing_name)| {
720 existing_location == location && existing_name == name
721 })
722 }) {
723 *existing = parameter;
724 return;
725 }
726 }
727
728 parameters.push(parameter);
729}