use serde_json::Value;
use std::fmt;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use surf::get;
#[derive(Debug, Clone, Copy)]
pub enum Service {
IpWhois,
IpApi,
IpApiCo,
FreeGeoIp,
}
impl fmt::Display for Service {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Service::IpWhois => {
write!(f, "ipwhois")
}
Service::IpApi => {
write!(f, "ipapi")
}
Service::IpApiCo => {
write!(f, "ipapico")
}
Service::FreeGeoIp => {
write!(f, "freegeoip")
}
}
}
}
#[derive(Debug, Clone)]
pub enum GeoError {
HttpError(String),
ParseError(String),
}
impl std::error::Error for GeoError {}
impl fmt::Display for GeoError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
GeoError::HttpError(error) => {
write!(f, "HTTP Request Error: {}", error.to_string())
}
GeoError::ParseError(error) => {
write!(f, "JSON Parsing Error: {}", error.to_string())
}
}
}
}
pub struct Locator {
pub ip: String,
pub latitude: String,
pub longitude: String,
pub city: String,
pub region: String,
pub country: String,
pub timezone: String,
}
impl Locator {
pub async fn get_ipv4(ip: Ipv4Addr, service: Service) -> std::result::Result<Self, GeoError> {
Locator::get(&ip.to_string(), service).await
}
pub async fn get_ipv6(ip: Ipv6Addr, service: Service) -> std::result::Result<Self, GeoError> {
Locator::get(&ip.to_string(), service).await
}
pub async fn get_ipaddr(ip: IpAddr, service: Service) -> std::result::Result<Self, GeoError> {
Locator::get(&ip.to_string(), service).await
}
pub async fn get(ip: &str, service: Service) -> std::result::Result<Self, GeoError> {
match service {
Service::IpWhois => Locator::ipwhois(ip).await,
Service::IpApi => Locator::ipapi(ip).await,
Service::IpApiCo => Locator::ipapico(ip).await,
Service::FreeGeoIp => Locator::freegeoip(ip).await,
}
}
async fn freegeoip(ip: &str) -> std::result::Result<Self, GeoError> {
let url = format!("https://freegeoip.app/json/{}", ip);
let response = match get(&url).recv_string().await {
Ok(response) => response,
Err(_) => return Err(GeoError::HttpError(format!("Couldn't connect to freegeoip.app"))),
};
let parsed_json: Value = match serde_json::from_str(&response) {
Ok(parsed_json) => parsed_json,
Err(error) => {
return Err(GeoError::ParseError(format!(
"Couldn't parse json: {}",
error
)));
}
};
let latitude = match &parsed_json["latitude"] {
Value::Number(latitude) => latitude,
_ => {
return Err(GeoError::ParseError(
"Unable to find latitude in parsed JSON".to_string(),
))
}
};
let longitude = match &parsed_json["longitude"] {
Value::Number(longitude) => longitude,
_ => {
return Err(GeoError::ParseError(
"Unable to find longitude in parsed JSON".to_string(),
));
}
};
let city = match &parsed_json["city"] {
Value::String(city_str) => city_str,
_ => {
return Err(GeoError::ParseError(
"Unable to find city in parsed JSON".to_string(),
));
}
};
let region = match &parsed_json["region_name"] {
Value::String(region_str) => region_str,
_ => {
return Err(GeoError::ParseError(
"Unable to find region in parsed JSON".to_string(),
));
}
};
let country = match &parsed_json["country_name"] {
Value::String(country_str) => country_str,
_ => {
return Err(GeoError::ParseError(
"Unable to find country in parsed JSON".to_string(),
));
}
};
let timezone = match &parsed_json["time_zone"] {
Value::String(timezone_str) => timezone_str,
_ => {
return Err(GeoError::ParseError(
"Unable to find timezone in parsed JSON".to_string(),
));
}
};
let ip = ip.to_string();
let latitude = latitude.to_string();
let longitude = longitude.to_string();
let city = city.to_string();
let region = region.to_string();
let country = country.to_string();
let timezone = timezone.to_string();
let result = Locator {
ip,
latitude,
longitude,
city,
region,
country,
timezone,
};
Ok(result)
}
async fn ipwhois(ip: &str) -> std::result::Result<Self, GeoError> {
let url = format!("http://ipwhois.app/json/{}", ip);
let response = match get(&url).recv_string().await {
Ok(response) => response,
Err(_) => return Err(GeoError::HttpError(format!("Couldn't connect to ipwhois.app"))),
};
let parsed_json: Value = match serde_json::from_str(&response) {
Ok(parsed_json) => parsed_json,
Err(error) => {
return Err(GeoError::ParseError(format!(
"Error parsing json: {}",
error
)));
}
};
let success = match &parsed_json["success"] {
Value::Bool(latitude_str) => latitude_str,
_ => {
return Err(GeoError::ParseError(
"Cannot find success in JSON".to_string(),
));
}
};
if !success {
return Err(GeoError::ParseError(
"You've hit the monthly limit".to_string(),
));
}
let latitude_str = match &parsed_json["latitude"] {
Value::String(latitude_str) => latitude_str,
_ => {
return Err(GeoError::ParseError(
"Unable to find latitude in parsed JSON".to_string(),
));
}
};
let longitude_str = match &parsed_json["longitude"] {
Value::String(longitude_str) => longitude_str,
_ => {
return Err(GeoError::ParseError(
"Unable to find longitude in parsed JSON".to_string(),
));
}
};
let city_str = match &parsed_json["city"] {
Value::String(city_str) => city_str,
_ => {
return Err(GeoError::ParseError(
"Unable to find city in parsed JSON".to_string(),
));
}
};
let region_str = match &parsed_json["region"] {
Value::String(region_str) => region_str,
_ => {
return Err(GeoError::ParseError(
"Unable to find region in parsed JSON".to_string(),
));
}
};
let country_str = match &parsed_json["country"] {
Value::String(country_str) => country_str,
_ => {
return Err(GeoError::ParseError(
"Unable to find country in parsed JSON".to_string(),
));
}
};
let timezone_str = match &parsed_json["timezone"] {
Value::String(timezone_str) => timezone_str,
_ => {
return Err(GeoError::ParseError(
"Unable to find timezone in parsed JSON".to_string(),
));
}
};
let ip = ip.to_string();
let latitude = latitude_str.to_string();
let longitude = longitude_str.to_string();
let city = city_str.to_string();
let region = region_str.to_string();
let country = country_str.to_string();
let timezone = timezone_str.to_string();
let result = Locator {
ip,
latitude,
longitude,
city,
region,
country,
timezone,
};
Ok(result)
}
async fn ipapi(ip: &str) -> std::result::Result<Self, GeoError> {
let url = format!("http://ip-api.com/json/{}", ip);
let response = match get(&url).recv_string().await {
Ok(response) => response,
Err(_) => return Err(GeoError::HttpError(format!("Couldn't connect to ip-api.com"))),
};
let parsed_json: Value = match serde_json::from_str(&response) {
Ok(parsed_json) => parsed_json,
Err(error) => {
return Err(GeoError::ParseError(format!(
"Couldn't parse json: {}",
error
)));
}
};
let latitude = match &parsed_json["lat"] {
Value::Number(latitude) => latitude,
_ => {
return Err(GeoError::ParseError(
"Unable to find latitude in parsed JSON".to_string(),
));
}
};
let longitude = match &parsed_json["lon"] {
Value::Number(longitude) => longitude,
_ => {
return Err(GeoError::ParseError(
"Unable to find longitude in parsed JSON".to_string(),
));
}
};
let city = match &parsed_json["city"] {
Value::String(city_str) => city_str,
_ => {
return Err(GeoError::ParseError(
"Unable to find city in parsed JSON".to_string(),
));
}
};
let region = match &parsed_json["regionName"] {
Value::String(region_str) => region_str,
_ => {
return Err(GeoError::ParseError(
"Unable to find region in parsed JSON".to_string(),
));
}
};
let country = match &parsed_json["country"] {
Value::String(country_str) => country_str,
_ => {
return Err(GeoError::ParseError(
"Unable to find country in parsed JSON".to_string(),
));
}
};
let timezone = match &parsed_json["timezone"] {
Value::String(timezone_str) => timezone_str,
_ => {
return Err(GeoError::ParseError(
"Unable to find timezone in parsed JSON".to_string(),
));
}
};
let ip = ip.to_string();
let latitude = latitude.to_string();
let longitude = longitude.to_string();
let city = city.to_string();
let region = region.to_string();
let country = country.to_string();
let timezone = timezone.to_string();
let result = Locator {
ip,
latitude,
longitude,
city,
region,
country,
timezone,
};
Ok(result)
}
async fn ipapico(ip: &str) -> std::result::Result<Self, GeoError> {
let url = format!("https://ipapi.co/{}/json/", ip);
let response = match get(&url).recv_string().await {
Ok(response) => response,
Err(_) => return Err(GeoError::HttpError(format!("Couldn't connect to ipapi.co"))),
};
let parsed_json: Value = match serde_json::from_str(&response) {
Ok(parsed_json) => parsed_json,
Err(error) => {
return Err(GeoError::ParseError(format!(
"Couldn't parse json: {}",
error
)));
}
};
let latitude = match &parsed_json["latitude"] {
Value::Number(latitude) => latitude,
_ => {
return Err(GeoError::ParseError(
"Unable to find latitude in parsed JSON".to_string(),
))
}
};
let longitude = match &parsed_json["longitude"] {
Value::Number(longitude) => longitude,
_ => {
return Err(GeoError::ParseError(
"Unable to find longitude in parsed JSON".to_string(),
));
}
};
let city = match &parsed_json["city"] {
Value::String(city_str) => city_str,
_ => {
return Err(GeoError::ParseError(
"Unable to find city in parsed JSON".to_string(),
));
}
};
let region = match &parsed_json["region"] {
Value::String(region_str) => region_str,
_ => {
return Err(GeoError::ParseError(
"Unable to find region in parsed JSON".to_string(),
));
}
};
let country = match &parsed_json["country_name"] {
Value::String(country_str) => country_str,
_ => {
return Err(GeoError::ParseError(
"Unable to find country in parsed JSON".to_string(),
));
}
};
let timezone = match &parsed_json["timezone"] {
Value::String(timezone_str) => timezone_str,
_ => {
return Err(GeoError::ParseError(
"Unable to find timezone in parsed JSON".to_string(),
));
}
};
let ip = ip.to_string();
let latitude = latitude.to_string();
let longitude = longitude.to_string();
let city = city.to_string();
let region = region.to_string();
let country = country.to_string();
let timezone = timezone.to_string();
let result = Locator {
ip,
latitude,
longitude,
city,
region,
country,
timezone,
};
Ok(result)
}
}