From 8bc23434abee4a3e48e4e5f71eeb21ae58d15006 Mon Sep 17 00:00:00 2001 From: Mark Sinclair Date: Wed, 15 Dec 2021 18:06:03 +0000 Subject: [PATCH] Network Explorer API improvements: - upgrade `okapi` for swagger generation across multiple resources - switched `GET mix-node` to `GET mix-nodes` - added error message when no geolocation env var is set and process continues --- Cargo.lock | 15 +++-- explorer-api/Cargo.toml | 4 +- .../src/country_statistics/geolocate.rs | 7 ++ explorer-api/src/country_statistics/http.rs | 7 +- explorer-api/src/http/mod.rs | 65 +++++++++++-------- explorer-api/src/http/swagger.rs | 8 +-- explorer-api/src/main.rs | 5 ++ explorer-api/src/mix_node/http.rs | 18 ++--- explorer-api/src/mix_nodes/http.rs | 20 ++++++ explorer-api/src/mix_nodes/mod.rs | 1 + explorer-api/src/ping/http.rs | 7 +- 11 files changed, 100 insertions(+), 57 deletions(-) create mode 100644 explorer-api/src/mix_nodes/http.rs diff --git a/Cargo.lock b/Cargo.lock index 96c44ed4bb..f6749181ab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3972,10 +3972,11 @@ dependencies = [ [[package]] name = "okapi" -version = "0.6.0-alpha-1" +version = "0.7.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb085e00daf8d75b9dbf0ffdb4738e69503e28898d9641fa8bdc6ad536c7bcf4" +checksum = "ce66b6366e049880a35c378123fddb630b1a1a3c37fa1ca70caaf4a09f6e2893" dependencies = [ + "log", "schemars", "serde", "serde_json", @@ -5212,10 +5213,12 @@ dependencies = [ [[package]] name = "rocket_okapi" -version = "0.7.0-alpha-1" +version = "0.8.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b2f4f48fb070f9f6c56d5663df5fa8a514406207744f4abd84661bfb24efd7d" +checksum = "0025aa04994af8cd8e1fcdd5a73579a395c941ae090ecb0a39b41cca7e237a20" dependencies = [ + "either", + "log", "okapi", "rocket", "rocket_okapi_codegen", @@ -5226,9 +5229,9 @@ dependencies = [ [[package]] name = "rocket_okapi_codegen" -version = "0.7.0-alpha-1" +version = "0.8.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88ccf1550e1c806461a6b08e2ab64eb10701d41bf50bde59ab9aa3a57ab14d41" +checksum = "dc114779fc27afb78179233e966f469e47fd7a98dc15181cff2574cdddb65612" dependencies = [ "darling 0.13.0", "proc-macro2", diff --git a/explorer-api/Cargo.toml b/explorer-api/Cargo.toml index f9c043bae4..3719d89d7a 100644 --- a/explorer-api/Cargo.toml +++ b/explorer-api/Cargo.toml @@ -16,8 +16,8 @@ serde_json = "1.0.66" tokio = {version = "1.9.0", features = ["full"] } chrono = { version = "0.4.19", features = ["serde"] } schemars = { version = "0.8", features = ["preserve_order"] } -okapi = { version = "0.6.0-alpha-1", features = ["derive_json_schema"] } -rocket_okapi = "0.7.0-alpha-1" +okapi = { version = "0.7.0-rc.1", features = ["impl_json_schema"] } +rocket_okapi = { version = "0.8.0-rc.1", features = ["swagger"] } log = "0.4.0" pretty_env_logger = "0.4.0" thiserror = "1.0.29" diff --git a/explorer-api/src/country_statistics/geolocate.rs b/explorer-api/src/country_statistics/geolocate.rs index f03fd8d365..a09231ceae 100644 --- a/explorer-api/src/country_statistics/geolocate.rs +++ b/explorer-api/src/country_statistics/geolocate.rs @@ -15,6 +15,13 @@ impl GeoLocateTask { } pub(crate) fn start(mut self) { + if ::std::env::var("GEO_IP_SERVICE_API_KEY").is_err() { + error!( + "Env var GEO_IP_SERVICE_API_KEY is not set. Geolocation tasks will not be started." + ); + return; + } + info!("Spawning mix node locator task runner..."); tokio::spawn(async move { let mut interval_timer = tokio::time::interval(std::time::Duration::from_millis(50)); diff --git a/explorer-api/src/country_statistics/http.rs b/explorer-api/src/country_statistics/http.rs index 74be62cb13..45259f11dc 100644 --- a/explorer-api/src/country_statistics/http.rs +++ b/explorer-api/src/country_statistics/http.rs @@ -2,9 +2,12 @@ use crate::country_statistics::country_nodes_distribution::CountryNodesDistribut use crate::state::ExplorerApiStateContext; use rocket::serde::json::Json; use rocket::{Route, State}; +use rocket_okapi::okapi::openapi3::OpenApi; +use rocket_okapi::openapi_get_routes_spec; +use rocket_okapi::settings::OpenApiSettings; -pub fn country_statistics_make_default_routes() -> Vec { - routes_with_openapi![index] +pub fn country_statistics_make_default_routes(settings: &OpenApiSettings) -> (Vec, OpenApi) { + openapi_get_routes_spec![settings: index] } // We could either separate stuff by structure (like this, http is separate), or we could just diff --git a/explorer-api/src/http/mod.rs b/explorer-api/src/http/mod.rs index 2a3171081d..0cb78e6b4e 100644 --- a/explorer-api/src/http/mod.rs +++ b/explorer-api/src/http/mod.rs @@ -1,12 +1,13 @@ use log::info; use rocket::http::Method; -use rocket::Request; +use rocket::{Build, Request, Rocket}; use rocket_cors::{AllowedHeaders, AllowedOrigins}; use rocket_okapi::swagger_ui::make_swagger_ui; use crate::country_statistics::http::country_statistics_make_default_routes; use crate::http::swagger::get_docs; use crate::mix_node::http::mix_node_make_default_routes; +use crate::mix_nodes::http::mix_nodes_make_default_routes; use crate::ping::http::ping_make_default_routes; use crate::state::ExplorerApiStateContext; @@ -15,35 +16,45 @@ mod swagger; pub(crate) fn start(state: ExplorerApiStateContext) { tokio::spawn(async move { info!("Starting up..."); - - let allowed_origins = AllowedOrigins::all(); - - // You can also deserialize this - let cors = rocket_cors::CorsOptions { - allowed_origins, - allowed_methods: vec![Method::Get].into_iter().map(From::from).collect(), - allowed_headers: AllowedHeaders::some(&["*"]), - allow_credentials: true, - ..Default::default() - } - .to_cors() - .unwrap(); - - let config = rocket::config::Config::release_default(); - rocket::build() - .configure(config) - .mount("/countries", country_statistics_make_default_routes()) - .mount("/ping", ping_make_default_routes()) - .mount("/mix-node", mix_node_make_default_routes()) - .mount("/swagger", make_swagger_ui(&get_docs())) - .register("/", catchers![not_found]) - .manage(state) - .attach(cors) - .launch() - .await + configure_rocket(state).launch().await }); } +fn configure_rocket(state: ExplorerApiStateContext) -> Rocket { + let allowed_origins = AllowedOrigins::all(); + + // You can also deserialize this + let cors = rocket_cors::CorsOptions { + allowed_origins, + allowed_methods: vec![Method::Get].into_iter().map(From::from).collect(), + allowed_headers: AllowedHeaders::some(&["*"]), + allow_credentials: true, + ..Default::default() + } + .to_cors() + .unwrap(); + + let openapi_settings = rocket_okapi::settings::OpenApiSettings::default(); + let config = rocket::config::Config::release_default(); + let mut building_rocket = rocket::build().configure(config); + + mount_endpoints_and_merged_docs! { + building_rocket, + "/v1".to_owned(), + openapi_settings, + "/ping" => ping_make_default_routes(&openapi_settings), + "/countries" => country_statistics_make_default_routes(&openapi_settings), + "/mix-node" => mix_node_make_default_routes(&openapi_settings), + "/mix-nodes" => mix_nodes_make_default_routes(&openapi_settings), + }; + + building_rocket + .mount("/swagger", make_swagger_ui(&get_docs())) + .register("/", catchers![not_found]) + .manage(state) + .attach(cors) +} + #[catch(404)] pub(crate) fn not_found(req: &Request) -> String { format!("I couldn't find '{}'. Try something else?", req.uri()) diff --git a/explorer-api/src/http/swagger.rs b/explorer-api/src/http/swagger.rs index 4f30b69e74..c7489cfcaa 100644 --- a/explorer-api/src/http/swagger.rs +++ b/explorer-api/src/http/swagger.rs @@ -1,12 +1,8 @@ -use rocket_okapi::swagger_ui::{SwaggerUIConfig, UrlObject}; +use rocket_okapi::swagger_ui::SwaggerUIConfig; pub(crate) fn get_docs() -> SwaggerUIConfig { SwaggerUIConfig { - urls: vec![ - UrlObject::new("Country statistics", "/countries/openapi.json"), - UrlObject::new("Node ping", "/ping/openapi.json"), - UrlObject::new("Mix node", "/mix-node/openapi.json"), - ], + url: "../v1/openapi.json".to_owned(), ..Default::default() } } diff --git a/explorer-api/src/main.rs b/explorer-api/src/main.rs index b47e2e38c3..6aa9619b06 100644 --- a/explorer-api/src/main.rs +++ b/explorer-api/src/main.rs @@ -35,6 +35,11 @@ impl ExplorerApi { async fn run(&mut self) { info!("Explorer API starting up..."); + info!( + "Using validator API - {}", + network_defaults::default_api_endpoints()[0].clone() + ); + // spawn concurrent tasks mix_nodes::tasks::MixNodesTasks::new(self.state.clone()).start(); country_statistics::distribution::CountryStatisticsDistributionTask::new( diff --git a/explorer-api/src/mix_node/http.rs b/explorer-api/src/mix_node/http.rs index da6ec972a7..79b2b5750e 100644 --- a/explorer-api/src/mix_node/http.rs +++ b/explorer-api/src/mix_node/http.rs @@ -1,6 +1,9 @@ use reqwest::Error as ReqwestError; use rocket::serde::json::Json; use rocket::{Route, State}; +use rocket_okapi::okapi::openapi3::OpenApi; +use rocket_okapi::openapi_get_routes_spec; +use rocket_okapi::settings::OpenApiSettings; use serde::Serialize; use mixnet_contract::{Addr, Coin, Delegation, Layer, MixNode}; @@ -9,13 +12,12 @@ use crate::mix_node::models::{NodeDescription, NodeStats}; use crate::mix_nodes::{get_mixnode_delegations, get_single_mixnode_delegations, Location}; use crate::state::ExplorerApiStateContext; -pub fn mix_node_make_default_routes() -> Vec { - routes_with_openapi![ - get_delegations, +pub fn mix_node_make_default_routes(settings: &OpenApiSettings) -> (Vec, OpenApi) { + openapi_get_routes_spec![ + settings: get_delegations, get_all_delegations, get_description, get_stats, - list ] } @@ -29,14 +31,6 @@ pub(crate) struct PrettyMixNodeBondWithLocation { pub mix_node: MixNode, } -#[openapi(tag = "mix_node")] -#[get("/")] -pub(crate) async fn list( - state: &State, -) -> Json> { - Json(state.inner.mix_nodes.get_mixnodes_with_location().await) -} - #[openapi(tag = "mix_node")] #[get("//delegations")] pub(crate) async fn get_delegations(pubkey: &str) -> Json> { diff --git a/explorer-api/src/mix_nodes/http.rs b/explorer-api/src/mix_nodes/http.rs new file mode 100644 index 0000000000..efe75f6221 --- /dev/null +++ b/explorer-api/src/mix_nodes/http.rs @@ -0,0 +1,20 @@ +use rocket::serde::json::Json; +use rocket::{Route, State}; +use rocket_okapi::okapi::openapi3::OpenApi; +use rocket_okapi::openapi_get_routes_spec; +use rocket_okapi::settings::OpenApiSettings; + +use crate::mix_node::http::PrettyMixNodeBondWithLocation; +use crate::state::ExplorerApiStateContext; + +pub fn mix_nodes_make_default_routes(settings: &OpenApiSettings) -> (Vec, OpenApi) { + openapi_get_routes_spec![settings: list] +} + +#[openapi(tag = "mix_nodes")] +#[get("/")] +pub(crate) async fn list( + state: &State, +) -> Json> { + Json(state.inner.mix_nodes.get_mixnodes_with_location().await) +} diff --git a/explorer-api/src/mix_nodes/mod.rs b/explorer-api/src/mix_nodes/mod.rs index eaaa2095f2..c6c2a72f16 100644 --- a/explorer-api/src/mix_nodes/mod.rs +++ b/explorer-api/src/mix_nodes/mod.rs @@ -1,3 +1,4 @@ +pub(crate) mod http; pub(crate) mod tasks; mod utils; diff --git a/explorer-api/src/ping/http.rs b/explorer-api/src/ping/http.rs index 8853fbac61..006938bec6 100644 --- a/explorer-api/src/ping/http.rs +++ b/explorer-api/src/ping/http.rs @@ -4,6 +4,9 @@ use std::time::Duration; use rocket::serde::json::Json; use rocket::{Route, State}; +use rocket_okapi::okapi::openapi3::OpenApi; +use rocket_okapi::openapi_get_routes_spec; +use rocket_okapi::settings::OpenApiSettings; use mixnet_contract::MixNodeBond; @@ -12,8 +15,8 @@ use crate::state::ExplorerApiStateContext; const CONNECTION_TIMEOUT_SECONDS: Duration = Duration::from_secs(10); -pub fn ping_make_default_routes() -> Vec { - routes_with_openapi![index] +pub fn ping_make_default_routes(settings: &OpenApiSettings) -> (Vec, OpenApi) { + openapi_get_routes_spec![settings: index] } #[openapi(tag = "ping")]