From 188a7ec91ddc0bae2e74d0fcc3e8e6fe712b8247 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Thu, 5 May 2022 11:35:00 +0200 Subject: [PATCH] validator-api: reenable swagger openapi (#1249) * validator-api: reenable swagger openapi * rustfmt * validator-api: rename openapi tag to status --- validator-api/src/contract_cache/mod.rs | 34 +++++----- validator-api/src/main.rs | 75 ++++++++++++++------- validator-api/src/node_status_api/mod.rs | 68 +++++++++---------- validator-api/src/node_status_api/routes.rs | 24 +++---- validator-api/src/swagger.rs | 11 +++ 5 files changed, 124 insertions(+), 88 deletions(-) create mode 100644 validator-api/src/swagger.rs diff --git a/validator-api/src/contract_cache/mod.rs b/validator-api/src/contract_cache/mod.rs index 533a4375ad..4e51d12400 100644 --- a/validator-api/src/contract_cache/mod.rs +++ b/validator-api/src/contract_cache/mod.rs @@ -4,12 +4,14 @@ use crate::nymd_client::Client; use ::time::OffsetDateTime; use anyhow::Result; -use config::defaults::VALIDATOR_API_VERSION; use mixnet_contract_common::reward_params::EpochRewardParams; use mixnet_contract_common::{ GatewayBond, IdentityKey, IdentityKeyRef, Interval, MixNodeBond, RewardedSetNodeStatus, }; -use rocket_okapi::openapi_get_routes; +use okapi::openapi3::OpenApi; +use rocket::Route; +use rocket_okapi::openapi_get_routes_spec; +use rocket_okapi::settings::OpenApiSettings; use rocket::fairing::AdHoc; use serde::Serialize; @@ -179,6 +181,19 @@ impl ValidatorCacheRefresher { } } +pub(crate) fn validator_cache_routes(settings: &OpenApiSettings) -> (Vec, OpenApi) { + openapi_get_routes_spec![ + settings: routes::get_mixnodes, + routes::get_gateways, + routes::get_active_set, + routes::get_rewarded_set, + routes::get_blacklisted_mixnodes, + routes::get_blacklisted_gateways, + routes::get_epoch_reward_params, + routes::get_current_epoch + ] +} + impl ValidatorCache { fn new() -> Self { ValidatorCache { @@ -189,20 +204,7 @@ impl ValidatorCache { pub fn stage() -> AdHoc { AdHoc::on_ignite("Validator Cache Stage", |rocket| async { - rocket.manage(Self::new()).mount( - // this format! is so ugly... - format!("/{}", VALIDATOR_API_VERSION), - openapi_get_routes![ - routes::get_mixnodes, - routes::get_gateways, - routes::get_active_set, - routes::get_rewarded_set, - routes::get_blacklisted_mixnodes, - routes::get_blacklisted_gateways, - routes::get_epoch_reward_params, - routes::get_current_epoch - ], - ) + rocket.manage(Self::new()) }) } diff --git a/validator-api/src/main.rs b/validator-api/src/main.rs index f0c2cca728..4cc25452f0 100644 --- a/validator-api/src/main.rs +++ b/validator-api/src/main.rs @@ -15,12 +15,13 @@ use anyhow::Result; use clap::{crate_version, App, Arg, ArgMatches}; use contract_cache::ValidatorCache; use log::{info, warn}; +use okapi::openapi3::OpenApi; use rocket::fairing::AdHoc; use rocket::http::Method; use rocket::{Ignite, Rocket}; use rocket_cors::{AllowedHeaders, AllowedOrigins, Cors}; -use rocket_okapi::settings::UrlObject; -use rocket_okapi::swagger_ui::SwaggerUIConfig; +use rocket_okapi::mount_endpoints_and_merged_docs; +use rocket_okapi::swagger_ui::make_swagger_ui; use std::process; use std::sync::Arc; use std::time::Duration; @@ -41,6 +42,7 @@ mod node_status_api; pub(crate) mod nymd_client; mod rewarded_set_updater; pub(crate) mod storage; +mod swagger; #[cfg(feature = "coconut")] mod coconut; @@ -398,8 +400,22 @@ async fn setup_rocket( liftoff_notify: Arc, _nymd_client: Option>, ) -> Result> { - // let's build our rocket! - let rocket = rocket::build() + let openapi_settings = rocket_okapi::settings::OpenApiSettings::default(); + let mut rocket = rocket::build(); + + let custom_route_spec = (vec![], custom_openapi_spec()); + + mount_endpoints_and_merged_docs! { + rocket, + "/v1".to_owned(), + openapi_settings, + "/" => custom_route_spec, + "" => contract_cache::validator_cache_routes(&openapi_settings), + "/status" => node_status_api::node_status_routes(&openapi_settings, config.get_network_monitor_enabled()), + } + + let rocket = rocket + .mount("/swagger", make_swagger_ui(&swagger::get_docs())) .attach(setup_cors()?) .attach(setup_liftoff_notify(liftoff_notify)) .attach(ValidatorCache::stage()); @@ -423,30 +439,43 @@ async fn setup_rocket( rocket }; - // see if we should start up network monitor and if so, attach the node status api - if config.get_network_monitor_enabled() { - Ok(rocket - .attach(storage::ValidatorApiStorage::stage(storage.unwrap())) - .attach(node_status_api::stage_full()) - .ignite() - .await?) + // see if we should start up network monitor + let rocket = if config.get_network_monitor_enabled() { + rocket.attach(storage::ValidatorApiStorage::stage(storage.unwrap())) } else { - Ok(rocket - .attach(node_status_api::stage_minimal()) - .ignite() - .await?) + rocket + }; + + Ok(rocket.ignite().await?) +} + +fn custom_openapi_spec() -> OpenApi { + use rocket_okapi::okapi::openapi3::*; + OpenApi { + openapi: OpenApi::default_version(), + info: Info { + title: "Validator API".to_owned(), + description: None, + terms_of_service: None, + contact: None, + license: None, + version: env!("CARGO_PKG_VERSION").to_owned(), + ..Default::default() + }, + servers: get_servers(), + ..Default::default() } } -#[allow(unused)] -pub(crate) fn get_docs() -> SwaggerUIConfig { - SwaggerUIConfig { - urls: vec![ - UrlObject::new("Contract cache", "../v1/openapi.json"), - UrlObject::new("Node status", "../v1/status/openapi.json"), - ], - ..SwaggerUIConfig::default() +fn get_servers() -> Vec { + if std::env::var_os("CARGO").is_some() { + return vec![]; } + return vec![rocket_okapi::okapi::openapi3::Server { + url: std::env::var("OPEN_API_BASE").unwrap_or_else(|_| "/api/v1/".to_owned()), + description: Some("API".to_owned()), + ..Default::default() + }]; } async fn run_validator_api(matches: ArgMatches<'static>) -> Result<()> { diff --git a/validator-api/src/node_status_api/mod.rs b/validator-api/src/node_status_api/mod.rs index 9802f7c989..bc17732764 100644 --- a/validator-api/src/node_status_api/mod.rs +++ b/validator-api/src/node_status_api/mod.rs @@ -1,8 +1,9 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use rocket::fairing::AdHoc; -use rocket_okapi::openapi_get_routes; +use okapi::openapi3::OpenApi; +use rocket::Route; +use rocket_okapi::{openapi_get_routes_spec, settings::OpenApiSettings}; use std::time::Duration; pub(crate) mod local_guard; @@ -15,39 +16,32 @@ pub(crate) const FIFTEEN_MINUTES: Duration = Duration::from_secs(900); pub(crate) const ONE_HOUR: Duration = Duration::from_secs(3600); pub(crate) const ONE_DAY: Duration = Duration::from_secs(86400); -pub(crate) fn stage_full() -> AdHoc { - AdHoc::on_ignite("Node Status API Stage", |rocket| async { - rocket.mount( - "/v1/status", - openapi_get_routes![ - routes::mixnode_report, - routes::gateway_report, - routes::mixnode_uptime_history, - routes::gateway_uptime_history, - routes::mixnode_core_status_count, - routes::gateway_core_status_count, - routes::get_mixnode_status, - routes::get_mixnode_reward_estimation, - routes::get_mixnode_stake_saturation, - routes::get_mixnode_inclusion_probability, - routes::get_mixnode_avg_uptime, - routes::get_mixnode_avg_uptimes, - ], - ) - }) -} - -// in the minimal variant we would not have access to endpoints relying on existence -// of the network monitor and the associated storage -pub(crate) fn stage_minimal() -> AdHoc { - AdHoc::on_ignite("Node Status API Stage", |rocket| async { - rocket.mount( - "/v1/status", - openapi_get_routes![ - routes::get_mixnode_status, - routes::get_mixnode_stake_saturation, - routes::get_mixnode_inclusion_probability, - ], - ) - }) +pub(crate) fn node_status_routes( + settings: &OpenApiSettings, + enabled: bool, +) -> (Vec, OpenApi) { + if enabled { + openapi_get_routes_spec![ + settings: routes::mixnode_report, + routes::gateway_report, + routes::mixnode_uptime_history, + routes::gateway_uptime_history, + routes::mixnode_core_status_count, + routes::gateway_core_status_count, + routes::get_mixnode_status, + routes::get_mixnode_reward_estimation, + routes::get_mixnode_stake_saturation, + routes::get_mixnode_inclusion_probability, + routes::get_mixnode_avg_uptime, + routes::get_mixnode_avg_uptimes, + ] + } else { + // in the minimal variant we would not have access to endpoints relying on existence + // of the network monitor and the associated storage + openapi_get_routes_spec![ + routes::get_mixnode_status, + routes::get_mixnode_stake_saturation, + routes::get_mixnode_inclusion_probability, + ] + } } diff --git a/validator-api/src/node_status_api/routes.rs b/validator-api/src/node_status_api/routes.rs index 3490828f06..116bb9b190 100644 --- a/validator-api/src/node_status_api/routes.rs +++ b/validator-api/src/node_status_api/routes.rs @@ -20,7 +20,7 @@ use validator_api_requests::models::{ use super::models::Uptime; -#[openapi(tag = "mixnode")] +#[openapi(tag = "status")] #[get("/mixnode//report")] pub(crate) async fn mixnode_report( storage: &State, @@ -33,7 +33,7 @@ pub(crate) async fn mixnode_report( .map_err(|err| ErrorResponse::new(err.to_string(), Status::NotFound)) } -#[openapi(tag = "mixnode")] +#[openapi(tag = "status")] #[get("/gateway//report")] pub(crate) async fn gateway_report( storage: &State, @@ -46,7 +46,7 @@ pub(crate) async fn gateway_report( .map_err(|err| ErrorResponse::new(err.to_string(), Status::NotFound)) } -#[openapi(tag = "mixnode")] +#[openapi(tag = "status")] #[get("/mixnode//history")] pub(crate) async fn mixnode_uptime_history( storage: &State, @@ -59,7 +59,7 @@ pub(crate) async fn mixnode_uptime_history( .map_err(|err| ErrorResponse::new(err.to_string(), Status::NotFound)) } -#[openapi(tag = "mixnode")] +#[openapi(tag = "status")] #[get("/gateway//history")] pub(crate) async fn gateway_uptime_history( storage: &State, @@ -72,7 +72,7 @@ pub(crate) async fn gateway_uptime_history( .map_err(|err| ErrorResponse::new(err.to_string(), Status::NotFound)) } -#[openapi(tag = "mixnode")] +#[openapi(tag = "status")] #[get("/mixnode//core-status-count?")] pub(crate) async fn mixnode_core_status_count( storage: &State, @@ -90,7 +90,7 @@ pub(crate) async fn mixnode_core_status_count( }) } -#[openapi(tag = "mixnode")] +#[openapi(tag = "status")] #[get("/gateway//core-status-count?")] pub(crate) async fn gateway_core_status_count( storage: &State, @@ -108,7 +108,7 @@ pub(crate) async fn gateway_core_status_count( }) } -#[openapi(tag = "mixnode")] +#[openapi(tag = "status")] #[get("/mixnode//status")] pub(crate) async fn get_mixnode_status( cache: &State, @@ -119,7 +119,7 @@ pub(crate) async fn get_mixnode_status( }) } -#[openapi(tag = "mixnode")] +#[openapi(tag = "status")] #[get("/mixnode//reward-estimation")] pub(crate) async fn get_mixnode_reward_estimation( cache: &State, @@ -175,7 +175,7 @@ pub(crate) async fn get_mixnode_reward_estimation( } } -#[openapi(tag = "mixnode")] +#[openapi(tag = "status")] #[get("/mixnode//stake-saturation")] pub(crate) async fn get_mixnode_stake_saturation( cache: &State, @@ -204,7 +204,7 @@ pub(crate) async fn get_mixnode_stake_saturation( } } -#[openapi(tag = "mixnode")] +#[openapi(tag = "status")] #[get("/mixnode//inclusion-probability")] pub(crate) async fn get_mixnode_inclusion_probability( cache: &State, @@ -254,7 +254,7 @@ async fn average_mixnode_uptime( }) } -#[openapi(tag = "mixnode")] +#[openapi(tag = "status")] #[get("/mixnode//avg_uptime")] pub(crate) async fn get_mixnode_avg_uptime( cache: &State, @@ -270,7 +270,7 @@ pub(crate) async fn get_mixnode_avg_uptime( })) } -#[openapi(tag = "mixnode")] +#[openapi(tag = "status")] #[get("/mixnodes/avg_uptime")] pub(crate) async fn get_mixnode_avg_uptimes( cache: &State, diff --git a/validator-api/src/swagger.rs b/validator-api/src/swagger.rs new file mode 100644 index 0000000000..02ff24c31e --- /dev/null +++ b/validator-api/src/swagger.rs @@ -0,0 +1,11 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use rocket_okapi::swagger_ui::SwaggerUIConfig; + +pub(crate) fn get_docs() -> SwaggerUIConfig { + SwaggerUIConfig { + url: "../v1/openapi.json".to_owned(), + ..SwaggerUIConfig::default() + } +}