From 3077c2ea8d327572cd5a7e5a6a461c4e7194be63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Mon, 20 May 2024 10:57:37 +0100 Subject: [PATCH] create '/api/v1/auxiliary-details' that exposes nodes location --- Cargo.lock | 1 + .../src/router/api/v1/node/auxiliary.rs | 31 +++++++++++++++++++ .../src/router/api/v1/node/mod.rs | 10 ++++++ .../src/router/api/v1/openapi.rs | 2 ++ nym-node/nym-node-http-api/src/router/mod.rs | 9 +++++- nym-node/nym-node-requests/Cargo.toml | 1 + .../src/api/v1/node/models.rs | 16 +++++++++- nym-node/nym-node-requests/src/helpers.rs | 20 ++++++++++++ nym-node/nym-node-requests/src/lib.rs | 3 ++ nym-node/src/node/mod.rs | 7 ++++- 10 files changed, 97 insertions(+), 3 deletions(-) create mode 100644 nym-node/nym-node-http-api/src/router/api/v1/node/auxiliary.rs create mode 100644 nym-node/nym-node-requests/src/helpers.rs diff --git a/Cargo.lock b/Cargo.lock index f9ea903ad8..57bbd60ad5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5153,6 +5153,7 @@ version = "0.1.0" dependencies = [ "async-trait", "base64 0.21.7", + "celes", "humantime 2.1.0", "humantime-serde", "nym-bin-common", diff --git a/nym-node/nym-node-http-api/src/router/api/v1/node/auxiliary.rs b/nym-node/nym-node-http-api/src/router/api/v1/node/auxiliary.rs new file mode 100644 index 0000000000..bc4d4dae94 --- /dev/null +++ b/nym-node/nym-node-http-api/src/router/api/v1/node/auxiliary.rs @@ -0,0 +1,31 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::api::{FormattedResponse, OutputParams}; +use crate::router::types::RequestError; +use axum::extract::Query; +use nym_node_requests::api::v1::node::models::AuxiliaryDetails; + +/// Returns auxiliary details of this node. +#[utoipa::path( + get, + path = "/auxiliary-details", + context_path = "/api/v1", + tag = "Node", + responses( + (status = 200, content( + ("application/json" = AuxiliaryDetails), + ("application/yaml" = AuxiliaryDetails) + )), + ), + params(OutputParams) +)] +pub(crate) async fn auxiliary( + description: AuxiliaryDetails, + Query(output): Query, +) -> Result { + let output = output.output.unwrap_or_default(); + Ok(output.to_response(description)) +} + +pub type AuxiliaryDetailsResponse = FormattedResponse; diff --git a/nym-node/nym-node-http-api/src/router/api/v1/node/mod.rs b/nym-node/nym-node-http-api/src/router/api/v1/node/mod.rs index c9d228d781..6589b57aeb 100644 --- a/nym-node/nym-node-http-api/src/router/api/v1/node/mod.rs +++ b/nym-node/nym-node-http-api/src/router/api/v1/node/mod.rs @@ -1,6 +1,7 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only +use crate::api::v1::node::auxiliary::auxiliary; use crate::api::v1::node::build_information::build_information; use crate::api::v1::node::description::description; use crate::api::v1::node::hardware::host_system; @@ -11,6 +12,7 @@ use axum::Router; use nym_node_requests::api::v1::node::models; use nym_node_requests::routes::api::v1; +pub mod auxiliary; pub mod build_information; pub mod description; pub mod hardware; @@ -24,6 +26,7 @@ pub struct Config { pub system_info: Option, pub roles: models::NodeRoles, pub description: models::NodeDescription, + pub auxiliary_details: models::AuxiliaryDetails, } pub(super) fn routes(config: Config) -> Router { @@ -63,4 +66,11 @@ pub(super) fn routes(config: Config) -> Router move |query| description(node_description, query) }), ) + .route( + v1::AUXILIARY, + get({ + let auxiliary_details = config.auxiliary_details; + move |query| auxiliary(auxiliary_details, query) + }), + ) } diff --git a/nym-node/nym-node-http-api/src/router/api/v1/openapi.rs b/nym-node/nym-node-http-api/src/router/api/v1/openapi.rs index 7a1fbcda3b..520e5b578d 100644 --- a/nym-node/nym-node-http-api/src/router/api/v1/openapi.rs +++ b/nym-node/nym-node-http-api/src/router/api/v1/openapi.rs @@ -19,6 +19,7 @@ use utoipa_swagger_ui::SwaggerUi; api::v1::node::roles::roles, api::v1::node::hardware::host_system, api::v1::node::description::description, + api::v1::node::auxiliary::auxiliary, api::v1::metrics::mixing::mixing_stats, api::v1::metrics::verloc::verloc_stats, api::v1::metrics::prometheus::prometheus_metrics, @@ -52,6 +53,7 @@ use utoipa_swagger_ui::SwaggerUi; api_requests::v1::node::models::Cpu, api_requests::v1::node::models::CryptoHardware, api_requests::v1::node::models::NodeDescription, + api_requests::v1::node::models::AuxiliaryDetails, api_requests::v1::metrics::models::MixingStats, api_requests::v1::metrics::models::VerlocStats, api_requests::v1::metrics::models::VerlocResult, diff --git a/nym-node/nym-node-http-api/src/router/mod.rs b/nym-node/nym-node-http-api/src/router/mod.rs index 67e51d73f0..aba74606f5 100644 --- a/nym-node/nym-node-http-api/src/router/mod.rs +++ b/nym-node/nym-node-http-api/src/router/mod.rs @@ -15,7 +15,7 @@ use nym_node_requests::api::v1::mixnode::models::Mixnode; use nym_node_requests::api::v1::network_requester::exit_policy::models::UsedExitPolicy; use nym_node_requests::api::v1::network_requester::models::NetworkRequester; use nym_node_requests::api::v1::node::models; -use nym_node_requests::api::v1::node::models::{HostSystem, NodeDescription}; +use nym_node_requests::api::v1::node::models::{AuxiliaryDetails, HostSystem, NodeDescription}; use nym_node_requests::api::SignedHostInformation; use nym_node_requests::routes; use std::net::SocketAddr; @@ -47,6 +47,7 @@ impl Config { system_info: None, roles: Default::default(), description: Default::default(), + auxiliary_details: Default::default(), }, metrics: Default::default(), gateway: Default::default(), @@ -88,6 +89,12 @@ impl Config { self } + #[must_use] + pub fn with_auxiliary_details(mut self, auxiliary_details: AuxiliaryDetails) -> Self { + self.api.v1_config.node.auxiliary_details = auxiliary_details; + self + } + #[must_use] pub fn with_gateway(mut self, gateway: Gateway) -> Self { self.api.v1_config.node.roles.gateway_enabled = true; diff --git a/nym-node/nym-node-requests/Cargo.toml b/nym-node/nym-node-requests/Cargo.toml index 7215eb432e..dd95f2a0e6 100644 --- a/nym-node/nym-node-requests/Cargo.toml +++ b/nym-node/nym-node-requests/Cargo.toml @@ -12,6 +12,7 @@ license.workspace = true [dependencies] base64 = { workspace = true } +celes = { workspace = true } # country codes humantime = { workspace = true } humantime-serde = { workspace = true } schemars = { workspace = true, features = ["preserve_order"] } diff --git a/nym-node/nym-node-requests/src/api/v1/node/models.rs b/nym-node/nym-node-requests/src/api/v1/node/models.rs index 1101d1fbb1..9fb14c240f 100644 --- a/nym-node/nym-node-requests/src/api/v1/node/models.rs +++ b/nym-node/nym-node-requests/src/api/v1/node/models.rs @@ -1,6 +1,8 @@ -// Copyright 2023 - Nym Technologies SA +// Copyright 2023-2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use crate::helpers::de_maybe_stringified; +use celes::Country; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use std::net::IpAddr; @@ -163,3 +165,15 @@ pub struct NodeDescription { /// details define other optional details. pub details: String, } + +/// Auxiliary details of the associated Nym Node. +#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] +#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] +pub struct AuxiliaryDetails { + /// Optional ISO 3166 alpha-2 two-letter country code of the node's **physical** location + #[serde(deserialize_with = "de_maybe_stringified")] + #[cfg_attr(feature = "openapi", schema(example = "PL", value_type = Option))] + #[schemars(with = "Option")] + #[schemars(length(equal = 2))] + pub location: Option, +} diff --git a/nym-node/nym-node-requests/src/helpers.rs b/nym-node/nym-node-requests/src/helpers.rs new file mode 100644 index 0000000000..6dd35a3e92 --- /dev/null +++ b/nym-node/nym-node-requests/src/helpers.rs @@ -0,0 +1,20 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use serde::{Deserialize, Deserializer}; +use std::fmt::Display; +use std::str::FromStr; + +pub fn de_maybe_stringified<'de, D, T, E>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, + T: FromStr, + E: Display, +{ + let raw = String::deserialize(deserializer)?; + if raw.is_empty() { + Ok(None) + } else { + Ok(Some(raw.parse().map_err(serde::de::Error::custom)?)) + } +} diff --git a/nym-node/nym-node-requests/src/lib.rs b/nym-node/nym-node-requests/src/lib.rs index d5ae5d5368..e8519a5073 100644 --- a/nym-node/nym-node-requests/src/lib.rs +++ b/nym-node/nym-node-requests/src/lib.rs @@ -6,6 +6,7 @@ pub mod api; pub mod error; +pub(crate) mod helpers; macro_rules! absolute_route { ( $name:ident, $parent:expr, $suffix:expr ) => { @@ -34,6 +35,7 @@ pub mod routes { pub const HOST_INFO: &str = "/host-information"; pub const SYSTEM_INFO: &str = "/system-info"; pub const NODE_DESCRIPTION: &str = "/description"; + pub const AUXILIARY: &str = "/auxiliary-details"; pub const HEALTH: &str = "/health"; pub const SWAGGER: &str = "/swagger"; @@ -50,6 +52,7 @@ pub mod routes { absolute_route!(host_info_absolute, v1_absolute(), HOST_INFO); absolute_route!(system_info_absolute, v1_absolute(), SYSTEM_INFO); absolute_route!(description_absolute, v1_absolute(), NODE_DESCRIPTION); + absolute_route!(auxiliary_absolute, v1_absolute(), AUXILIARY); absolute_route!(gateway_absolute, v1_absolute(), GATEWAY); absolute_route!(mixnode_absolute, v1_absolute(), MIXNODE); diff --git a/nym-node/src/node/mod.rs b/nym-node/src/node/mod.rs index 0d7f05f1c5..3b6fea7169 100644 --- a/nym-node/src/node/mod.rs +++ b/nym-node/src/node/mod.rs @@ -463,6 +463,10 @@ impl NymNode { &self.ed25519_identity_keys, )?; + let auxiliary_details = api_requests::v1::node::models::AuxiliaryDetails { + location: self.config.host.location.clone(), + }; + // mixnode info let mixnode_details = api_requests::v1::mixnode::models::Mixnode {}; @@ -535,7 +539,8 @@ impl NymNode { .with_network_requester_details(nr_details) .with_ip_packet_router_details(ipr_details) .with_used_exit_policy(exit_policy_details) - .with_description(self.description.clone()); + .with_description(self.description.clone()) + .with_auxiliary_details(auxiliary_details); if self.config.http.expose_system_info { config = config.with_system_info(get_system_info(