use bytes::Bytes;
use std::convert::Infallible;
#[cfg(test)]
#[path = "../../tests/unit/server/swagger_tests.rs"]
mod tests;
use hyper::{Request, Response, StatusCode, header};
use reqwest::Body;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SwaggerSource {
pub name: String,
pub url: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SwaggerUIConfig {
#[serde(default)]
pub enabled: bool,
#[serde(default = "default_swagger_path")]
pub path: String,
#[serde(default)]
pub sources: Vec<SwaggerSource>,
}
fn default_swagger_path() -> String {
"/swagger-ui".to_string()
}
impl Default for SwaggerUIConfig {
fn default() -> Self {
Self {
enabled: false,
path: default_swagger_path(),
sources: Vec::new(),
}
}
}
fn generate_urls_config(sources: &[SwaggerSource]) -> String {
if sources.is_empty() {
return "[]".to_string();
}
let mut urls_json = String::from("[");
for (i, source) in sources.iter().enumerate() {
urls_json.push_str(&format!(
"{{ url: \"{}\", name: \"{}\" }}",
source.url, source.name
));
if i < sources.len() - 1 {
urls_json.push_str(", ");
}
}
urls_json.push(']');
urls_json
}
fn generate_html(config: &SwaggerUIConfig) -> String {
let urls_config = generate_urls_config(&config.sources);
let default_url = config.sources.first().map_or("", |s| &s.url);
format!(
r#"
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Foxy Swagger UI</title>
<link rel="stylesheet" type="text/css" href="https://unpkg.com/swagger-ui-dist@5.24.0/swagger-ui.css">
<style>
html {{ box-sizing: border-box; overflow: -moz-scrollbars-vertical; overflow-y: scroll; }}
*, *:before, *:after {{ box-sizing: inherit; }}
body {{ margin:0; background: #fafafa; }}
</style>
</head>
<body>
<div id="swagger-ui"></div>
<script src="https://unpkg.com/swagger-ui-dist@5.24.0/swagger-ui-bundle.js" charset="UTF-8" crossorigin></script>
<script src="https://unpkg.com/swagger-ui-dist@5.24.0/swagger-ui-standalone-preset.js" crossorigin></script>
<script>
window.onload = () => {{
window.ui = SwaggerUIBundle({{
url: '{default_url}',
urls: {urls_config},
dom_id: '#swagger-ui',
deepLinking: true,
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
layout: "StandaloneLayout",
}});
}};
</script>
</body>
</html>
"#,
default_url = default_url,
urls_config = urls_config
)
}
pub async fn handle_swagger_request<T>(
req: &Request<T>,
config: &SwaggerUIConfig,
) -> Result<Response<Body>, Infallible> {
let path = req.uri().path();
let root_path = &config.path;
let index_path = format!("{}/index.html", root_path);
if path == *root_path || path == format!("{root_path}/") || path == index_path {
let html = generate_html(config);
return Ok(Response::builder()
.status(StatusCode::OK)
.header(header::CONTENT_TYPE, "text/html; charset=utf-8")
.body(Body::from(Bytes::from(html)))
.unwrap());
}
Ok(Response::builder()
.status(StatusCode::NOT_FOUND)
.body(Body::from(Bytes::from("Not Found")))
.unwrap())
}