extern crate hyper;
extern crate hyper_native_tls;
extern crate serde;
extern crate serde_json;
#[macro_use]
extern crate serde_derive;
use hyper::Client;
use hyper::net::HttpsConnector;
use hyper_native_tls::NativeTlsClient;
use serde_json::Value;
use std::io::Read;
use std::error::Error;
mod error;
pub use error::*;
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct GeoIp {
country: String,
country_code: String,
region: String,
region_name: String,
city: String,
zip: String,
lat: f32,
lon: f32,
timezone: String,
isp: String,
org: String,
#[serde(rename = "as")]
as_nn: String,
mobile: bool,
proxy: bool
}
impl GeoIp {
pub fn new(host: Option<&str>, https: bool) -> Result<GeoIp, IpApiError> {
let ssl = NativeTlsClient::new().unwrap();
let connector = HttpsConnector::new(ssl);
let client = Client::with_connector(connector);
let url = format!(
"{}://ip-api.com/json/{}?fields=258047",
if https { "https" } else { "http" },
host.unwrap_or("")
);
let mut response = match client.get(&url).send() {
Err(e) => return Err(IpApiError::OtherError(format!("{}", e.description()))),
Ok(res) => res,
};
let mut body = String::new();
response.read_to_string(&mut body).unwrap();
let json: Value = match serde_json::from_str(&body) {
Err(_) => return Err(IpApiError::OtherError(
format!("Error interpreting body as json; the body is: {}", body))),
Ok(json) => json
};
match json.get("status") {
Some(&Value::String(ref s)) => {
match s.as_ref() {
"success" => (),
"fail" => {
match json.get("message") {
Some(&Value::String(ref s)) => {
match s.as_ref() {
"private range" => return Err(IpApiError::PrivateRange),
"reserved range" => return Err(IpApiError::ReservedRange),
"invalid query" => return Err(IpApiError::InvalidQuery),
"quota" => return Err(IpApiError::Quota),
_ => return Err(unexpected_json(&body, "unknown error message"))
}
}
_ => return Err(unexpected_json(&body, "unexpected message type"))
}
}
_ => return Err(unexpected_json(&body, "invalid status value"))
};
}
_ => return Err(unexpected_json(&body, "invalid status"))
};
match serde_json::from_value(json) {
Err(_) => return Err(IpApiError::OtherError(
format!("Error deserializing json to GeoIp; the body is: {}", body))),
Ok(geo_ip) => Ok(geo_ip)
}
}
pub fn country(&self) -> Option<String> {
as_option(&self.country)
}
pub fn country_code(&self) -> Option<String> {
as_option(&self.country_code)
}
pub fn region(&self) -> Option<String> {
as_option(&self.region)
}
pub fn region_name(&self) -> Option<String> {
as_option(&self.region_name)
}
pub fn city(&self) -> Option<String> {
as_option(&self.city)
}
pub fn zip_code(&self) -> Option<String> {
as_option(&self.zip)
}
pub fn location(&self) -> Option<(f32, f32)> {
if self.lat == 0.0 && self.lon == 0.0 {
None
} else {
Some((self.lat, self.lon))
}
}
pub fn timezone(&self) -> Option<String> {
as_option(&self.timezone)
}
pub fn isp(&self) -> Option<String> {
as_option(&self.isp)
}
pub fn organization(&self) -> Option<String> {
as_option(&self.org)
}
pub fn as_nn(&self) -> Option<String> {
as_option(&self.as_nn)
}
pub fn is_mobile(&self) -> bool {
self.mobile
}
pub fn is_proxy(&self) -> bool {
self.proxy
}
}
fn unexpected_json(body: &str, reason: &str) -> IpApiError {
IpApiError::OtherError(format!("Unexpected response: {}; body is: {}", reason, body))
}
fn as_option(string: &String) -> Option<String> {
if string.is_empty() {
None
} else {
Some(string.clone())
}
}