use crate::AppConfig;
use actix_web::{web, HttpRequest, HttpResponse, Result};
use serde_json::{json, Value};
use std::sync::LazyLock;
const EMBEDDED_OPENAPI: &str = include_str!("../../static/openapi.json");
static BASE_SPEC: LazyLock<Value> = LazyLock::new(|| {
serde_json::from_str(EMBEDDED_OPENAPI).expect("embedded openapi.json is valid JSON")
});
pub fn spec_path_count() -> usize {
BASE_SPEC
.as_object()
.and_then(|o| o.get("paths"))
.and_then(|p| p.as_object())
.map(|p| p.len())
.unwrap_or(0)
}
pub async fn openapi_handler(
req: HttpRequest,
config: web::Data<AppConfig>,
) -> Result<HttpResponse> {
let mut openapi: Value = match std::fs::read_to_string(config.static_path.join("openapi.json"))
{
Ok(content) => match serde_json::from_str(&content) {
Ok(spec) => spec,
Err(_) => {
return Ok(HttpResponse::InternalServerError().json(json!({
"error": "Failed to parse OpenAPI specification"
})));
}
},
Err(_) => BASE_SPEC.clone(),
};
if config.add_current_server {
let connection_info = req.connection_info();
let scheme = connection_info.scheme();
let host = connection_info.host();
let current_server_url = format!("{}://{}", scheme, host);
let mut servers_array = Vec::new();
servers_array.push(json!({
"url": current_server_url,
"description": "Current server"
}));
if let Some(existing_servers) = openapi.get("servers").and_then(|s| s.as_array()) {
for server in existing_servers {
if let Some(url) = server.get("url").and_then(|u| u.as_str()) {
if url != current_server_url {
servers_array.push(server.clone());
}
}
}
}
if let Some(obj) = openapi.as_object_mut() {
obj.insert("servers".to_string(), json!(servers_array));
}
}
Ok(HttpResponse::Ok()
.content_type("application/json")
.json(openapi))
}
pub async fn api_catalog_handler(req: HttpRequest) -> Result<HttpResponse> {
let connection_info = req.connection_info();
let base = format!("{}://{}", connection_info.scheme(), connection_info.host());
let catalog = json!({
"linkset": [
{
"anchor": format!("{base}/"),
"service-desc": [
{ "href": format!("{base}/openapi.json"), "type": "application/json" }
],
"service-doc": [
{ "href": format!("{base}/"), "type": "text/html" }
],
"status": [
{ "href": format!("{base}/healthz"), "type": "application/json" }
]
}
]
});
let body =
serde_json::to_string(&catalog).map_err(actix_web::error::ErrorInternalServerError)?;
Ok(HttpResponse::Ok()
.content_type(
r#"application/linkset+json; profile="https://www.rfc-editor.org/info/rfc9727""#,
)
.body(body))
}