Skip to content

Commit

Permalink
added missing files for axum integration
Browse files Browse the repository at this point in the history
  • Loading branch information
Wicpar committed Oct 5, 2023
1 parent 01bd218 commit 0265c22
Show file tree
Hide file tree
Showing 2 changed files with 154 additions and 0 deletions.
22 changes: 22 additions & 0 deletions axum-swagger-ui/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[package]
name = "axum-swagger-ui"
version = "0.1.0"
edition = "2021"
authors = ["Wicpar"]
description = "Swagger-ui for rust applications with axum integration"
license = "MIT"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
swagger-ui = { version = "0.1", path = "../swagger-ui" }
axum = { version = "0.6", features = ["headers"] }
mime = "0.3"
mime_guess = "2.0"

[dev-dependencies]
tokio = "1.32.0"
hyper = { version = "0.14", features = ["full"] }
tower = { version = "0.4", features = ["util"] }
tower-http = { version = "0.4.0"}
serde_json = "1.0"
132 changes: 132 additions & 0 deletions axum-swagger-ui/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
use std::sync::Arc;
use axum::extract::OriginalUri;
use axum::http::{StatusCode, Uri};
use axum::response::{IntoResponse, Redirect, Response};
use axum::{Json, Router, TypedHeader};
use axum::headers::ContentType;
use axum::routing::get;
use swagger_ui::{Assets, Config, SpecOrUrl};

/// Helper trait to allow `route.swagger_ui_route(...)`
pub trait SwaggerUiExt {
fn swagger_ui(self, path: &str, spec: impl Into<SpecOrUrl>, config: impl Into<Option<Config>>) -> Self;
}

impl SwaggerUiExt for Router {
fn swagger_ui(self, path: &str, spec: impl Into<SpecOrUrl>, config: impl Into<Option<Config>>) -> Self {
self.nest(path, swagger_ui_route(spec, config))
}
}

/// creates a route that is configured to serve the specified spec and config with swagger_ui
pub fn swagger_ui_route(spec: impl Into<SpecOrUrl>, config: impl Into<Option<Config>>) -> Router {
let config = Arc::new(config.into().unwrap_or_default());
let spec = Arc::new(spec.into());
Router::new()
.route("/", get(redirect_index))
.route("/*path",
get(move |uri: Uri, original: OriginalUri| {
let config = config.clone();
let spec = spec.clone();
async move {
handle_path(uri, original, &spec, &config).await
}
}),
)
}

async fn redirect_index(uri: OriginalUri) -> Redirect {
let p = uri.path().trim_end_matches("/");
let query = uri.query();
Redirect::permanent(&if let Some(q) = query {
format!("{p}/index.html?{q}")
} else {
format!("{p}/index.html")
})
}

fn mime_type(filename: &str) -> TypedHeader<ContentType> {
TypedHeader(ContentType::from(mime_guess::from_ext(filename.split(".").last().unwrap_or_default()).first_or_octet_stream()))
}

async fn handle_path(uri: Uri, original: OriginalUri, spec: &SpecOrUrl, config: &Config) -> Response {
let path = uri.path().trim_start_matches("/");
if let Some(asset) = Assets::get(path) {
let t = mime_type(path);
return (t, asset).into_response();
}
if path == "swagger-ui-config.json" {
let mut config = config.clone();
match spec {
SpecOrUrl::Spec(spec) => config.url = original.path().replace("swagger-ui-config.json", &spec.name),
SpecOrUrl::Url(url) => config.url = url.to_string()
}
return Json(config).into_response();
}
if let SpecOrUrl::Spec(spec) = spec {
if path == spec.name.trim_start_matches("/") {
return (TypedHeader(ContentType::json()), spec.content.clone()).into_response();
}
}
StatusCode::NOT_FOUND.into_response()
}

#[cfg(test)]
mod tests {
use axum::body::Body;
use axum::headers::ContentType;
use axum::http::{Request, StatusCode};
use axum::http::header::CONTENT_TYPE;
use axum::Router;
use hyper::Method;
use tower::Service;

Check warning on line 82 in axum-swagger-ui/src/lib.rs

View workflow job for this annotation

GitHub Actions / Rust project

unused import: `tower::Service`
use tower::ServiceExt;
use swagger_ui::Config;
use crate::{swagger_ui_route};

fn app() -> Router {
swagger_ui_route(swagger_ui::swagger_spec_file!("../../swagger-ui/examples/openapi.json"), None)
}

#[tokio::test]
async fn does_redirect() {
let app = app();
let response = app
.oneshot(Request::builder().method(Method::GET).uri("/").body(Body::empty()).unwrap())
.await
.unwrap();

assert_eq!(response.status(), StatusCode::PERMANENT_REDIRECT);
}

#[tokio::test]
async fn does_index() {
let app = app();

let response = app
.oneshot(Request::builder().method(Method::GET).uri("/index.html").body(Body::empty()).unwrap())
.await
.unwrap();

assert_eq!(response.status(), StatusCode::OK);
let header: ContentType = response.headers().get(CONTENT_TYPE).unwrap().to_str().unwrap().parse().unwrap();
assert_eq!(header, ContentType::html());
let body = hyper::body::to_bytes(response.into_body()).await.unwrap();

Check warning on line 114 in axum-swagger-ui/src/lib.rs

View workflow job for this annotation

GitHub Actions / Rust project

unused variable: `body`
}

#[tokio::test]
async fn does_config() {
let app = app();

let response = app
.oneshot(Request::builder().method(Method::GET).uri("/swagger-ui-config.json").body(Body::empty()).unwrap())
.await
.unwrap();

assert_eq!(response.status(), StatusCode::OK);
let header: ContentType = response.headers().get(CONTENT_TYPE).unwrap().to_str().unwrap().parse().unwrap();
assert_eq!(header, ContentType::json());
let body = hyper::body::to_bytes(response.into_body()).await.unwrap();
let config: Config = serde_json::from_str(std::str::from_utf8(body.as_ref()).unwrap()).unwrap();

Check warning on line 130 in axum-swagger-ui/src/lib.rs

View workflow job for this annotation

GitHub Actions / Rust project

unused variable: `config`
}
}

0 comments on commit 0265c22

Please sign in to comment.