From fddb60946a458913bfeaea2e7e9fedd2fb640b47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johan=20Bj=C3=A4reholt?= Date: Mon, 10 Aug 2020 20:32:34 +0200 Subject: [PATCH] aw-server: Implement swagger for some endpoints --- Cargo.lock | 89 +++++++++++++++++++++++++++++++ aw-client-rust/src/lib.rs | 3 +- aw-server/Cargo.toml | 3 ++ aw-server/src/endpoints/bucket.rs | 22 ++++++-- aw-server/src/endpoints/export.rs | 11 ++-- aw-server/src/endpoints/import.rs | 3 +- aw-server/src/endpoints/mod.rs | 28 ++++++++-- aw-server/src/endpoints/util.rs | 20 ++++++- 8 files changed, 161 insertions(+), 18 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7c92cfce..f8cca1e8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -191,10 +191,13 @@ dependencies = [ "libc", "log 0.4.11", "multipart", + "okapi", "openssl-sys", "rocket", "rocket_contrib", "rocket_cors", + "rocket_okapi", + "schemars", "serde", "serde_json", "toml 0.5.6", @@ -455,6 +458,41 @@ dependencies = [ "subtle 1.0.0", ] +[[package]] +name = "darling" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d706e75d87e35569db781a9b5e2416cff1236a47ed380831f959382ccd5f858" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0c960ae2da4de88a91b2d920c2a7233b400bc33cb28453a2987822d8392519b" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2 1.0.18", + "quote 1.0.7", + "strsim", + "syn 1.0.33", +] + +[[package]] +name = "darling_macro" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72" +dependencies = [ + "darling_core", + "quote 1.0.7", + "syn 1.0.33", +] + [[package]] name = "devise" version = "0.2.0" @@ -887,6 +925,12 @@ dependencies = [ "tokio-tls", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "0.1.5" @@ -1293,6 +1337,17 @@ dependencies = [ "libc", ] +[[package]] +name = "okapi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e969ac53c86c158761836e746be203f4cfd774445bd8aff4bfdcf8e42dd93891" +dependencies = [ + "schemars", + "serde", + "serde_json", +] + [[package]] name = "ole32-sys" version = "0.2.0" @@ -1818,6 +1873,34 @@ dependencies = [ "unicode-xid 0.1.0", ] +[[package]] +name = "rocket_okapi" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf633b45fd1c03ed4e4da824d9fafbecab8af298e46716ab0cf446fdf36c58b2" +dependencies = [ + "okapi", + "rocket", + "rocket_contrib", + "rocket_okapi_codegen", + "schemars", + "serde", + "serde_json", +] + +[[package]] +name = "rocket_okapi_codegen" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b630c92ffa097a6425262547732e6bc19d80a24628d209126bd52c65acd96490" +dependencies = [ + "darling", + "proc-macro2 1.0.18", + "quote 1.0.7", + "rocket_http", + "syn 1.0.33", +] + [[package]] name = "rusqlite" version = "0.23.1" @@ -2050,6 +2133,12 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7345c971d1ef21ffdbd103a75990a15eb03604fc8b8852ca8cb418ee1a099028" +[[package]] +name = "strsim" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c" + [[package]] name = "subtle" version = "1.0.0" diff --git a/aw-client-rust/src/lib.rs b/aw-client-rust/src/lib.rs index d3f9fc91..802381e1 100644 --- a/aw-client-rust/src/lib.rs +++ b/aw-client-rust/src/lib.rs @@ -1,7 +1,6 @@ +extern crate aw_models; extern crate gethostname; extern crate reqwest; -#[macro_use] -extern crate aw_models; extern crate serde_json; use std::collections::HashMap; diff --git a/aw-server/Cargo.toml b/aw-server/Cargo.toml index 08aff1cc..dc4af380 100644 --- a/aw-server/Cargo.toml +++ b/aw-server/Cargo.toml @@ -29,6 +29,9 @@ toml = "0.5" gethostname = "0.2" uuid = { version = "0.8", features = ["serde", "v4"] } getopts = "0.2" +rocket_okapi = "0.5" +schemars = "0.7" +okapi = { version = "0.4", features = ["derive_json_schema"] } aw-datastore = { path = "../aw-datastore" } aw-models = { path = "../aw-models" } diff --git a/aw-server/src/endpoints/bucket.rs b/aw-server/src/endpoints/bucket.rs index 04ae46b4..3326c878 100644 --- a/aw-server/src/endpoints/bucket.rs +++ b/aw-server/src/endpoints/bucket.rs @@ -1,5 +1,4 @@ use std::collections::HashMap; -use std::io::Cursor; use rocket_contrib::json::Json; @@ -10,13 +9,13 @@ use aw_models::Bucket; use aw_models::BucketsExport; use aw_models::Event; -use rocket::http::Header; use rocket::http::Status; -use rocket::response::Response; use rocket::State; +use rocket_okapi::openapi; use crate::endpoints::{HttpErrorJson, ServerState}; +#[openapi] #[get("/")] pub fn buckets_get( state: State, @@ -28,6 +27,7 @@ pub fn buckets_get( } } +#[openapi] #[get("/")] pub fn bucket_get( bucket_id: String, @@ -40,6 +40,7 @@ pub fn bucket_get( } } +#[openapi] #[post("/", data = "", format = "application/json")] pub fn bucket_new( bucket_id: String, @@ -58,6 +59,7 @@ pub fn bucket_new( } } +#[openapi] #[get("//events?&&")] pub fn bucket_events_get( bucket_id: String, @@ -102,6 +104,7 @@ pub fn bucket_events_get( } } +#[openapi] #[post("//events", data = "", format = "application/json")] pub fn bucket_events_create( bucket_id: String, @@ -116,6 +119,7 @@ pub fn bucket_events_create( } } +#[openapi] #[post( "//heartbeat?", data = "", @@ -135,6 +139,7 @@ pub fn bucket_events_heartbeat( } } +#[openapi] #[get("//events/count")] pub fn bucket_event_count( bucket_id: String, @@ -148,6 +153,7 @@ pub fn bucket_event_count( } } +#[openapi] #[delete("//events/")] pub fn bucket_events_delete_by_id( bucket_id: String, @@ -161,11 +167,12 @@ pub fn bucket_events_delete_by_id( } } +#[openapi] #[get("//export")] pub fn bucket_export( bucket_id: String, state: State, -) -> Result { +) -> Result, HttpErrorJson> { let datastore = endpoints_get_lock!(state.datastore); let mut export = BucketsExport { buckets: HashMap::new(), @@ -180,8 +187,10 @@ pub fn bucket_export( .expect("Failed to get events for bucket"), ); export.buckets.insert(bucket_id.clone(), bucket); - let filename = format!("aw-bucket-export_{}.json", bucket_id); + // TODO: Add back Content-Disposition + /* + let filename = format!("aw-bucket-export_{}.json", bucket_id); let header_content = format!("attachment; filename={}", filename); Ok(Response::build() .status(Status::Ok) @@ -190,8 +199,11 @@ pub fn bucket_export( serde_json::to_string(&export).expect("Failed to serialize"), )) .finalize()) + */ + Ok(Json(export)) } +#[openapi] #[delete("/")] pub fn bucket_delete(bucket_id: String, state: State) -> Result<(), HttpErrorJson> { let datastore = endpoints_get_lock!(state.datastore); diff --git a/aw-server/src/endpoints/export.rs b/aw-server/src/endpoints/export.rs index 5b562d7d..b769bed6 100644 --- a/aw-server/src/endpoints/export.rs +++ b/aw-server/src/endpoints/export.rs @@ -1,17 +1,17 @@ use std::collections::HashMap; -use std::io::Cursor; -use rocket::http::Header; use rocket::http::Status; -use rocket::response::Response; use rocket::State; +use rocket_contrib::json::Json; +use rocket_okapi::openapi; use aw_models::BucketsExport; use crate::endpoints::{HttpErrorJson, ServerState}; +#[openapi] #[get("/")] -pub fn buckets_export(state: State) -> Result { +pub fn buckets_export(state: State) -> Result, HttpErrorJson> { let datastore = endpoints_get_lock!(state.datastore); let mut export = BucketsExport { buckets: HashMap::new(), @@ -28,6 +28,7 @@ pub fn buckets_export(state: State) -> Result) -> Result) -> Option { NamedFile::open(state.asset_path.join("favicon.ico")).ok() } +#[openapi] #[get("/")] fn server_info(config: State, state: State) -> Json { #[allow(clippy::or_fun_call)] @@ -72,6 +76,20 @@ fn server_info(config: State, state: State) -> Json }) } +fn get_docs() -> SwaggerUIConfig { + use rocket_okapi::swagger_ui::UrlObject; + + SwaggerUIConfig { + url: "/info/openapi.json".to_string(), + urls: vec![ + UrlObject::new("Info", "/api/0/info/openapi.json"), + UrlObject::new("Bucket", "/api/0/buckets/openapi.json"), + UrlObject::new("Export", "/api/0/export/openapi.json"), + ], + ..Default::default() + } +} + pub fn build_rocket(server_state: ServerState, config: AWConfig) -> rocket::Rocket { info!( "Starting aw-server-rust at {}:{}", @@ -89,10 +107,10 @@ pub fn build_rocket(server_state: ServerState, config: AWConfig) -> rocket::Rock root_static, ], ) - .mount("/api/0/info", routes![server_info]) + .mount("/api/0/info", routes_with_openapi![server_info]) .mount( "/api/0/buckets", - routes![ + routes_with_openapi![ bucket::bucket_new, bucket::bucket_delete, bucket::buckets_get, @@ -110,7 +128,10 @@ pub fn build_rocket(server_state: ServerState, config: AWConfig) -> rocket::Rock "/api/0/import", routes![import::bucket_import_json, import::bucket_import_form], ) - .mount("/api/0/export", routes![export::buckets_export]) + .mount( + "/api/0/export", + routes_with_openapi![export::buckets_export], + ) .mount( "/api/0/settings", routes![ @@ -120,6 +141,7 @@ pub fn build_rocket(server_state: ServerState, config: AWConfig) -> rocket::Rock settings::setting_delete ], ) + .mount("/api", make_swagger_ui(&get_docs())) .attach(cors::cors(&config)) .manage(server_state) .manage(config) diff --git a/aw-server/src/endpoints/util.rs b/aw-server/src/endpoints/util.rs index 3680db1e..fd401504 100644 --- a/aw-server/src/endpoints/util.rs +++ b/aw-server/src/endpoints/util.rs @@ -4,11 +4,13 @@ use rocket::http::ContentType; use rocket::http::Status; use rocket::request::Request; use rocket::response::{self, Responder, Response}; +use rocket_okapi::JsonSchema; use serde::Serialize; -#[derive(Serialize, Debug)] +#[derive(Serialize, JsonSchema, Debug)] pub struct HttpErrorJson { - #[serde(skip_serializing)] + #[serde(skip)] + #[schemars(skip)] status: Status, message: String, } @@ -34,6 +36,20 @@ impl<'r> Responder<'r> for HttpErrorJson { } } +use std::collections::BTreeMap; + +impl<'r> rocket_okapi::response::OpenApiResponder<'r> for HttpErrorJson { + fn responses( + _gen: &mut rocket_okapi::gen::OpenApiGenerator, + ) -> Result { + Ok(okapi::openapi3::Responses { + default: None, + responses: BTreeMap::new(), + extensions: BTreeMap::new(), + }) + } +} + use aw_datastore::DatastoreError; impl Into for DatastoreError {