├── rocket-swagger-ui ├── .gitignore ├── README.md ├── examples │ └── basic.rs ├── Cargo.toml └── src │ ├── handlers.rs │ └── lib.rs ├── swagger-ui ├── .gitignore ├── examples │ ├── basic.rs │ └── openapi.json ├── Cargo.toml ├── build.rs ├── README.md └── src │ └── lib.rs ├── doc └── swagger-ui.png ├── .gitignore ├── Cargo.toml ├── actix-web-swagger-ui ├── examples │ └── basic.rs ├── Cargo.toml └── src │ └── lib.rs ├── axum-swagger-ui ├── Cargo.toml └── src │ └── lib.rs ├── .github └── workflows │ └── rust.yml └── README.md /rocket-swagger-ui/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | .idea -------------------------------------------------------------------------------- /swagger-ui/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | .idea 4 | .dist -------------------------------------------------------------------------------- /doc/swagger-ui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kotborealis/swagger-ui/HEAD/doc/swagger-ui.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Cargo.lock 2 | .idea 3 | debug/ 4 | target/ 5 | 6 | **/*.rs.bk 7 | 8 | *.pdb 9 | 10 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "swagger-ui", 4 | "rocket-swagger-ui", 5 | "actix-web-swagger-ui", 6 | "axum-swagger-ui" 7 | ] -------------------------------------------------------------------------------- /rocket-swagger-ui/README.md: -------------------------------------------------------------------------------- 1 | # rocket-swagger-ui 2 | 3 | This crate is a part of [swagger-ui](https://crates.io/crates/swagger-ui). 4 | Please see it for instructions. -------------------------------------------------------------------------------- /actix-web-swagger-ui/examples/basic.rs: -------------------------------------------------------------------------------- 1 | use actix_web::{App, HttpResponse}; 2 | use actix_web::web::{get, scope}; 3 | 4 | use actix_web_swagger_ui; 5 | use swagger_ui; 6 | 7 | fn main() { 8 | let spec = swagger_ui::swagger_spec_file!("../../swagger-ui/examples/openapi.json"); 9 | let config = swagger_ui::Config::default(); 10 | 11 | let _app = App::new() 12 | .service( 13 | scope("/api/v1/swagger") 14 | .configure(actix_web_swagger_ui::swagger(spec, config)) 15 | ) 16 | .route("/index.html", get().to(|| HttpResponse::Ok())); 17 | } -------------------------------------------------------------------------------- /rocket-swagger-ui/examples/basic.rs: -------------------------------------------------------------------------------- 1 | #![feature(proc_macro_hygiene, decl_macro)] 2 | 3 | extern crate rocket; 4 | 5 | use rocket_swagger_ui; 6 | use swagger_ui; 7 | 8 | fn main() { 9 | rocket::ignite() 10 | .mount("/api/v1/swagger/", 11 | rocket_swagger_ui::routes( 12 | // Specify file with openapi specification, 13 | // relative to current file 14 | swagger_ui::swagger_spec_file!("../../swagger-ui/examples/openapi.json"), 15 | swagger_ui::Config { ..Default::default() } 16 | ) 17 | ) 18 | .launch(); 19 | } -------------------------------------------------------------------------------- /actix-web-swagger-ui/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "actix-web-swagger-ui" 3 | version = "0.1.0" 4 | authors = ["Tarkin25 "] 5 | edition = "2021" 6 | exclude = [ 7 | ".idea" 8 | ] 9 | description = "Swagger-ui for rust applications with actix-web integration" 10 | license = "MIT" 11 | 12 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 13 | 14 | [dependencies] 15 | swagger-ui = { version = "0.1", path = "../swagger-ui" } 16 | actix-web = "4.0.0-beta.10" 17 | actix-files = "0.5" 18 | actix-utils = "3.0" 19 | serde = { version = "1.0" } 20 | 21 | [dev-dependencies] 22 | actix-rt = "2.4.0" 23 | actix-http = "3.0.0-beta.11" 24 | -------------------------------------------------------------------------------- /axum-swagger-ui/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "axum-swagger-ui" 3 | version = "0.1.0" 4 | edition = "2021" 5 | authors = ["Wicpar"] 6 | description = "Swagger-ui for rust applications with axum integration" 7 | license = "MIT" 8 | 9 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 10 | 11 | [dependencies] 12 | swagger-ui = { version = "0.1", path = "../swagger-ui" } 13 | axum = { version = "0.6", features = ["headers"] } 14 | mime = "0.3" 15 | mime_guess = "2.0" 16 | 17 | [dev-dependencies] 18 | tokio = "1.32.0" 19 | hyper = { version = "0.14", features = ["full"] } 20 | tower = { version = "0.4", features = ["util"] } 21 | tower-http = { version = "0.4.0"} 22 | serde_json = "1.0" -------------------------------------------------------------------------------- /rocket-swagger-ui/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rocket-swagger-ui" 3 | version = "0.1.5" 4 | authors = ["kotborealis "] 5 | edition = "2021" 6 | exclude = [ 7 | ".idea" 8 | ] 9 | description = "Swagger-ui for rust applications with rocket integration" 10 | license = "MIT" 11 | documentation = "https://docs.rs/rocket-swagger-ui/" 12 | repository = "https://github.com/kotborealis/swagger-ui" 13 | 14 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 15 | 16 | [dependencies] 17 | swagger-ui = { version = "0.1", path = "../swagger-ui" } 18 | rocket = "0.4.7" 19 | serde = { version = "1.0", features = ["derive"] } 20 | serde_json = "1.0.64" 21 | derive_builder = "0.12.0" -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | on: [push] 2 | 3 | name: build 4 | 5 | jobs: 6 | check: 7 | name: Rust project 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | - name: Install latest nightly 12 | uses: actions-rs/toolchain@v1 13 | with: 14 | toolchain: nightly 15 | override: true 16 | components: rustfmt, clippy 17 | 18 | - name: Install openssl 19 | run: sudo apt-get install pkg-config libssl-dev 20 | 21 | - name: Run cargo check 22 | uses: actions-rs/cargo@v1 23 | with: 24 | command: check 25 | 26 | - name: Run cargo build 27 | uses: actions-rs/cargo@v1 28 | with: 29 | command: build 30 | 31 | - name: Run cargo test 32 | uses: actions-rs/cargo@v1 33 | with: 34 | command: test -------------------------------------------------------------------------------- /swagger-ui/examples/basic.rs: -------------------------------------------------------------------------------- 1 | use swagger_ui::{Assets, Config, Spec, DefaultModelRendering, DocExpansion, Filter, swagger_spec_file}; 2 | 3 | fn main() { 4 | println!("swagger-ui bundles files:"); 5 | // Use Assets::iter() to get iterator of all filenames 6 | for file in Assets::iter() { 7 | let filename = file.as_ref(); 8 | println!("\t{}", filename); 9 | // `Assets::get(filename)` returns file content 10 | }; 11 | 12 | // Load openapi spec (compile-time) 13 | let _spec: Spec = swagger_spec_file!("./openapi.json"); 14 | 15 | // swagger-ui configuration struct 16 | let _config: Config = Config { 17 | url: "".to_string(), 18 | urls: vec![], 19 | deep_linking: false, 20 | display_operation_id: false, 21 | default_models_expand_depth: 0, 22 | default_model_expand_depth: 0, 23 | default_model_rendering: DefaultModelRendering::Example, 24 | display_request_duration: false, 25 | doc_expansion: DocExpansion::List, 26 | filter: Filter::Bool(false), 27 | max_displayed_tags: 0, 28 | show_extensions: false, 29 | show_common_extensions: false 30 | }; 31 | } -------------------------------------------------------------------------------- /swagger-ui/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "swagger-ui" 3 | version = "0.1.5" 4 | authors = ["kotborealis "] 5 | edition = "2021" 6 | exclude = [ 7 | ".idea" 8 | ] 9 | description = "Swagger-ui for rust applications" 10 | license = "MIT" 11 | documentation = "https://docs.rs/swagger-ui/" 12 | repository = "https://github.com/kotborealis/swagger-ui" 13 | 14 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 15 | 16 | [features] 17 | default = [] 18 | rocket = ["rocket-swagger-ui"] 19 | # actix-web = ["actix-web-swagger-ui"] 20 | 21 | [dependencies] 22 | rust-embed = { version = "5.9.0", features = ["interpolate-folder-path"] } 23 | serde = { version = "1.0", features = ["derive"] } 24 | serde_json = "1.0.64" 25 | bytes = "1.5.0" 26 | 27 | rocket-swagger-ui = { version = "0.1", optional = true } 28 | # actix-web-swagger-ui = { version = "0.1", optional = true } 29 | 30 | [build-dependencies] 31 | reqwest = { version = "0.11.20", features = ["json", "stream", "rustls"] } 32 | futures = "0.3.28" 33 | futures-executor = "0.3.28" 34 | tokio = { version = "1.32.0", features = ["full"] } 35 | serde = { version = "1.0", features = ["derive"] } 36 | serde_json = "1.0" 37 | anyhow = "1.0.75" 38 | async-recursion = "1.0.5" -------------------------------------------------------------------------------- /swagger-ui/build.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | use anyhow::Context; 3 | use async_recursion::async_recursion; 4 | use serde::Deserialize; 5 | use futures::StreamExt; 6 | use reqwest::{Client, IntoUrl}; 7 | 8 | #[derive(Deserialize)] 9 | #[serde(rename_all="snake_case")] 10 | enum EntryType { 11 | File, 12 | Dir 13 | } 14 | 15 | #[derive(Deserialize)] 16 | struct FolderEntry { 17 | name: String, 18 | url: String, 19 | r#type: EntryType, 20 | download_url: Option 21 | } 22 | 23 | #[derive(Clone, Debug, Deserialize)] 24 | #[serde(untagged)] 25 | pub enum OneOrMany { 26 | /// Single value 27 | One(T), 28 | /// Array of values 29 | Vec(Vec), 30 | } 31 | impl From> for Vec { 32 | fn from(from: OneOrMany) -> Self { 33 | match from { 34 | OneOrMany::One(val) => vec![val], 35 | OneOrMany::Vec(vec) => vec, 36 | } 37 | } 38 | } 39 | 40 | #[tokio::main] 41 | async fn main() -> anyhow::Result<()> { 42 | let out_dir = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR")?).join(".dist"); 43 | download_folder("https://api.github.com/repos/swagger-api/swagger-ui/contents/dist", out_dir).await?; 44 | Ok(()) 45 | } 46 | 47 | #[async_recursion] 48 | async fn download_folder(url: &str, to: impl Into + Send + 'static) -> anyhow::Result<()> { 49 | let entries: Vec<_> = reqwest()?.get(url).send().await.with_context(||format!("failed to query folder data for {url}"))?.json::>().await.with_context(||format!("failed to parse json for {url}"))?.into(); 50 | let ref path = to.into(); 51 | futures::future::try_join_all(entries.into_iter().map(|entry| async move { 52 | match entry.r#type { 53 | EntryType::File => download_file(entry.download_url.unwrap(), path.clone().join(entry.name)).await, 54 | EntryType::Dir => download_folder(&entry.url, path.clone().join(entry.name)).await 55 | } 56 | })).await?; 57 | Ok(()) 58 | } 59 | 60 | async fn download_file(url: impl IntoUrl + Send, to: impl Into + Send) -> anyhow::Result<()> { 61 | let path = to.into(); 62 | if let Some(parent) = path.parent() { 63 | tokio::fs::create_dir_all(parent).await?; 64 | } 65 | let mut byte_stream = reqwest()?.get(url).send().await?.bytes_stream(); 66 | let mut tmp_file = tokio::fs::File::create(path).await?; 67 | while let Some(item) = byte_stream.next().await { 68 | tokio::io::copy(&mut item?.as_ref(), &mut tmp_file).await?; 69 | } 70 | Ok(()) 71 | } 72 | 73 | fn reqwest() -> anyhow::Result { 74 | Ok(Client::builder() 75 | .user_agent("reqwest") 76 | .build()?) 77 | } -------------------------------------------------------------------------------- /rocket-swagger-ui/src/handlers.rs: -------------------------------------------------------------------------------- 1 | use rocket::handler::{Handler, Outcome}; 2 | use rocket::http::{ContentType, Method}; 3 | use rocket::response::{Content, Responder, Redirect}; 4 | use rocket::{Data, Request, Route}; 5 | 6 | /// A content handler is a wrapper type around `rocket::response::Content`, which can be turned into 7 | /// a `rocket::Route` that serves the content with correct content-type. 8 | #[derive(Clone)] 9 | pub struct ContentHandler + Clone + Send + Sync + 'static> { 10 | content: Content, 11 | } 12 | 13 | impl ContentHandler { 14 | /// Create a `ContentHandler` which serves its content as JSON. 15 | pub fn json(content: &impl serde::Serialize) -> Self { 16 | let json = 17 | serde_json::to_string_pretty(content).expect("Could not serialize content as JSON."); 18 | ContentHandler { 19 | content: Content(ContentType::JSON, json), 20 | } 21 | } 22 | } 23 | 24 | impl ContentHandler> { 25 | /// Create a `ContentHandler>`, which serves its content with the specified 26 | /// `content_type`. 27 | pub fn bytes(content_type: ContentType, content: Vec) -> Self { 28 | ContentHandler { 29 | content: Content(content_type, content), 30 | } 31 | } 32 | } 33 | 34 | impl + Clone + Send + Sync + 'static> ContentHandler { 35 | /// Create a `rocket::Route` from the current `ContentHandler`. 36 | pub fn into_route(self, path: impl AsRef) -> Route { 37 | Route::new(Method::Get, path, self) 38 | } 39 | } 40 | 41 | impl + Clone + Send + Sync + 'static> Handler for ContentHandler { 42 | fn handle<'r>(&self, req: &'r Request, data: Data) -> Outcome<'r> { 43 | // match e.g. "/index.html" but not "/index.html/" 44 | if req.uri().path().ends_with('/') { 45 | Outcome::Forward(data) 46 | } else { 47 | Outcome::from(req, self.content.clone()) 48 | } 49 | } 50 | } 51 | 52 | /// A handler that instead of serving content always redirects to some specified destination URL. 53 | #[derive(Clone)] 54 | pub struct RedirectHandler { 55 | dest: &'static str, 56 | } 57 | 58 | impl RedirectHandler { 59 | /// Create a new `RedirectHandler` that redirects to the specified URL. 60 | pub fn to(dest: &'static str) -> Self { 61 | Self { 62 | dest: dest.trim_start_matches('/'), 63 | } 64 | } 65 | 66 | /// Create a new `Route` from this `Handler`. 67 | pub fn into_route(self, path: impl AsRef) -> Route { 68 | Route::new(Method::Get, path, self) 69 | } 70 | } 71 | 72 | impl Handler for RedirectHandler { 73 | fn handle<'r>(&self, req: &'r Request, _: Data) -> Outcome<'r> { 74 | let path = req.route().unwrap().base().trim_end_matches('/'); 75 | Outcome::from(req, Redirect::to(format!("{}/{}", path, self.dest))) 76 | } 77 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # swagger-ui 2 | 3 | Swagger-ui is a crate to use in rust web-servers to render 4 | OpenAPI specification, using [swagger-ui JS library](https://www.npmjs.com/package/swagger-ui). 5 | 6 | This crate embeds [swagger-ui-dist](https://www.npmjs.com/package/swagger-ui-dist) into your binary, using [rust-embed](https://crates.io/crates/rust-embed). 7 | 8 | It also provides bindings for [rocket](https://rocket.rs). 9 | 10 | ![swagger-ui petstore](https://github.com/kotborealis/swagger-ui/blob/master/doc/swagger-ui.png?raw=true) 11 | 12 | ## Usage 13 | 14 | ### Rocket 15 | 16 | Use this crate with rocket to serve `swagger-ui` for your OpenAPI specification. 17 | 18 | Use `rocket` feature in your `Cargo.toml`: 19 | ```toml 20 | swagger-ui = { version = "0.1", features = ["rocket"] } 21 | ``` 22 | 23 | Or install `rocket-swagger-ui`: 24 | ```toml 25 | swagger-ui = "0.1" 26 | rocket-swagger-ui = "0.1" 27 | ``` 28 | 29 | See [./rocket-swagger-ui/examples/basic.rs](./rocket-swagger-ui/examples/basic.rs) 30 | for a full example: 31 | 32 | ```rust 33 | #![feature(proc_macro_hygiene, decl_macro)] 34 | 35 | extern crate rocket; 36 | 37 | use rocket_swagger_ui; 38 | use swagger_ui; 39 | 40 | fn main() { 41 | rocket::ignite() 42 | .mount("/api/v1/swagger/", 43 | rocket_swagger_ui::routes( 44 | // Specify file with openapi specification, 45 | // relative to current file 46 | swagger_ui::swagger_spec_file!("./openapi.json"), 47 | swagger_ui::Config { ..Default::default() } 48 | ) 49 | ) 50 | .launch(); 51 | } 52 | ``` 53 | 54 | ### Standalone 55 | 56 | This library isn't really useful without webserver bindings. 57 | You can get files from `swagger-ui-dist` and create configuration 58 | for `swagger-ui`, which can be serialized to json via [serde](https://docs.rs/serde/). 59 | 60 | See [./swagger-ui/examples/basic.rs](./swagger-ui/examples/basic.rs) 61 | for a full example: 62 | 63 | ```rust 64 | use swagger_ui::{Assets, Config, Spec, DefaultModelRendering, DocExpansion, Filter, swagger_spec_file}; 65 | 66 | fn main() { 67 | println!("swagger-ui bundles files:"); 68 | // Use Assets::iter() to get iterator of all filenames 69 | for file in Assets::iter() { 70 | let filename = file.as_ref(); 71 | println!("\t{}", filename); 72 | // `Assets::get(filename)` returns file content 73 | }; 74 | 75 | // Load openapi spec (compile-time) 76 | let _spec: Spec = swagger_spec_file!("./openapi.json"); 77 | 78 | // swagger-ui configuration struct 79 | let _config: Config = Config { 80 | url: "".to_string(), 81 | urls: vec![], 82 | deep_linking: false, 83 | display_operation_id: false, 84 | default_models_expand_depth: 0, 85 | default_model_expand_depth: 0, 86 | default_model_rendering: DefaultModelRendering::Example, 87 | display_request_duration: false, 88 | doc_expansion: DocExpansion::List, 89 | filter: Filter::Bool(false), 90 | max_displayed_tags: 0, 91 | show_extensions: false, 92 | show_common_extensions: false 93 | }; 94 | } 95 | ``` -------------------------------------------------------------------------------- /swagger-ui/README.md: -------------------------------------------------------------------------------- 1 | # swagger-ui 2 | 3 | Swagger-ui is a crate to use in rust web-servers to render 4 | OpenAPI specification, using [swagger-ui JS library](https://www.npmjs.com/package/swagger-ui). 5 | 6 | This crate downloads [swagger-ui-dist](https://www.npmjs.com/package/swagger-ui-dist) from npm 7 | during build and 8 | embeds it into your binary, using [rust-embed](https://crates.io/crates/rust-embed). 9 | 10 | It also provides bindings for [rocket](https://rocket.rs). 11 | 12 | ![swagger-ui petstore](https://github.com/kotborealis/swagger-ui/blob/master/doc/swagger-ui.png?raw=true) 13 | 14 | ## Usage 15 | 16 | ### Rocket 17 | 18 | Use this crate with rocket to serve `swagger-ui` for your OpenAPI specification. 19 | 20 | Use `rocket` feature in your `Cargo.toml`: 21 | ```toml 22 | swagger-ui = { version = "0.1", features = ["rocket"] } 23 | ``` 24 | 25 | Or install `rocket-swagger-ui`: 26 | ```toml 27 | swagger-ui = "0.1" 28 | rocket-swagger-ui = "0.1" 29 | ``` 30 | 31 | See [rocket-swagger-ui/examples/basic.rs](../rocket-swagger-ui/examples/basic.rs) 32 | for a full example: 33 | 34 | ```rust 35 | #![feature(proc_macro_hygiene, decl_macro)] 36 | 37 | extern crate rocket; 38 | 39 | use rocket_swagger_ui; 40 | use swagger_ui; 41 | 42 | fn main() { 43 | rocket::ignite() 44 | .mount("/api/v1/swagger/", 45 | rocket_swagger_ui::routes( 46 | // Specify file with openapi specification, 47 | // relative to current file 48 | swagger_ui::swagger_spec_file!("./openapi.json"), 49 | swagger_ui::Config { ..Default::default() } 50 | ) 51 | ) 52 | .launch(); 53 | } 54 | ``` 55 | 56 | ### Standalone 57 | 58 | This library isn't really useful without webserver bindings. 59 | You can get files from `swagger-ui-dist` and create configuration 60 | for `swagger-ui`, which can be serialized to json via [serde](https://docs.rs/serde/). 61 | 62 | See [../swagger-ui/examples/basic.rs](../swagger-ui/examples/basic.rs) 63 | for a full example: 64 | 65 | ```rust 66 | use swagger_ui::{Assets, Config, Spec, DefaultModelRendering, DocExpansion, Filter, swagger_spec_file}; 67 | 68 | fn main() { 69 | println!("swagger-ui bundles files:"); 70 | // Use Assets::iter() to get iterator of all filenames 71 | for file in Assets::iter() { 72 | let filename = file.as_ref(); 73 | println!("\t{}", filename); 74 | // `Assets::get(filename)` returns file content 75 | }; 76 | 77 | // Load openapi spec (compile-time) 78 | let _spec: Spec = swagger_spec_file!("./openapi.json"); 79 | 80 | // swagger-ui configuration struct 81 | let _config: Config = Config { 82 | url: "".to_string(), 83 | urls: vec![], 84 | deep_linking: false, 85 | display_operation_id: false, 86 | default_models_expand_depth: 0, 87 | default_model_expand_depth: 0, 88 | default_model_rendering: DefaultModelRendering::Example, 89 | display_request_duration: false, 90 | doc_expansion: DocExpansion::List, 91 | filter: Filter::Bool(false), 92 | max_displayed_tags: 0, 93 | show_extensions: false, 94 | show_common_extensions: false 95 | }; 96 | } 97 | ``` -------------------------------------------------------------------------------- /rocket-swagger-ui/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod handlers; 2 | 3 | use rocket::http::{ContentType}; 4 | use rocket::{Route}; 5 | use crate::handlers::{ContentHandler, RedirectHandler}; 6 | use swagger_ui::{Assets, Config, Spec}; 7 | use std::path::Path; 8 | 9 | fn mime_type(filename: &str) -> ContentType { 10 | let parts = filename.split('.').collect::>(); 11 | match parts.last() { 12 | Some(v) => 13 | match *v { 14 | "html" => ContentType::HTML, 15 | "js" => ContentType::JavaScript, 16 | "png" => ContentType::PNG, 17 | "css" => ContentType::CSS, 18 | _ => ContentType::Plain 19 | }, 20 | _ => ContentType::Plain 21 | } 22 | } 23 | 24 | pub fn routes(spec: Spec, mut config: Config) -> Vec { 25 | let spec_handler = 26 | ContentHandler::bytes( 27 | mime_type(spec.name.as_ref()), 28 | spec.content.into(), 29 | ); 30 | 31 | let spec_name: &str = 32 | Path::new(spec.name.as_ref()) 33 | .file_name() 34 | .unwrap_or("openapi.json".as_ref()) 35 | .to_str() 36 | .unwrap_or("openapi.json".as_ref()); 37 | 38 | config.url = String::from(spec_name); 39 | 40 | let config_handler = ContentHandler::json(&config); 41 | 42 | let mut routes = vec![ 43 | config_handler.into_route(format!("/{}", "swagger-ui-config.json")), 44 | spec_handler.into_route(format!("/{}", spec_name)), 45 | RedirectHandler::to("index.html").into_route("/"), 46 | ]; 47 | 48 | for file in Assets::iter() { 49 | let filename = file.as_ref(); 50 | let mime_type = mime_type(filename); 51 | 52 | let content: Vec = Assets::get(filename).unwrap().into_owned(); 53 | 54 | let path = format!("/{}", filename); 55 | let handler = ContentHandler::bytes(mime_type, content); 56 | let route = handler.into_route(path); 57 | 58 | routes.push(route); 59 | }; 60 | 61 | routes 62 | } 63 | 64 | #[cfg(test)] 65 | mod tests { 66 | use rocket; 67 | use rocket::local::Client; 68 | use rocket::http::Status; 69 | 70 | fn ignite() -> rocket::Rocket { 71 | rocket::ignite() 72 | .mount("/api/v1/swagger/", 73 | super::routes( 74 | // Specify file with openapi specification, 75 | // relative to current file 76 | swagger_ui::swagger_spec_file!("../../swagger-ui/examples/openapi.json"), 77 | swagger_ui::Config { ..Default::default() }, 78 | ), 79 | ) 80 | } 81 | 82 | #[test] 83 | fn swagger_ui() { 84 | let client = Client::new(ignite()).expect("valid rocket instance"); 85 | 86 | let response = client.get("/api/v1/swagger").dispatch(); 87 | assert_eq!(response.status(), Status::SeeOther); 88 | 89 | let response = client.get("/api/v1/swagger/index.html").dispatch(); 90 | assert_eq!(response.status(), Status::Ok); 91 | 92 | let response = client.get("/api/v1/swagger/swagger-ui-config.json").dispatch(); 93 | assert_eq!(response.status(), Status::Ok); 94 | 95 | let mut response = client.get("/api/v1/swagger/openapi.json").dispatch(); 96 | assert_eq!(response.status(), Status::Ok); 97 | 98 | let path = env!("CARGO_MANIFEST_DIR").to_string() + "/../swagger-ui/examples/openapi.json"; 99 | 100 | println!("Loading {}", path); 101 | 102 | assert_eq!( 103 | response.body_string().unwrap(), 104 | String::from_utf8(std::fs::read(path).unwrap()).unwrap() 105 | ); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /axum-swagger-ui/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | use axum::extract::OriginalUri; 3 | use axum::http::{StatusCode, Uri}; 4 | use axum::response::{IntoResponse, Redirect, Response}; 5 | use axum::{Json, Router, TypedHeader}; 6 | use axum::headers::ContentType; 7 | use axum::routing::get; 8 | use swagger_ui::{Assets, Config, SpecOrUrl}; 9 | 10 | /// Helper trait to allow `route.swagger_ui_route(...)` 11 | pub trait SwaggerUiExt { 12 | fn swagger_ui(self, path: &str, spec: impl Into, config: impl Into>) -> Self; 13 | } 14 | 15 | impl SwaggerUiExt for Router { 16 | fn swagger_ui(self, path: &str, spec: impl Into, config: impl Into>) -> Self { 17 | self.nest(path, swagger_ui_route(spec, config)) 18 | } 19 | } 20 | 21 | /// creates a route that is configured to serve the specified spec and config with swagger_ui 22 | pub fn swagger_ui_route(spec: impl Into, config: impl Into>) -> Router { 23 | let config = Arc::new(config.into().unwrap_or_default()); 24 | let spec = Arc::new(spec.into()); 25 | Router::new() 26 | .route("/", get(redirect_index)) 27 | .route("/*path", 28 | get(move |uri: Uri, original: OriginalUri| { 29 | let config = config.clone(); 30 | let spec = spec.clone(); 31 | async move { 32 | handle_path(uri, original, &spec, &config).await 33 | } 34 | }), 35 | ) 36 | } 37 | 38 | async fn redirect_index(uri: OriginalUri) -> Redirect { 39 | let p = uri.path().trim_end_matches("/"); 40 | let query = uri.query(); 41 | Redirect::permanent(&if let Some(q) = query { 42 | format!("{p}/index.html?{q}") 43 | } else { 44 | format!("{p}/index.html") 45 | }) 46 | } 47 | 48 | fn mime_type(filename: &str) -> TypedHeader { 49 | TypedHeader(ContentType::from(mime_guess::from_ext(filename.split(".").last().unwrap_or_default()).first_or_octet_stream())) 50 | } 51 | 52 | async fn handle_path(uri: Uri, original: OriginalUri, spec: &SpecOrUrl, config: &Config) -> Response { 53 | let path = uri.path().trim_start_matches("/"); 54 | if let Some(asset) = Assets::get(path) { 55 | let t = mime_type(path); 56 | return (t, asset).into_response(); 57 | } 58 | if path == "swagger-ui-config.json" { 59 | let mut config = config.clone(); 60 | match spec { 61 | SpecOrUrl::Spec(spec) => config.url = original.path().replace("swagger-ui-config.json", &spec.name), 62 | SpecOrUrl::Url(url) => config.url = url.to_string() 63 | } 64 | return Json(config).into_response(); 65 | } 66 | if let SpecOrUrl::Spec(spec) = spec { 67 | if path == spec.name.trim_start_matches("/") { 68 | return (TypedHeader(ContentType::json()), spec.content.clone()).into_response(); 69 | } 70 | } 71 | StatusCode::NOT_FOUND.into_response() 72 | } 73 | 74 | #[cfg(test)] 75 | mod tests { 76 | use axum::body::Body; 77 | use axum::headers::ContentType; 78 | use axum::http::{Request, StatusCode}; 79 | use axum::http::header::CONTENT_TYPE; 80 | use axum::Router; 81 | use hyper::Method; 82 | use tower::Service; 83 | use tower::ServiceExt; 84 | use swagger_ui::Config; 85 | use crate::{swagger_ui_route}; 86 | 87 | fn app() -> Router { 88 | swagger_ui_route(swagger_ui::swagger_spec_file!("../../swagger-ui/examples/openapi.json"), None) 89 | } 90 | 91 | #[tokio::test] 92 | async fn does_redirect() { 93 | let app = app(); 94 | let response = app 95 | .oneshot(Request::builder().method(Method::GET).uri("/").body(Body::empty()).unwrap()) 96 | .await 97 | .unwrap(); 98 | 99 | assert_eq!(response.status(), StatusCode::PERMANENT_REDIRECT); 100 | } 101 | 102 | #[tokio::test] 103 | async fn does_index() { 104 | let app = app(); 105 | 106 | let response = app 107 | .oneshot(Request::builder().method(Method::GET).uri("/index.html").body(Body::empty()).unwrap()) 108 | .await 109 | .unwrap(); 110 | 111 | assert_eq!(response.status(), StatusCode::OK); 112 | let header: ContentType = response.headers().get(CONTENT_TYPE).unwrap().to_str().unwrap().parse().unwrap(); 113 | assert_eq!(header, ContentType::html()); 114 | let body = hyper::body::to_bytes(response.into_body()).await.unwrap(); 115 | } 116 | 117 | #[tokio::test] 118 | async fn does_config() { 119 | let app = app(); 120 | 121 | let response = app 122 | .oneshot(Request::builder().method(Method::GET).uri("/swagger-ui-config.json").body(Body::empty()).unwrap()) 123 | .await 124 | .unwrap(); 125 | 126 | assert_eq!(response.status(), StatusCode::OK); 127 | let header: ContentType = response.headers().get(CONTENT_TYPE).unwrap().to_str().unwrap().parse().unwrap(); 128 | assert_eq!(header, ContentType::json()); 129 | let body = hyper::body::to_bytes(response.into_body()).await.unwrap(); 130 | let config: Config = serde_json::from_str(std::str::from_utf8(body.as_ref()).unwrap()).unwrap(); 131 | } 132 | } -------------------------------------------------------------------------------- /actix-web-swagger-ui/src/lib.rs: -------------------------------------------------------------------------------- 1 | use actix_files::file_extension_to_mime; 2 | use actix_utils::future::{ok, ready}; 3 | use actix_web::http::header::{ContentType, LOCATION}; 4 | use actix_web::web::{self, ServiceConfig}; 5 | use actix_web::{HttpRequest, HttpResponse, Route}; 6 | 7 | use swagger_ui::{Assets, Config, Spec}; 8 | 9 | const CONFIG_FILE_PATH: &str = "/swagger-ui-config.json"; 10 | 11 | /// Returns a function which configures an `App` or a `Scope` to serve the swagger-ui page displaying the given `Spec` 12 | pub fn swagger(spec: Spec, config: Config) -> impl FnOnce(&mut ServiceConfig) { 13 | let mut routes: Vec<(String, Route)> = vec![]; 14 | 15 | let config_route = config_route(config, spec.name.clone().into_owned()); 16 | routes.push(("/swagger-ui-config.json".to_owned(), config_route)); 17 | 18 | let spec_path = spec.name.clone().into_owned(); 19 | let spec_route = spec_route(spec); 20 | routes.push((spec_path, spec_route)); 21 | 22 | let index_route = index_route(); 23 | routes.push(("".to_owned(), index_route)); 24 | 25 | for file in Assets::iter() { 26 | let filename = file.as_ref(); 27 | let content_type = content_type(filename); 28 | let content = Assets::get(filename).unwrap().into_owned(); 29 | 30 | routes.push((format!("/{}", filename), body(content_type, content))); 31 | } 32 | 33 | move |c| { 34 | for (path, route) in routes { 35 | c.route(path.as_str(), route); 36 | } 37 | } 38 | } 39 | 40 | fn config_route(config: Config, spec_name: String) -> Route { 41 | web::to(move |req: HttpRequest| { 42 | let path = req.path().replace(CONFIG_FILE_PATH, ""); 43 | let mut config = config.clone(); 44 | config.url = format!("{}/{}", path, &spec_name); 45 | 46 | ready(HttpResponse::Ok().json(config)) 47 | }) 48 | } 49 | 50 | fn spec_route(spec: Spec) -> Route { 51 | let content_type = content_type(&spec.name); 52 | let content = spec.content; 53 | 54 | web::to(move || { 55 | ready(HttpResponse::Ok() 56 | .content_type(content_type.clone()) 57 | .body(content.clone())) 58 | }) 59 | } 60 | 61 | fn index_route() -> Route { 62 | web::to(|req: HttpRequest| { 63 | let path = req.path(); 64 | 65 | let config_url = format!("{}{}", path, CONFIG_FILE_PATH); 66 | let index_url = format!("{}/index.html?configUrl={}", path, config_url); 67 | 68 | ready(HttpResponse::Found() 69 | .append_header((LOCATION, index_url)) 70 | .finish()) 71 | }) 72 | } 73 | 74 | fn body(content_type: ContentType, content: Vec) -> Route { 75 | let handler = move || { 76 | ok::<_, actix_web::Error>( 77 | HttpResponse::Ok() 78 | .content_type(content_type.clone()) 79 | .body(content.clone()), 80 | ) 81 | }; 82 | 83 | web::to(handler) 84 | } 85 | 86 | fn content_type(filename: impl AsRef) -> ContentType { 87 | let mime = file_extension_to_mime(extension(filename.as_ref())); 88 | 89 | ContentType(mime) 90 | } 91 | 92 | fn extension(filename: &str) -> &str { 93 | if let Some(dot_index) = filename.rfind('.') { 94 | &filename[dot_index + 1..] 95 | } else { 96 | "" 97 | } 98 | } 99 | 100 | #[cfg(test)] 101 | mod tests { 102 | use std::fs; 103 | use actix_http::Request; 104 | use actix_web::{ test::{TestRequest, call_service, init_service, read_body}, web::scope, App}; 105 | use actix_web::dev::ServiceResponse; 106 | use actix_web::web::Bytes; 107 | use swagger_ui::swagger_spec_file; 108 | 109 | use super::*; 110 | 111 | macro_rules! init_app { 112 | ($scope:expr) => {{ 113 | let spec = swagger_spec_file!("../../swagger-ui/examples/openapi.json"); 114 | let config = Config::default(); 115 | let app = App::new() 116 | .service(scope($scope).configure(swagger(spec, config))); 117 | init_service(app).await 118 | }}; 119 | } 120 | 121 | #[actix_rt::test] 122 | async fn extension_works() { 123 | assert_eq!("html", extension("index.html")); 124 | assert_eq!("js", extension("jquery.min.js")); 125 | assert_eq!("", extension("src")); 126 | } 127 | 128 | fn get(uri: impl AsRef) -> Request { 129 | TestRequest::with_uri(uri.as_ref()).to_request() 130 | } 131 | 132 | fn has_location(res: &ServiceResponse, expected_location: String) -> bool { 133 | let location = res.headers().get(LOCATION).unwrap(); 134 | 135 | location.eq(expected_location.as_str().into()) 136 | } 137 | 138 | #[actix_rt::test] 139 | async fn index_redirects_with_config_url_param() { 140 | let prefix = "/swagger-ui"; 141 | 142 | let mut app = init_app!(prefix); 143 | 144 | let res = call_service(&mut app, get(prefix)).await; 145 | assert!(res.status().is_redirection()); 146 | assert!(has_location(&res, format!("{0}/index.html?configUrl={0}/swagger-ui-config.json", prefix))); 147 | 148 | let res = call_service(&mut app, get(format!("{}/index.html", prefix))).await; 149 | assert!(res.status().is_success()); 150 | 151 | let res = call_service(&mut app, get(format!("{}/swagger-ui-config.json", prefix))).await; 152 | assert!(res.status().is_success()); 153 | 154 | let res = call_service(&mut app, get(format!("{}/openapi.json", prefix))).await; 155 | 156 | let path = env!("CARGO_MANIFEST_DIR").to_string() + "/../swagger-ui/examples/openapi.json"; 157 | println!("Loading {}", path); 158 | let expected_body = Bytes::from(fs::read(path).unwrap()); 159 | 160 | let body = read_body(res).await; 161 | 162 | assert_eq!(body, expected_body); 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /swagger-ui/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | use rust_embed::RustEmbed; 3 | use serde::{Deserialize, Serialize}; 4 | 5 | pub use bytes::Bytes; 6 | 7 | /// Assets from swagger-ui-dist 8 | #[derive(RustEmbed)] 9 | #[folder = "$CARGO_MANIFEST_DIR/.dist"] 10 | pub struct Assets; 11 | 12 | /// Contains a named url. 13 | #[derive(Debug, Clone, Deserialize, Serialize)] 14 | pub struct UrlObject { 15 | /// The name of the url. 16 | pub name: String, 17 | /// The url itself. 18 | pub url: String, 19 | } 20 | 21 | impl UrlObject { 22 | /// Create a new `UrlObject` from the provided name and url. 23 | pub fn new(name: &str, url: &str) -> Self { 24 | Self { 25 | name: name.to_string(), 26 | url: url.to_string(), 27 | } 28 | } 29 | } 30 | 31 | /// Used to control the way models are displayed by default. 32 | #[derive(Debug, Clone, Serialize, Deserialize)] 33 | #[serde(rename_all = "camelCase")] 34 | pub enum DefaultModelRendering { 35 | /// Expand the `example` section. 36 | Example, 37 | /// Expand the `model` section. 38 | Model, 39 | } 40 | 41 | /// Used to control the default expansion setting for the operations and tags. 42 | #[derive(Debug, Clone, Serialize, Deserialize)] 43 | #[serde(rename_all = "camelCase")] 44 | pub enum DocExpansion { 45 | /// Expands only the tags. 46 | List, 47 | /// Expands the tags and operations 48 | Full, 49 | /// Expands nothing 50 | None, 51 | } 52 | 53 | /// Used to enable, disable and preconfigure filtering 54 | #[derive(Debug, Clone, Serialize, Deserialize)] 55 | #[serde(untagged)] 56 | pub enum Filter { 57 | /// Use this variant to enable or disable filtering. 58 | Bool(bool), 59 | /// Use this variant to enable filtering, and preconfigure a filter. 60 | Str(String), 61 | } 62 | 63 | /// Used to represent openapi specification file 64 | #[derive(Debug, Clone)] 65 | pub struct Spec { 66 | /// Spec file name 67 | pub name: Cow<'static, str>, 68 | /// Spec file content 69 | pub content: Bytes 70 | } 71 | 72 | /// Helper type to accept both provided or existing spec 73 | #[derive(Debug, Clone)] 74 | pub enum SpecOrUrl { 75 | Spec(Spec), 76 | Url(Cow<'static, str>) 77 | } 78 | 79 | impl From for SpecOrUrl { 80 | fn from(value: Spec) -> Self { 81 | Self::Spec(value) 82 | } 83 | } 84 | 85 | impl From for SpecOrUrl { 86 | fn from(value: String) -> Self { 87 | Self::Url(value.into()) 88 | } 89 | } 90 | 91 | impl From<&'static str> for SpecOrUrl { 92 | fn from(value: &'static str) -> Self { 93 | Self::Url(Cow::Borrowed(value)) 94 | } 95 | } 96 | 97 | /// Macro used to create `Spec` struct, 98 | /// loads file using `include_bytes!` 99 | #[macro_export] 100 | macro_rules! swagger_spec_file { 101 | ($name: literal) => { 102 | $crate::Spec { 103 | name: std::borrow::Cow::Borrowed(($name).split("/").last().unwrap()), 104 | content: $crate::Bytes::from_static(include_bytes!($name)) 105 | } 106 | }; 107 | } 108 | 109 | /// Swagger UI configuration 110 | #[derive(Debug, Clone, Serialize, Deserialize)] 111 | #[serde(rename_all = "camelCase")] 112 | pub struct Config { 113 | /// The url to a single `openapi.json` file that is showed when the web ui is first opened. 114 | #[serde(default, skip_serializing_if = "String::is_empty")] 115 | pub url: String, 116 | /// A list of named urls that contain all the `openapi.json` files that you want to display in 117 | /// your web ui. If this field is populated, the `url` field is not used. 118 | #[serde(default, skip_serializing_if = "Vec::is_empty")] 119 | pub urls: Vec, 120 | 121 | // display options: 122 | /// If set to true, enables deep linking for tags and operations. See the 123 | /// [Deep Linking documentation](https://github.com/swagger-api/swagger-ui/blob/master/docs/usage/deep-linking.md) 124 | /// for more information. 125 | /// Default: `false`. 126 | pub deep_linking: bool, 127 | /// Controls the display of operationId in operations list. 128 | /// Default: `false`. 129 | pub display_operation_id: bool, 130 | /// The default expansion depth for models (set to -1 completely hide the models). 131 | /// Default: `1`. 132 | pub default_models_expand_depth: i32, 133 | /// The default expansion depth for the model on the model-example section. 134 | /// Default: `1`. 135 | pub default_model_expand_depth: i32, 136 | /// Controls how the model is shown when the API is first rendered. (The user can always switch 137 | /// the rendering for a given model by clicking the 'Model' and 'Example Value' links.) 138 | /// Default: `DefaultModelRendering::Example`. 139 | pub default_model_rendering: DefaultModelRendering, 140 | /// Controls the display of the request duration (in milliseconds) for "Try it out" requests. 141 | /// Default: `false`. 142 | pub display_request_duration: bool, 143 | /// Controls the default expansion setting for the operations and tags. 144 | /// Default: `DocExpansion::List`. 145 | pub doc_expansion: DocExpansion, 146 | /// If set, enables filtering. The top bar will show an edit box that you can use to filter the 147 | /// tagged operations that are shown. Filtering is case sensitive matching the filter expression 148 | /// anywhere inside the tag. 149 | /// Default: `Filter(false)`. 150 | pub filter: Filter, 151 | /// If set, limits the number of tagged operations displayed to at most this many. The default 152 | /// is to show all operations. 153 | /// Default: `None` (displays all tagged operations). 154 | #[serde(default, skip_serializing_if = "is_zero")] 155 | pub max_displayed_tags: u32, 156 | /// Controls the display of vendor extension (`x-`) fields and values for Operations, 157 | /// Parameters, and Schema. 158 | /// Default: `false`. 159 | pub show_extensions: bool, 160 | /// Controls the display of extensions (`pattern`, `maxLength`, `minLength`, `maximum`, 161 | /// `minimum`) fields and values for Parameters. 162 | /// Default: `false`. 163 | pub show_common_extensions: bool, 164 | } 165 | 166 | fn is_zero(num: &u32) -> bool { 167 | *num == 0 168 | } 169 | 170 | impl Default for Config { 171 | fn default() -> Self { 172 | Self { 173 | url: String::new(), 174 | urls: vec![], 175 | deep_linking: false, 176 | display_operation_id: false, 177 | default_model_expand_depth: 1, 178 | default_model_rendering: DefaultModelRendering::Example, 179 | default_models_expand_depth: 1, 180 | display_request_duration: false, 181 | doc_expansion: DocExpansion::List, 182 | filter: Filter::Bool(false), 183 | max_displayed_tags: 0, 184 | show_extensions: false, 185 | show_common_extensions: false, 186 | } 187 | } 188 | } 189 | 190 | #[cfg(test)] 191 | mod tests { 192 | use std::path::Path; 193 | use crate::Assets; 194 | 195 | fn asset_list() -> [&'static str; 8] { 196 | [ 197 | "favicon-16x16.png", 198 | "favicon-32x32.png", 199 | "index.html", 200 | "oauth2-redirect.html", 201 | "swagger-ui.css", 202 | "swagger-ui.js", 203 | "swagger-ui-bundle.js", 204 | "swagger-ui-standalone-preset.js", 205 | ] 206 | } 207 | 208 | #[test] 209 | fn swagger_ui_assets() { 210 | println!("Checking if assets exists in binary"); 211 | for asset in &asset_list() { 212 | println!("\t{}", asset); 213 | let data = Assets::get(&asset).unwrap(); 214 | assert!(!data.is_empty()); 215 | } 216 | } 217 | 218 | #[test] 219 | fn swagger_ui_macro_name() { 220 | let spec = swagger_spec_file!("../examples/openapi.json"); 221 | assert_eq!(&spec.name, "openapi.json") 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /swagger-ui/examples/openapi.json: -------------------------------------------------------------------------------- 1 | {"swagger":"2.0","info":{"description":"This is a sample server Petstore server. You can find out more about Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). For this sample, you can use the api key `special-key` to test the authorization filters.","version":"1.0.5","title":"Swagger Petstore","termsOfService":"http://swagger.io/terms/","contact":{"email":"apiteam@swagger.io"},"license":{"name":"Apache 2.0","url":"http://www.apache.org/licenses/LICENSE-2.0.html"}},"host":"petstore.swagger.io","basePath":"/v2","tags":[{"name":"pet","description":"Everything about your Pets","externalDocs":{"description":"Find out more","url":"http://swagger.io"}},{"name":"store","description":"Access to Petstore orders"},{"name":"user","description":"Operations about user","externalDocs":{"description":"Find out more about our store","url":"http://swagger.io"}}],"schemes":["https","http"],"paths":{"/pet/{petId}/uploadImage":{"post":{"tags":["pet"],"summary":"uploads an image","description":"","operationId":"uploadFile","consumes":["multipart/form-data"],"produces":["application/json"],"parameters":[{"name":"petId","in":"path","description":"ID of pet to update","required":true,"type":"integer","format":"int64"},{"name":"additionalMetadata","in":"formData","description":"Additional data to pass to server","required":false,"type":"string"},{"name":"file","in":"formData","description":"file to upload","required":false,"type":"file"}],"responses":{"200":{"description":"successful operation","schema":{"$ref":"#/definitions/ApiResponse"}}},"security":[{"petstore_auth":["write:pets","read:pets"]}]}},"/pet":{"post":{"tags":["pet"],"summary":"Add a new pet to the store","description":"","operationId":"addPet","consumes":["application/json","application/xml"],"produces":["application/json","application/xml"],"parameters":[{"in":"body","name":"body","description":"Pet object that needs to be added to the store","required":true,"schema":{"$ref":"#/definitions/Pet"}}],"responses":{"405":{"description":"Invalid input"}},"security":[{"petstore_auth":["write:pets","read:pets"]}]},"put":{"tags":["pet"],"summary":"Update an existing pet","description":"","operationId":"updatePet","consumes":["application/json","application/xml"],"produces":["application/json","application/xml"],"parameters":[{"in":"body","name":"body","description":"Pet object that needs to be added to the store","required":true,"schema":{"$ref":"#/definitions/Pet"}}],"responses":{"400":{"description":"Invalid ID supplied"},"404":{"description":"Pet not found"},"405":{"description":"Validation exception"}},"security":[{"petstore_auth":["write:pets","read:pets"]}]}},"/pet/findByStatus":{"get":{"tags":["pet"],"summary":"Finds Pets by status","description":"Multiple status values can be provided with comma separated strings","operationId":"findPetsByStatus","produces":["application/json","application/xml"],"parameters":[{"name":"status","in":"query","description":"Status values that need to be considered for filter","required":true,"type":"array","items":{"type":"string","enum":["available","pending","sold"],"default":"available"},"collectionFormat":"multi"}],"responses":{"200":{"description":"successful operation","schema":{"type":"array","items":{"$ref":"#/definitions/Pet"}}},"400":{"description":"Invalid status value"}},"security":[{"petstore_auth":["write:pets","read:pets"]}]}},"/pet/findByTags":{"get":{"tags":["pet"],"summary":"Finds Pets by tags","description":"Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.","operationId":"findPetsByTags","produces":["application/json","application/xml"],"parameters":[{"name":"tags","in":"query","description":"Tags to filter by","required":true,"type":"array","items":{"type":"string"},"collectionFormat":"multi"}],"responses":{"200":{"description":"successful operation","schema":{"type":"array","items":{"$ref":"#/definitions/Pet"}}},"400":{"description":"Invalid tag value"}},"security":[{"petstore_auth":["write:pets","read:pets"]}],"deprecated":true}},"/pet/{petId}":{"get":{"tags":["pet"],"summary":"Find pet by ID","description":"Returns a single pet","operationId":"getPetById","produces":["application/json","application/xml"],"parameters":[{"name":"petId","in":"path","description":"ID of pet to return","required":true,"type":"integer","format":"int64"}],"responses":{"200":{"description":"successful operation","schema":{"$ref":"#/definitions/Pet"}},"400":{"description":"Invalid ID supplied"},"404":{"description":"Pet not found"}},"security":[{"api_key":[]}]},"post":{"tags":["pet"],"summary":"Updates a pet in the store with form data","description":"","operationId":"updatePetWithForm","consumes":["application/x-www-form-urlencoded"],"produces":["application/json","application/xml"],"parameters":[{"name":"petId","in":"path","description":"ID of pet that needs to be updated","required":true,"type":"integer","format":"int64"},{"name":"name","in":"formData","description":"Updated name of the pet","required":false,"type":"string"},{"name":"status","in":"formData","description":"Updated status of the pet","required":false,"type":"string"}],"responses":{"405":{"description":"Invalid input"}},"security":[{"petstore_auth":["write:pets","read:pets"]}]},"delete":{"tags":["pet"],"summary":"Deletes a pet","description":"","operationId":"deletePet","produces":["application/json","application/xml"],"parameters":[{"name":"api_key","in":"header","required":false,"type":"string"},{"name":"petId","in":"path","description":"Pet id to delete","required":true,"type":"integer","format":"int64"}],"responses":{"400":{"description":"Invalid ID supplied"},"404":{"description":"Pet not found"}},"security":[{"petstore_auth":["write:pets","read:pets"]}]}},"/store/order":{"post":{"tags":["store"],"summary":"Place an order for a pet","description":"","operationId":"placeOrder","consumes":["application/json"],"produces":["application/json","application/xml"],"parameters":[{"in":"body","name":"body","description":"order placed for purchasing the pet","required":true,"schema":{"$ref":"#/definitions/Order"}}],"responses":{"200":{"description":"successful operation","schema":{"$ref":"#/definitions/Order"}},"400":{"description":"Invalid Order"}}}},"/store/order/{orderId}":{"get":{"tags":["store"],"summary":"Find purchase order by ID","description":"For valid response try integer IDs with value >= 1 and <= 10. Other values will generated exceptions","operationId":"getOrderById","produces":["application/json","application/xml"],"parameters":[{"name":"orderId","in":"path","description":"ID of pet that needs to be fetched","required":true,"type":"integer","maximum":10,"minimum":1,"format":"int64"}],"responses":{"200":{"description":"successful operation","schema":{"$ref":"#/definitions/Order"}},"400":{"description":"Invalid ID supplied"},"404":{"description":"Order not found"}}},"delete":{"tags":["store"],"summary":"Delete purchase order by ID","description":"For valid response try integer IDs with positive integer value. Negative or non-integer values will generate API errors","operationId":"deleteOrder","produces":["application/json","application/xml"],"parameters":[{"name":"orderId","in":"path","description":"ID of the order that needs to be deleted","required":true,"type":"integer","minimum":1,"format":"int64"}],"responses":{"400":{"description":"Invalid ID supplied"},"404":{"description":"Order not found"}}}},"/store/inventory":{"get":{"tags":["store"],"summary":"Returns pet inventories by status","description":"Returns a map of status codes to quantities","operationId":"getInventory","produces":["application/json"],"parameters":[],"responses":{"200":{"description":"successful operation","schema":{"type":"object","additionalProperties":{"type":"integer","format":"int32"}}}},"security":[{"api_key":[]}]}},"/user/createWithArray":{"post":{"tags":["user"],"summary":"Creates list of users with given input array","description":"","operationId":"createUsersWithArrayInput","consumes":["application/json"],"produces":["application/json","application/xml"],"parameters":[{"in":"body","name":"body","description":"List of user object","required":true,"schema":{"type":"array","items":{"$ref":"#/definitions/User"}}}],"responses":{"default":{"description":"successful operation"}}}},"/user/createWithList":{"post":{"tags":["user"],"summary":"Creates list of users with given input array","description":"","operationId":"createUsersWithListInput","consumes":["application/json"],"produces":["application/json","application/xml"],"parameters":[{"in":"body","name":"body","description":"List of user object","required":true,"schema":{"type":"array","items":{"$ref":"#/definitions/User"}}}],"responses":{"default":{"description":"successful operation"}}}},"/user/{username}":{"get":{"tags":["user"],"summary":"Get user by user name","description":"","operationId":"getUserByName","produces":["application/json","application/xml"],"parameters":[{"name":"username","in":"path","description":"The name that needs to be fetched. Use user1 for testing. ","required":true,"type":"string"}],"responses":{"200":{"description":"successful operation","schema":{"$ref":"#/definitions/User"}},"400":{"description":"Invalid username supplied"},"404":{"description":"User not found"}}},"put":{"tags":["user"],"summary":"Updated user","description":"This can only be done by the logged in user.","operationId":"updateUser","consumes":["application/json"],"produces":["application/json","application/xml"],"parameters":[{"name":"username","in":"path","description":"name that need to be updated","required":true,"type":"string"},{"in":"body","name":"body","description":"Updated user object","required":true,"schema":{"$ref":"#/definitions/User"}}],"responses":{"400":{"description":"Invalid user supplied"},"404":{"description":"User not found"}}},"delete":{"tags":["user"],"summary":"Delete user","description":"This can only be done by the logged in user.","operationId":"deleteUser","produces":["application/json","application/xml"],"parameters":[{"name":"username","in":"path","description":"The name that needs to be deleted","required":true,"type":"string"}],"responses":{"400":{"description":"Invalid username supplied"},"404":{"description":"User not found"}}}},"/user/login":{"get":{"tags":["user"],"summary":"Logs user into the system","description":"","operationId":"loginUser","produces":["application/json","application/xml"],"parameters":[{"name":"username","in":"query","description":"The user name for login","required":true,"type":"string"},{"name":"password","in":"query","description":"The password for login in clear text","required":true,"type":"string"}],"responses":{"200":{"description":"successful operation","headers":{"X-Expires-After":{"type":"string","format":"date-time","description":"date in UTC when token expires"},"X-Rate-Limit":{"type":"integer","format":"int32","description":"calls per hour allowed by the user"}},"schema":{"type":"string"}},"400":{"description":"Invalid username/password supplied"}}}},"/user/logout":{"get":{"tags":["user"],"summary":"Logs out current logged in user session","description":"","operationId":"logoutUser","produces":["application/json","application/xml"],"parameters":[],"responses":{"default":{"description":"successful operation"}}}},"/user":{"post":{"tags":["user"],"summary":"Create user","description":"This can only be done by the logged in user.","operationId":"createUser","consumes":["application/json"],"produces":["application/json","application/xml"],"parameters":[{"in":"body","name":"body","description":"Created user object","required":true,"schema":{"$ref":"#/definitions/User"}}],"responses":{"default":{"description":"successful operation"}}}}},"securityDefinitions":{"api_key":{"type":"apiKey","name":"api_key","in":"header"},"petstore_auth":{"type":"oauth2","authorizationUrl":"https://petstore.swagger.io/oauth/authorize","flow":"implicit","scopes":{"read:pets":"read your pets","write:pets":"modify pets in your account"}}},"definitions":{"ApiResponse":{"type":"object","properties":{"code":{"type":"integer","format":"int32"},"type":{"type":"string"},"message":{"type":"string"}}},"Category":{"type":"object","properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"}},"xml":{"name":"Category"}},"Pet":{"type":"object","required":["name","photoUrls"],"properties":{"id":{"type":"integer","format":"int64"},"category":{"$ref":"#/definitions/Category"},"name":{"type":"string","example":"doggie"},"photoUrls":{"type":"array","xml":{"wrapped":true},"items":{"type":"string","xml":{"name":"photoUrl"}}},"tags":{"type":"array","xml":{"wrapped":true},"items":{"xml":{"name":"tag"},"$ref":"#/definitions/Tag"}},"status":{"type":"string","description":"pet status in the store","enum":["available","pending","sold"]}},"xml":{"name":"Pet"}},"Tag":{"type":"object","properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"}},"xml":{"name":"Tag"}},"Order":{"type":"object","properties":{"id":{"type":"integer","format":"int64"},"petId":{"type":"integer","format":"int64"},"quantity":{"type":"integer","format":"int32"},"shipDate":{"type":"string","format":"date-time"},"status":{"type":"string","description":"Order Status","enum":["placed","approved","delivered"]},"complete":{"type":"boolean"}},"xml":{"name":"Order"}},"User":{"type":"object","properties":{"id":{"type":"integer","format":"int64"},"username":{"type":"string"},"firstName":{"type":"string"},"lastName":{"type":"string"},"email":{"type":"string"},"password":{"type":"string"},"phone":{"type":"string"},"userStatus":{"type":"integer","format":"int32","description":"User Status"}},"xml":{"name":"User"}}},"externalDocs":{"description":"Find out more about Swagger","url":"http://swagger.io"}} --------------------------------------------------------------------------------