use std::borrow::Cow;
use std::sync::Arc;
use swagger_ui::UrlObject;
use crate::internals::AccessSwaggapiPageBuilder;
use crate::internals::SwaggapiPageBuilderImpl;
use crate::page::SwaggapiPageBuilder;
use crate::PageOfEverything;
use crate::SwaggapiPage;
pub struct SwaggerUi {
pub path: &'static str,
pub config: swagger_ui::Config,
unnamed_pages: usize,
pages: Vec<(
&'static str,
Cow<'static, str>,
&'static SwaggapiPageBuilder,
)>,
}
impl Default for SwaggerUi {
fn default() -> Self {
Self {
path: "/swagger-ui",
config: swagger_ui::Config::default(),
unnamed_pages: 0,
pages: vec![(
"Entire API",
Cow::Borrowed("openapi.json"),
PageOfEverything.get_builder(),
)],
}
}
}
impl SwaggerUi {
pub fn without_everything() -> Self {
Self {
pages: vec![],
..Default::default()
}
}
pub fn with_path(path: &'static str) -> Self {
Self {
path,
..Default::default()
}
}
pub fn page(mut self, display_name: &'static str, page: impl SwaggapiPage) -> Self {
let builder = page.get_builder();
let filename = builder.filename.map(Cow::Borrowed).unwrap_or_else(|| {
self.unnamed_pages += 1;
Cow::Owned(format!("openapi_{}.json", self.unnamed_pages))
});
self.pages
.push((display_name, filename, page.get_builder()));
self
}
}
#[cfg(feature = "actix")]
const _: () = {
use actix_web::dev::AppService;
use actix_web::dev::HttpServiceFactory;
use actix_web::web;
use actix_web::web::scope;
use actix_web::web::Json;
use actix_web::web::Redirect;
use actix_web::HttpResponse;
use actix_web::Responder;
use actix_web::Route;
impl HttpServiceFactory for SwaggerUi {
fn register(self, app: &mut AppService) {
let mut config = self.config;
config.urls.extend(
self.pages
.iter()
.map(|(page_name, file_name, _)| UrlObject::new(page_name, file_name)),
);
let config = Arc::new(config);
let mut scope = scope(self.path)
.route(
"/",
serve_static(|| Redirect::to("index.html?configUrl=config.json")),
)
.route("config.json", serve_static(move || Json(config)));
for (_, file_name, builder) in self.pages {
scope = scope.route(
&file_name,
serve_static(|| Json(SwaggapiPageBuilderImpl::build(builder))),
);
}
for file_name in swagger_ui::Assets::iter() {
if let Some(file_content) = swagger_ui::Assets::get(&file_name) {
scope = scope.route(
&file_name,
serve_static(|| HttpResponse::Ok().body(file_content)),
);
}
}
scope.register(app)
}
}
fn serve_static<H: FnOnce() -> R + Clone + 'static, R: Responder + 'static>(
handler: H,
) -> Route {
web::get().to(move || std::future::ready((handler.clone())()))
}
};
#[cfg(feature = "axum")]
const _: () = {
use axum::body::Body;
use axum::response::IntoResponse;
use axum::response::Redirect;
use axum::response::Response;
use axum::routing::MethodRouter;
use axum::Json;
use axum::Router;
impl<S> From<SwaggerUi> for Router<S>
where
S: Clone + Send + Sync + 'static,
{
fn from(value: SwaggerUi) -> Router<S> {
let mut config = value.config;
config.urls.extend(
value
.pages
.iter()
.map(|(page_name, file_name, _)| UrlObject::new(page_name, file_name)),
);
let config = Arc::new(config);
let mut router = Router::<S>::new()
.route(
&format!("{}/", value.path),
serve_static(|| Redirect::to("index.html?configUrl=config.json")),
)
.route(
&format!("{}/config.json", value.path),
serve_static(move || Json(config)),
);
for (_, file_name, builder) in value.pages {
router = router.route(
&format!("{}/{file_name}", value.path),
serve_static(|| Json(SwaggapiPageBuilderImpl::build(builder))),
);
}
for file_name in swagger_ui::Assets::iter() {
if let Some(file_content) = swagger_ui::Assets::get(&file_name) {
router = router.route(
&format!("{}/{file_name}", value.path),
serve_static(|| Response::new(Body::from(file_content))),
);
}
}
router
}
}
fn serve_static<S, H, R>(handler: H) -> MethodRouter<S>
where
S: Clone,
H: FnOnce() -> R + Clone,
R: IntoResponse,
S: Send + Sync + 'static,
H: Send + Sync + 'static,
R: Send + 'static,
{
MethodRouter::new().get(move || std::future::ready((handler.clone())()))
}
};