ipdata/
lib.rs

1#[macro_use]
2extern crate serde_derive;
3
4extern crate reqwest;
5extern crate serde;
6extern crate serde_json;
7
8use std::env;
9use std::net;
10
11mod error;
12pub use error::IpDataError;
13
14mod carrier;
15pub use carrier::Carrier;
16
17mod currency;
18pub use currency::Currency;
19
20mod language;
21pub use language::Language;
22
23mod timezone;
24pub use timezone::TimeZone;
25
26mod threat;
27pub use threat::Threat;
28
29/// Default URL for ipdata.co API. This can be configured with the
30/// IPDATA_URL environment variable.
31const DEFAULT_URL: &str = "https://api.ipdata.co";
32
33#[derive(Deserialize, Debug)]
34pub struct IpData {
35    ip: String,
36    is_eu: bool,
37    city: String,
38    region: String,
39    region_code: String,
40    country_name: String,
41    country_code: String,
42    continent_name: String,
43    latitude: f64,
44    longitude: f64,
45    asn: String,
46    organisation: String,
47    postal: String,
48    calling_code: String,
49    flag: String,
50    emoji_flag: String,
51    emoji_unicode: String,
52    languages: Vec<Language>,
53    currency: Currency,
54    time_zone: TimeZone,
55    threat: Threat,
56    count: String,
57    carrier: Option<Carrier>,
58}
59
60impl IpData {
61    /// Returns the IP address that was looked up.
62    pub fn ip(&self) -> Result<net::IpAddr, net::AddrParseError> {
63        self.ip.parse::<net::IpAddr>()
64    }
65
66    /// Returns true or false depending on whether the country is a recognized
67    /// member of the European Union. The list of all EU countries is compiled
68    /// from the European Union website:
69    /// https://europa.eu/european-union/about-eu/countries_en.
70    pub fn is_eu(&self) -> bool {
71        self.is_eu
72    }
73
74    /// Returns the name of the city where the IP Address is located.
75    pub fn city(&self) -> &String {
76        &self.city
77    }
78
79    /// Returns the name of the region where the IP address is located.
80    pub fn region(&self) -> &String {
81        &self.region
82    }
83
84    /// Returns the ISO 3166-2 region code for the IP address.
85    pub fn region_code(&self) -> &String {
86        &self.region_code
87    }
88
89    /// Returns the country name where the IP address is located.
90    pub fn country_name(&self) -> &String {
91        &self.country_name
92    }
93
94    /// Returns the 2 letter ISO 3166-1 alpha-2 code for the country where the
95    /// IP address is located.
96    pub fn country_code(&self) -> &String {
97        &self.country_code
98    }
99
100    /// Returns the name of the continent where the IP Address is located. One
101    /// of Africa, Antarctica, Asia, Europe, North America, Oceania, South America.
102    pub fn continent_name(&self) -> &String {
103        &self.continent_name
104    }
105
106    /// Returns an approximate latitudinal location for the IP Address. Often
107    /// near the center of population.
108    pub fn latitude(&self) -> f64 {
109        self.latitude
110    }
111
112    /// Returns an approximate longitudinal location for the IP Address. Often
113    /// near the center of population.
114    pub fn longitude(&self) -> f64 {
115        self.longitude
116    }
117
118    /// Returns the Autonomous System Number that references the IP Address's
119    /// owning organization.
120    pub fn asn(&self) -> &String {
121        &self.asn
122    }
123
124    /// Returns the name of the Organisation that owns the IP Address. This
125    /// will default to the ISP name if the organisation is not available.
126    pub fn organization(&self) -> &String {
127        &self.organisation
128    }
129
130    /// Returns the postal code where the IP address is located.
131    pub fn postal(&self) -> &String {
132        &self.postal
133    }
134
135    /// Returns the international calling code where the IP adress is located.
136    pub fn calling_code(&self) -> &String {
137        &self.calling_code
138    }
139
140    /// Returns a URL to a PNG image with the flag of the country where the IP
141    /// address is located.
142    pub fn flag(&self) -> &String {
143        &self.flag
144    }
145
146    /// Returns an emoji version of the flag of the country where the IP
147    /// address is located.
148    pub fn emoji_flag(&self) -> &String {
149        &self.emoji_flag
150    }
151
152    /// Returns the country flag emoji in unicode.
153    pub fn emoji_unicode(&self) -> &String {
154        &self.emoji_unicode
155    }
156
157    /// Returns the location's languages.
158    pub fn languages(&self) -> &Vec<Language> {
159        &self.languages
160    }
161
162    /// Returns the location's local currency.
163    pub fn currency(&self) -> &Currency {
164        &self.currency
165    }
166
167    /// Returns the locations local time zone.
168    pub fn time_zone(&self) -> &TimeZone {
169        &self.time_zone
170    }
171
172    /// Returns known threat data about the IP.
173    pub fn threat(&self) -> &Threat {
174        &self.threat
175    }
176
177    /// The total number of requests made by your API key in the last 24 hrs.
178    /// Updates once a minute.
179    pub fn count(&self) -> u64 {
180        match self.count.parse::<u64>() {
181            Ok(count) => count,
182            Err(_) => 0, // FIXME: this could be better...
183        }
184    }
185
186    /// Returns details of the IP Addresses's mobile carrier, if available.
187    pub fn carrier(&self) -> &Option<Carrier> {
188        &self.carrier
189    }
190}
191
192#[derive(Deserialize, Debug)]
193struct ResponseError {
194    message: String,
195}
196
197/// Performs a lookup of the provided IpAddr.
198///
199/// # Examples
200///
201/// ```
202/// # extern crate ipdata;
203/// #
204/// # use std::env;
205/// # use std::net;
206/// #
207/// # fn main() {
208///      let ip = net::Ipv4Addr::new(1,1,1,1);
209///      let resp = ipdata::lookup(net::IpAddr::V4(ip));
210///      println!("{}, {}", resp.latitude(), resp.longitude());
211/// # }
212/// ```
213pub fn lookup(addr: net::IpAddr) -> Result<IpData, IpDataError> {
214    let mut url = api_endpoint(addr)?;
215
216    match api_key() {
217        Some(key) => {
218            url.set_query(Some(format!("api-key={}", key).as_str()));
219        }
220        None => eprintln!("warning: ipdata.co api key not configured"),
221    }
222
223    let resp = reqwest::get(url);
224    let mut resp = match resp {
225        Ok(resp) => resp,
226        Err(err) => {
227            return Err(request_error(err));
228        }
229    };
230
231    if !resp.status().is_success() {
232        match resp.json::<ResponseError>() {
233            Ok(err) => return Err(IpDataError::new(err.message.as_str())),
234            Err(_) => return Err(IpDataError::new("unknown failure occured")),
235        }
236    }
237
238    match resp.json::<IpData>() {
239        Ok(data) => Ok(data),
240        Err(err) => {
241            return Err(IpDataError::new(
242                format!("failed to decode response: {}", err).as_str(),
243            ));
244        }
245    }
246}
247
248fn request_error(err: reqwest::Error) -> IpDataError {
249    let code = match err.status() {
250        Some(code) => code,
251        None => return IpDataError::new("unknown error"),
252    };
253
254    match code.canonical_reason() {
255        Some(reason) => IpDataError::new(reason),
256        None => IpDataError::new("unknown error"),
257    }
258}
259
260fn api_endpoint(addr: net::IpAddr) -> Result<reqwest::Url, reqwest::UrlError> {
261    let url = match env::var("IPDATA_URL") {
262        Ok(url) => url,
263        Err(_) => DEFAULT_URL.to_string(),
264    };
265
266    let url = reqwest::Url::parse(url.as_str())?;
267
268    url.join(format!("{}", addr).as_str())
269}
270
271fn api_key() -> Option<String> {
272    match env::var("IPDATA_KEY") {
273        Ok(key) => Some(key),
274        Err(_) => None,
275    }
276}