1use async_trait::async_trait;
2use axum::Router as AxumRouter;
3use loco_rs::prelude::*;
4use utoipa::openapi::OpenApi;
5use utoipa_axum::router::OpenApiRouter;
6#[cfg(feature = "redoc")]
7use utoipa_redoc::{Redoc, Servable};
8#[cfg(feature = "scalar")]
9use utoipa_scalar::{Scalar, Servable as ScalarServable};
10#[cfg(feature = "swagger")]
11use utoipa_swagger_ui::SwaggerUi;
12
13use crate::config::{get_openapi_config, set_openapi_config, InitializerConfig};
14use crate::openapi::get_merged_router;
15use crate::utils::set_openapi_spec;
17#[cfg(any(feature = "redoc", feature = "scalar", feature = "swagger"))]
19use crate::utils::{add_openapi_endpoints, get_openapi_spec};
20
21pub mod auth;
22pub mod config;
23pub mod openapi;
24pub mod prelude;
25pub mod utils;
26
27type RouterList = Option<Vec<OpenApiRouter<AppContext>>>;
28type InitialSpec = dyn Fn(&AppContext) -> OpenApi + Send + Sync + 'static;
29
30#[derive(Default)]
32pub struct OpenapiInitializerWithSetup {
33 initial_spec: Option<Box<InitialSpec>>,
35 routes_setup: RouterList,
37}
38
39impl OpenapiInitializerWithSetup {
40 #[must_use]
41 pub fn new<F>(initial_spec: F, routes_setup: RouterList) -> Self
42 where
43 F: Fn(&AppContext) -> OpenApi + Send + Sync + 'static,
44 {
45 Self {
46 initial_spec: Some(Box::new(initial_spec)),
47 routes_setup,
48 }
49 }
50}
51
52#[async_trait]
53impl Initializer for OpenapiInitializerWithSetup {
54 fn name(&self) -> String {
55 "openapi".to_string()
56 }
57
58 async fn after_routes(&self, router: AxumRouter, ctx: &AppContext) -> Result<AxumRouter> {
59 set_openapi_config(InitializerConfig::from(&ctx.config.initializers).into())?;
61
62 let mut api_router: OpenApiRouter<AppContext> = self
63 .initial_spec
64 .as_ref()
65 .map_or_else(OpenApiRouter::new, |custom_spec_fn| {
66 OpenApiRouter::with_openapi(custom_spec_fn(ctx))
67 });
68
69 if let Some(ref routes_setup) = self.routes_setup {
71 for route in routes_setup {
72 api_router = api_router.merge(route.clone());
73 }
74 }
75
76 api_router = api_router.merge(get_merged_router());
78
79 let (_, open_api_spec) = api_router.split_for_parts();
81 set_openapi_spec(open_api_spec);
82
83 let Some(_open_api_config) = get_openapi_config() else {
85 return Ok(router);
87 };
88
89 #[allow(unused_mut)]
91 let mut ui_router = AxumRouter::new();
92
93 #[cfg(feature = "redoc")]
95 if let Some(config::OpenAPIType::Redoc {
96 url,
97 spec_json_url,
98 spec_yaml_url,
99 }) = get_openapi_config().and_then(|c| c.redoc.as_ref())
100 {
101 ui_router = ui_router.merge(Redoc::with_url(url, get_openapi_spec().clone()));
102 ui_router = add_openapi_endpoints(ui_router, spec_json_url, spec_yaml_url);
103 }
104
105 #[cfg(feature = "scalar")]
106 if let Some(config::OpenAPIType::Scalar {
107 url,
108 spec_json_url,
109 spec_yaml_url,
110 }) = get_openapi_config().and_then(|c| c.scalar.as_ref())
111 {
112 ui_router = ui_router.merge(Scalar::with_url(url, get_openapi_spec().clone()));
113 ui_router = add_openapi_endpoints(ui_router, spec_json_url, spec_yaml_url);
114 }
115
116 #[cfg(feature = "swagger")]
117 if let Some(config::OpenAPIType::Swagger {
118 url,
119 spec_json_url,
120 spec_yaml_url,
121 }) = get_openapi_config().and_then(|c| c.swagger.as_ref())
122 {
123 ui_router = ui_router
124 .merge(SwaggerUi::new(url).url(spec_json_url.clone(), get_openapi_spec().clone()));
125 ui_router = add_openapi_endpoints(ui_router, &None, spec_yaml_url);
126 }
127
128 Ok(router.merge(ui_router))
130 }
131}