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
29const 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 pub fn ip(&self) -> Result<net::IpAddr, net::AddrParseError> {
63 self.ip.parse::<net::IpAddr>()
64 }
65
66 pub fn is_eu(&self) -> bool {
71 self.is_eu
72 }
73
74 pub fn city(&self) -> &String {
76 &self.city
77 }
78
79 pub fn region(&self) -> &String {
81 &self.region
82 }
83
84 pub fn region_code(&self) -> &String {
86 &self.region_code
87 }
88
89 pub fn country_name(&self) -> &String {
91 &self.country_name
92 }
93
94 pub fn country_code(&self) -> &String {
97 &self.country_code
98 }
99
100 pub fn continent_name(&self) -> &String {
103 &self.continent_name
104 }
105
106 pub fn latitude(&self) -> f64 {
109 self.latitude
110 }
111
112 pub fn longitude(&self) -> f64 {
115 self.longitude
116 }
117
118 pub fn asn(&self) -> &String {
121 &self.asn
122 }
123
124 pub fn organization(&self) -> &String {
127 &self.organisation
128 }
129
130 pub fn postal(&self) -> &String {
132 &self.postal
133 }
134
135 pub fn calling_code(&self) -> &String {
137 &self.calling_code
138 }
139
140 pub fn flag(&self) -> &String {
143 &self.flag
144 }
145
146 pub fn emoji_flag(&self) -> &String {
149 &self.emoji_flag
150 }
151
152 pub fn emoji_unicode(&self) -> &String {
154 &self.emoji_unicode
155 }
156
157 pub fn languages(&self) -> &Vec<Language> {
159 &self.languages
160 }
161
162 pub fn currency(&self) -> &Currency {
164 &self.currency
165 }
166
167 pub fn time_zone(&self) -> &TimeZone {
169 &self.time_zone
170 }
171
172 pub fn threat(&self) -> &Threat {
174 &self.threat
175 }
176
177 pub fn count(&self) -> u64 {
180 match self.count.parse::<u64>() {
181 Ok(count) => count,
182 Err(_) => 0, }
184 }
185
186 pub fn carrier(&self) -> &Option<Carrier> {
188 &self.carrier
189 }
190}
191
192#[derive(Deserialize, Debug)]
193struct ResponseError {
194 message: String,
195}
196
197pub 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}