use async_trait::async_trait;
use axum::Router as AxumRouter;
use loco_rs::prelude::*;
use utoipa::openapi::OpenApi;
use utoipa_axum::router::OpenApiRouter;
#[cfg(feature = "redoc")]
use utoipa_redoc::{Redoc, Servable};
#[cfg(feature = "scalar")]
use utoipa_scalar::{Scalar, Servable as ScalarServable};
#[cfg(feature = "swagger")]
use utoipa_swagger_ui::SwaggerUi;
use crate::config::{get_openapi_config, set_openapi_config, InitializerConfig};
use crate::openapi::get_merged_router;
use crate::utils::set_openapi_spec;
#[cfg(any(feature = "redoc", feature = "scalar", feature = "swagger"))]
use crate::utils::{add_openapi_endpoints, get_openapi_spec};
pub mod auth;
pub mod config;
pub mod openapi;
pub mod prelude;
pub mod utils;
type RouterList = Option<Vec<OpenApiRouter<AppContext>>>;
type InitialSpec = dyn Fn(&AppContext) -> OpenApi + Send + Sync + 'static;
#[derive(Default)]
pub struct OpenapiInitializerWithSetup {
initial_spec: Option<Box<InitialSpec>>,
routes_setup: RouterList,
}
impl OpenapiInitializerWithSetup {
#[must_use]
pub fn new<F>(initial_spec: F, routes_setup: RouterList) -> Self
where
F: Fn(&AppContext) -> OpenApi + Send + Sync + 'static,
{
Self {
initial_spec: Some(Box::new(initial_spec)),
routes_setup,
}
}
}
#[async_trait]
impl Initializer for OpenapiInitializerWithSetup {
fn name(&self) -> String {
"openapi".to_string()
}
async fn after_routes(&self, router: AxumRouter, ctx: &AppContext) -> Result<AxumRouter> {
set_openapi_config(InitializerConfig::from(&ctx.config.initializers).into())?;
let mut api_router: OpenApiRouter<AppContext> = self
.initial_spec
.as_ref()
.map_or_else(OpenApiRouter::new, |custom_spec_fn| {
OpenApiRouter::with_openapi(custom_spec_fn(ctx))
});
if let Some(ref routes_setup) = self.routes_setup {
for route in routes_setup {
api_router = api_router.merge(route.clone());
}
}
api_router = api_router.merge(get_merged_router());
let (_, open_api_spec) = api_router.split_for_parts();
set_openapi_spec(open_api_spec);
let Some(_open_api_config) = get_openapi_config() else {
return Ok(router);
};
#[allow(unused_mut)]
let mut ui_router = AxumRouter::new();
#[cfg(feature = "redoc")]
if let Some(config::OpenAPIType::Redoc {
url,
spec_json_url,
spec_yaml_url,
}) = get_openapi_config().and_then(|c| c.redoc.as_ref())
{
ui_router = ui_router.merge(Redoc::with_url(url, get_openapi_spec().clone()));
ui_router = add_openapi_endpoints(ui_router, spec_json_url, spec_yaml_url);
}
#[cfg(feature = "scalar")]
if let Some(config::OpenAPIType::Scalar {
url,
spec_json_url,
spec_yaml_url,
}) = get_openapi_config().and_then(|c| c.scalar.as_ref())
{
ui_router = ui_router.merge(Scalar::with_url(url, get_openapi_spec().clone()));
ui_router = add_openapi_endpoints(ui_router, spec_json_url, spec_yaml_url);
}
#[cfg(feature = "swagger")]
if let Some(config::OpenAPIType::Swagger {
url,
spec_json_url,
spec_yaml_url,
}) = get_openapi_config().and_then(|c| c.swagger.as_ref())
{
ui_router = ui_router
.merge(SwaggerUi::new(url).url(spec_json_url.clone(), get_openapi_spec().clone()));
ui_router = add_openapi_endpoints(ui_router, &None, spec_yaml_url);
}
Ok(router.merge(ui_router))
}
}