use serde::{Deserialize, Serialize};
use crate::error::Result;
pub async fn check_ip_info(req: &IpInfoCheckRequest) -> Result<IpInfoCheckResult> {
let url = format!("https://ipinfo.io/{}/json", crate::api::helpers::percent_encode(&req.ip));
let client = reqwest::Client::new();
let response = client
.get(&url)
.timeout(std::time::Duration::from_secs(req.timeout_secs))
.send()
.await
.map_err(|e| crate::error::ShoheError::Transport(format!("IP info request failed: {}", e)))?;
if !response.status().is_success() {
return Ok(IpInfoCheckResult {
ip: req.ip.clone(),
country: None,
region: None,
city: None,
latitude: None,
longitude: None,
org: None,
timezone: None,
postal: None,
hostname: None,
error: Some(format!("API returned {}", response.status())),
});
}
match response.json::<serde_json::Value>().await {
Ok(data) => {
Ok(IpInfoCheckResult {
ip: req.ip.clone(),
country: data.get("country").and_then(|v| v.as_str()).map(|s| s.to_string()),
region: data.get("region").and_then(|v| v.as_str()).map(|s| s.to_string()),
city: data.get("city").and_then(|v| v.as_str()).map(|s| s.to_string()),
latitude: data.get("loc")
.and_then(|v| v.as_str())
.and_then(|loc| loc.split(',').next())
.and_then(|lat| lat.trim().parse::<f64>().ok()),
longitude: data.get("loc")
.and_then(|v| v.as_str())
.and_then(|loc| loc.split(',').nth(1))
.and_then(|lon| lon.trim().parse::<f64>().ok()),
org: data.get("org").and_then(|v| v.as_str()).map(|s| s.to_string()),
timezone: data.get("timezone").and_then(|v| v.as_str()).map(|s| s.to_string()),
postal: data.get("postal").and_then(|v| v.as_str()).map(|s| s.to_string()),
hostname: data.get("hostname").and_then(|v| v.as_str()).map(|s| s.to_string()),
error: None,
})
}
Err(e) => {
Ok(IpInfoCheckResult {
ip: req.ip.clone(),
country: None,
region: None,
city: None,
latitude: None,
longitude: None,
org: None,
timezone: None,
postal: None,
hostname: None,
error: Some(format!("Failed to parse response: {}", e)),
})
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IpInfoCheckRequest {
pub ip: String,
#[serde(default = "default_timeout")]
pub timeout_secs: u64,
}
fn default_timeout() -> u64 { 10 }
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IpInfoCheckResult {
pub ip: String,
pub country: Option<String>,
pub region: Option<String>,
pub city: Option<String>,
pub latitude: Option<f64>,
pub longitude: Option<f64>,
pub org: Option<String>,
pub timezone: Option<String>,
pub postal: Option<String>,
pub hostname: Option<String>,
pub error: Option<String>,
}