Skip to content

Commit e06d442

Browse files
authored
Restore Location fields (#5208)
* Add latitude/longitude fields to Location * Add regression test * Bump package version * Load secret during workflow
1 parent fc79f73 commit e06d442

File tree

4 files changed

+87
-14
lines changed

4 files changed

+87
-14
lines changed

.github/workflows/ci-build.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ jobs:
3434
runs-on: ${{ matrix.os }}
3535
env:
3636
CARGO_TERM_COLOR: always
37+
IPINFO_API_TOKEN: ${{ secrets.IPINFO_API_TOKEN }}
3738
steps:
3839
- name: Install Dependencies (Linux)
3940
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

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

nym-node-status-api/nym-node-status-api/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
[package]
55
name = "nym-node-status-api"
6-
version = "1.0.0-rc.5"
6+
version = "1.0.0-rc.6"
77
authors.workspace = true
88
repository.workspace = true
99
homepage.workspace = true

nym-node-status-api/nym-node-status-api/src/monitor/geodata.rs

Lines changed: 84 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use cosmwasm_std::{Addr, Coin};
2-
use serde::{Deserialize, Serialize};
2+
use serde::{Deserialize, Deserializer, Serialize};
33

44
pub(crate) struct IpInfoClient {
55
client: reqwest::Client,
@@ -15,11 +15,7 @@ impl IpInfoClient {
1515
}
1616

1717
pub(crate) async fn locate_ip(&self, ip: impl AsRef<str>) -> anyhow::Result<Location> {
18-
let url = format!(
19-
"https://ipinfo.io/{}/country?token={}",
20-
ip.as_ref(),
21-
&self.token
22-
);
18+
let url = format!("https://ipinfo.io/{}?token={}", ip.as_ref(), &self.token);
2319
let response = self
2420
.client
2521
.get(url)
@@ -33,11 +29,12 @@ impl IpInfoClient {
3329
}
3430
anyhow::Error::from(err)
3531
})?;
36-
let response_text = response.text().await?.trim().to_string();
32+
let raw_response = response.text().await?;
33+
let response: LocationResponse =
34+
serde_json::from_str(&raw_response).inspect_err(|e| tracing::error!("{e}"))?;
35+
let location = response.into();
3736

38-
Ok(Location {
39-
two_letter_iso_country_code: response_text,
40-
})
37+
Ok(location)
4138
}
4239

4340
/// check DOESN'T consume bandwidth allowance
@@ -64,23 +61,63 @@ impl IpInfoClient {
6461
}
6562
}
6663

67-
#[derive(Debug, Clone, Serialize, Deserialize)]
64+
#[derive(Debug, Clone, Serialize)]
6865
pub(crate) struct NodeGeoData {
6966
pub(crate) identity_key: String,
7067
pub(crate) owner: Addr,
7168
pub(crate) pledge_amount: Coin,
7269
pub(crate) location: Location,
7370
}
7471

75-
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
72+
#[derive(Debug, Clone, Default, Serialize)]
7673
pub(crate) struct Location {
7774
pub(crate) two_letter_iso_country_code: String,
75+
#[serde(flatten)]
76+
pub(crate) location: Coordinates,
77+
}
78+
79+
impl From<LocationResponse> for Location {
80+
fn from(value: LocationResponse) -> Self {
81+
Self {
82+
two_letter_iso_country_code: value.two_letter_iso_country_code,
83+
location: value.loc,
84+
}
85+
}
86+
}
87+
88+
#[derive(Debug, Clone, Deserialize)]
89+
pub(crate) struct LocationResponse {
90+
#[serde(rename = "country")]
91+
pub(crate) two_letter_iso_country_code: String,
92+
#[serde(deserialize_with = "deserialize_loc")]
93+
pub(crate) loc: Coordinates,
94+
}
95+
96+
fn deserialize_loc<'de, D>(deserializer: D) -> Result<Coordinates, D::Error>
97+
where
98+
D: Deserializer<'de>,
99+
{
100+
let loc_raw = String::deserialize(deserializer)?;
101+
match loc_raw.split_once(',') {
102+
Some((lat, long)) => Ok(Coordinates {
103+
latitude: lat.parse().map_err(serde::de::Error::custom)?,
104+
longitude: long.parse().map_err(serde::de::Error::custom)?,
105+
}),
106+
None => Err(serde::de::Error::custom("coordinates")),
107+
}
108+
}
109+
110+
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
111+
pub(crate) struct Coordinates {
112+
pub(crate) latitude: f64,
113+
pub(crate) longitude: f64,
78114
}
79115

80116
impl Location {
81117
pub(crate) fn empty() -> Self {
82118
Self {
83119
two_letter_iso_country_code: String::new(),
120+
location: Coordinates::default(),
84121
}
85122
}
86123
}
@@ -110,3 +147,38 @@ pub(crate) mod ipinfo {
110147
pub(crate) remaining: u64,
111148
}
112149
}
150+
151+
#[cfg(test)]
152+
mod api_regression {
153+
154+
use super::*;
155+
use std::{env::var, sync::LazyLock};
156+
157+
static IPINFO_TOKEN: LazyLock<String> = LazyLock::new(|| var("IPINFO_API_TOKEN").unwrap());
158+
159+
#[tokio::test]
160+
async fn should_parse_response() {
161+
let client = IpInfoClient::new(&(*IPINFO_TOKEN));
162+
let my_ip = reqwest::get("https://api.ipify.org")
163+
.await
164+
.expect("Couldn't get own IP")
165+
.text()
166+
.await
167+
.unwrap();
168+
169+
let location_result = client.locate_ip(my_ip).await;
170+
assert!(location_result.is_ok(), "Did ipinfo response change?");
171+
172+
assert!(
173+
client.check_remaining_bandwidth().await.is_ok(),
174+
"Failed to check remaining bandwidth?"
175+
);
176+
177+
// when serialized, these fields should be present because they're exposed over API
178+
let location_result = location_result.unwrap();
179+
let json = serde_json::to_value(&location_result).unwrap();
180+
assert!(json.get("two_letter_iso_country_code").is_some());
181+
assert!(json.get("latitude").is_some());
182+
assert!(json.get("longitude").is_some());
183+
}
184+
}

0 commit comments

Comments
 (0)