Restore Location fields (#5208)

* Add latitude/longitude fields to Location

* Add regression test

* Bump package version

* Load secret during workflow
This commit is contained in:
dynco-nym
2024-12-03 18:35:56 +01:00
committed by GitHub
parent fc79f739d4
commit e06d442e95
4 changed files with 87 additions and 14 deletions
+1
View File
@@ -34,6 +34,7 @@ jobs:
runs-on: ${{ matrix.os }}
env:
CARGO_TERM_COLOR: always
IPINFO_API_TOKEN: ${{ secrets.IPINFO_API_TOKEN }}
steps:
- name: Install Dependencies (Linux)
run: sudo apt-get update && sudo apt-get -y install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools protobuf-compiler
Generated
+1 -1
View File
@@ -6062,7 +6062,7 @@ dependencies = [
[[package]]
name = "nym-node-status-api"
version = "1.0.0-rc.5"
version = "1.0.0-rc.6"
dependencies = [
"anyhow",
"axum 0.7.7",
@@ -3,7 +3,7 @@
[package]
name = "nym-node-status-api"
version = "1.0.0-rc.5"
version = "1.0.0-rc.6"
authors.workspace = true
repository.workspace = true
homepage.workspace = true
@@ -1,5 +1,5 @@
use cosmwasm_std::{Addr, Coin};
use serde::{Deserialize, Serialize};
use serde::{Deserialize, Deserializer, Serialize};
pub(crate) struct IpInfoClient {
client: reqwest::Client,
@@ -15,11 +15,7 @@ impl IpInfoClient {
}
pub(crate) async fn locate_ip(&self, ip: impl AsRef<str>) -> anyhow::Result<Location> {
let url = format!(
"https://ipinfo.io/{}/country?token={}",
ip.as_ref(),
&self.token
);
let url = format!("https://ipinfo.io/{}?token={}", ip.as_ref(), &self.token);
let response = self
.client
.get(url)
@@ -33,11 +29,12 @@ impl IpInfoClient {
}
anyhow::Error::from(err)
})?;
let response_text = response.text().await?.trim().to_string();
let raw_response = response.text().await?;
let response: LocationResponse =
serde_json::from_str(&raw_response).inspect_err(|e| tracing::error!("{e}"))?;
let location = response.into();
Ok(Location {
two_letter_iso_country_code: response_text,
})
Ok(location)
}
/// check DOESN'T consume bandwidth allowance
@@ -64,7 +61,7 @@ impl IpInfoClient {
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize)]
pub(crate) struct NodeGeoData {
pub(crate) identity_key: String,
pub(crate) owner: Addr,
@@ -72,15 +69,55 @@ pub(crate) struct NodeGeoData {
pub(crate) location: Location,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[derive(Debug, Clone, Default, Serialize)]
pub(crate) struct Location {
pub(crate) two_letter_iso_country_code: String,
#[serde(flatten)]
pub(crate) location: Coordinates,
}
impl From<LocationResponse> for Location {
fn from(value: LocationResponse) -> Self {
Self {
two_letter_iso_country_code: value.two_letter_iso_country_code,
location: value.loc,
}
}
}
#[derive(Debug, Clone, Deserialize)]
pub(crate) struct LocationResponse {
#[serde(rename = "country")]
pub(crate) two_letter_iso_country_code: String,
#[serde(deserialize_with = "deserialize_loc")]
pub(crate) loc: Coordinates,
}
fn deserialize_loc<'de, D>(deserializer: D) -> Result<Coordinates, D::Error>
where
D: Deserializer<'de>,
{
let loc_raw = String::deserialize(deserializer)?;
match loc_raw.split_once(',') {
Some((lat, long)) => Ok(Coordinates {
latitude: lat.parse().map_err(serde::de::Error::custom)?,
longitude: long.parse().map_err(serde::de::Error::custom)?,
}),
None => Err(serde::de::Error::custom("coordinates")),
}
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub(crate) struct Coordinates {
pub(crate) latitude: f64,
pub(crate) longitude: f64,
}
impl Location {
pub(crate) fn empty() -> Self {
Self {
two_letter_iso_country_code: String::new(),
location: Coordinates::default(),
}
}
}
@@ -110,3 +147,38 @@ pub(crate) mod ipinfo {
pub(crate) remaining: u64,
}
}
#[cfg(test)]
mod api_regression {
use super::*;
use std::{env::var, sync::LazyLock};
static IPINFO_TOKEN: LazyLock<String> = LazyLock::new(|| var("IPINFO_API_TOKEN").unwrap());
#[tokio::test]
async fn should_parse_response() {
let client = IpInfoClient::new(&(*IPINFO_TOKEN));
let my_ip = reqwest::get("https://api.ipify.org")
.await
.expect("Couldn't get own IP")
.text()
.await
.unwrap();
let location_result = client.locate_ip(my_ip).await;
assert!(location_result.is_ok(), "Did ipinfo response change?");
assert!(
client.check_remaining_bandwidth().await.is_ok(),
"Failed to check remaining bandwidth?"
);
// when serialized, these fields should be present because they're exposed over API
let location_result = location_result.unwrap();
let json = serde_json::to_value(&location_result).unwrap();
assert!(json.get("two_letter_iso_country_code").is_some());
assert!(json.get("latitude").is_some());
assert!(json.get("longitude").is_some());
}
}