use actix_web::{App, HttpResponse, HttpServer, Responder, web}; use clap::Parser; const SCHEMA_FILE: &str = "schema.yaml"; /// Simple swagger UI server - serves OpenAPI file #[derive(Parser, Debug)] #[clap(author, version, about, long_about = None)] struct Args { /// Path to schema to serve #[clap(short, long)] schema: String, /// Listen address #[clap(short, long, default_value = "0.0.0.0:8000")] listen_address: String, /// Access token, optional #[clap(short, long)] token: Option, } #[derive(serde::Deserialize)] struct HandlerPath { filename: Option, } #[derive(serde::Deserialize)] struct HandlerQuery { token: Option, } #[derive(Debug, Clone, Eq, PartialEq)] struct Schema(String); #[derive(Debug, Clone, Eq, PartialEq)] struct AccessToken(Option); async fn handler(path: web::Path, query: web::Query, schema: web::Data, access_token: web::Data) -> impl Responder { let filename: &str = path.filename.as_deref().unwrap_or("index.html"); if filename.eq("index.html") || filename.eq(SCHEMA_FILE) { if let Some(tok) = access_token.0.as_ref() { if !query.token.as_deref().unwrap_or_default().eq(tok) { return HttpResponse::Unauthorized() .body("Please indicate as query parameter the 'token'"); } } } let ext = filename .rsplit_once('.') .map(|r| r.1).unwrap_or_default(); let content_type = match ext { "js" => "application/javascript", "css" => "text/css", "html" => "text/html", "png" => "image/png", "json" => "application/json", "yml" | "yaml" => "application/x-yaml", _ => "text/plain" }; let filebytes = if filename.eq(SCHEMA_FILE) { Some(schema.0.as_bytes().to_vec()) } else { swagger_ui::Assets::get(filename).map(|f| f.to_vec()) }; match filebytes { Some(f) => { let mut bytes = f.to_vec(); if filename.eq("index.html") { bytes = String::from_utf8_lossy(&bytes).to_string() .replace( "https://petstore.swagger.io/v2/swagger.json", &format!("/{}?token={}", SCHEMA_FILE, access_token.0.as_deref().unwrap_or_default()), ) .as_bytes().to_vec(); } HttpResponse::Ok() .insert_header(("Content-Type", content_type)) .body(bytes) } None => HttpResponse::NotFound().body("404 not found"), } } #[actix_web::main] async fn main() -> std::io::Result<()> { let args: Args = Args::parse(); env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); log::info!("Will listen on http://{}/", args.listen_address); let schema = Schema(std::fs::read_to_string(args.schema) .expect("Failed to read schema!")); let access_token = AccessToken(args.token); HttpServer::new(move || { App::new() .app_data(web::Data::new(schema.clone())) .app_data(web::Data::new(access_token.clone())) .route("/", web::get().to(handler)) .route("/{filename}", web::get().to(handler)) }) .bind(args.listen_address)? .run() .await }