use std::borrow::Cow;
use std::sync::{Arc, OnceLock};
use bytes::Bytes;
use swagger_ui_redist::SwaggerUiStaticFile;
use crate::App;
use crate::html::Html;
use crate::json::Json;
use crate::request::extractors::StaticFiles;
use crate::request::{Request, RequestExt};
use crate::router::{Route, Router};
use crate::static_files::StaticFile;
#[derive(Debug, Clone)]
pub struct SwaggerUi {
inner: Arc<OnceLock<swagger_ui_redist::SwaggerUi>>,
openapi_path: Arc<Cow<'static, str>>,
serve_openapi: bool,
}
async fn openapi_json(request: Request) -> Json<aide::openapi::OpenApi> {
Json(request.router().as_api())
}
impl Default for SwaggerUi {
fn default() -> Self {
Self::new()
}
}
impl SwaggerUi {
#[must_use]
pub fn new() -> Self {
Self::new_impl(Cow::Borrowed("openapi.json"), true)
}
#[must_use]
pub fn with_api_at<P: Into<Cow<'static, str>>>(openapi_path: P) -> Self {
Self::new_impl(openapi_path.into(), false)
}
fn new_impl(openapi_path: Cow<'static, str>, serve_openapi: bool) -> Self {
Self {
inner: Arc::new(OnceLock::new()),
openapi_path: Arc::new(openapi_path),
serve_openapi,
}
}
fn build_swagger_ui(
openapi_path: Cow<'static, str>,
static_files: &StaticFiles,
) -> crate::Result<swagger_ui_redist::SwaggerUi> {
let mut swagger_ui = swagger_ui_redist::SwaggerUi::new();
swagger_ui.config().urls([openapi_path]);
for static_file in SwaggerUiStaticFile::all() {
let file_path = static_files.url_for(&Self::static_file_path(*static_file))?;
swagger_ui.override_file_path(*static_file, file_path.to_owned());
}
Ok(swagger_ui)
}
fn static_file_path(static_file: SwaggerUiStaticFile) -> String {
format!("swagger/{}", static_file.file_name())
}
}
impl App for SwaggerUi {
fn name(&self) -> &'static str {
"swagger-ui"
}
fn router(&self) -> Router {
let swagger_ui = Arc::clone(&self.inner);
let openapi_path = Arc::clone(&self.openapi_path);
let swagger_handler = async move |static_files: StaticFiles| {
let swagger_ui = swagger_ui.get_or_init(move || {
Self::build_swagger_ui((*openapi_path).clone(), &static_files)
.expect("could not build swagger UI")
});
let swagger = swagger_ui.serve().map_err(cot::Error::internal)?;
Ok::<_, crate::Error>(Html::new(swagger))
};
let mut urls = vec![Route::with_handler("/", swagger_handler)];
if self.serve_openapi {
urls.push(Route::with_handler("/openapi.json", openapi_json));
}
Router::with_urls(urls)
}
fn static_files(&self) -> Vec<StaticFile> {
swagger_ui_redist::SwaggerUi::static_files()
.iter()
.map(|(static_file_id, data)| {
let path = Self::static_file_path(*static_file_id);
let bytes = Bytes::from_static(data);
StaticFile::new(path, bytes)
})
.collect()
}
}