pub use chrono::FixedOffset;
use crate::error::Error;
use crate::Request;
use serde::{Deserialize, Serialize};
use std::net::IpAddr;
pub fn geo_lookup(ip: IpAddr) -> Option<Geo> {
fn do_request(ip: IpAddr) -> Result<Option<Geo>, Error> {
let mut resp = Request::get("http://www.fastly.com/geolocation")
.with_header("Fastly-XQD-API", "geolocation")
.with_header("Fastly-XQD-arg1", ip.to_string())
.send("geolocation")?;
let raw: Option<RawGeo> = serde_json::from_reader(resp.get_body_mut())?;
Ok(raw.and_then(Geo::try_from_raw))
}
do_request(ip).unwrap_or(None)
}
#[derive(Clone, Debug, Deserialize, Serialize)]
struct RawGeo {
as_name: String,
as_number: u32,
area_code: u16,
city: String,
conn_speed: ConnSpeed,
conn_type: ConnType,
continent: Continent,
country_code: String,
country_code3: String,
country_name: String,
latitude: f64,
longitude: f64,
metro_code: i64,
postal_code: String,
proxy_description: ProxyDescription,
proxy_type: ProxyType,
region: Option<String>,
utc_offset: i32,
}
#[derive(Clone, Debug)]
pub struct Geo {
as_name: String,
as_number: u32,
area_code: u16,
city: String,
conn_speed: ConnSpeed,
conn_type: ConnType,
continent: Continent,
country_code: String,
country_code3: String,
country_name: String,
latitude: f64,
longitude: f64,
metro_code: i64,
postal_code: String,
proxy_description: ProxyDescription,
proxy_type: ProxyType,
region: Option<String>,
utc_offset: FixedOffset,
}
impl Geo {
fn try_from_raw(raw: RawGeo) -> Option<Self> {
let hours = raw.utc_offset / 100;
let minutes = raw.utc_offset % 100;
let seconds = (hours * 60 * 60) + (minutes * 60);
FixedOffset::east_opt(seconds).map(|utc_offset| Geo {
as_name: raw.as_name,
as_number: raw.as_number,
area_code: raw.area_code,
city: raw.city,
conn_speed: raw.conn_speed,
conn_type: raw.conn_type,
continent: raw.continent,
country_code: raw.country_code,
country_code3: raw.country_code3,
country_name: raw.country_name,
latitude: raw.latitude,
longitude: raw.longitude,
metro_code: raw.metro_code,
postal_code: raw.postal_code,
proxy_description: raw.proxy_description,
proxy_type: raw.proxy_type,
region: raw.region,
utc_offset,
})
}
pub fn as_name(&self) -> &str {
self.as_name.as_str()
}
pub fn as_number(&self) -> u32 {
self.as_number
}
pub fn area_code(&self) -> u16 {
self.area_code
}
pub fn city(&self) -> &str {
self.city.as_str()
}
pub fn conn_speed(&self) -> ConnSpeed {
self.conn_speed
}
pub fn conn_type(&self) -> ConnType {
self.conn_type
}
pub fn continent(&self) -> Continent {
self.continent
}
pub fn country_code(&self) -> &str {
self.country_code.as_str()
}
pub fn country_code3(&self) -> &str {
self.country_code3.as_str()
}
pub fn country_name(&self) -> &str {
self.country_name.as_str()
}
pub fn latitude(&self) -> f64 {
self.latitude
}
pub fn longitude(&self) -> f64 {
self.longitude
}
pub fn metro_code(&self) -> i64 {
self.metro_code
}
pub fn postal_code(&self) -> &str {
self.postal_code.as_str()
}
pub fn proxy_description(&self) -> ProxyDescription {
self.proxy_description
}
pub fn proxy_type(&self) -> ProxyType {
self.proxy_type
}
pub fn region(&self) -> Option<&str> {
self.region.as_ref().map(|s| s.as_str())
}
pub fn utc_offset(&self) -> FixedOffset {
self.utc_offset
}
}
#[allow(missing_docs)]
#[serde(rename_all = "kebab-case")]
#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq)]
pub enum ConnSpeed {
Broadband,
Cable,
Dialup,
Mobile,
Oc12,
Oc3,
T1,
T3,
Satellite,
Wireless,
Xdsl,
}
#[allow(missing_docs)]
#[serde(rename_all = "kebab-case")]
#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq)]
pub enum ConnType {
Wired,
Wifi,
Mobile,
Dialup,
Satellite,
#[serde(other)]
#[serde(rename = "?")]
Unknown,
}
#[allow(missing_docs)]
#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq)]
pub enum Continent {
#[serde(rename = "AF")]
Africa,
#[serde(rename = "AN")]
Antarctica,
#[serde(rename = "AS")]
Asia,
#[serde(rename = "EU")]
Europe,
#[serde(rename = "NA")]
NorthAmerica,
#[serde(rename = "OC")]
Oceania,
#[serde(rename = "SA")]
SouthAmerica,
}
impl Continent {
pub fn as_code(&self) -> &'static str {
match self {
Self::Africa => "AF",
Self::Antarctica => "AN",
Self::Asia => "AS",
Self::Europe => "EU",
Self::NorthAmerica => "NA",
Self::Oceania => "OC",
Self::SouthAmerica => "SA",
}
}
}
#[serde(rename_all = "kebab-case")]
#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq)]
pub enum ProxyDescription {
Cloud,
CloudSecurity,
Dns,
TorExit,
TorRelay,
Vpn,
WebBrowser,
#[serde(other)]
#[serde(rename = "?")]
Unknown,
}
#[allow(missing_docs)]
#[serde(rename_all = "kebab-case")]
#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq)]
pub enum ProxyType {
Anonymous,
Aol,
Blackberry,
Corporate,
Edu,
Hosting,
Public,
Transparent,
#[serde(other)]
#[serde(rename = "?")]
Unknown,
}