1#![warn(missing_docs)]
4
5pub mod api;
7pub mod error;
9pub mod prelude;
11
12use api::*;
13use dashmap::DashMap;
14use error::{Error, Result};
15use std::fmt;
16use std::time::Duration;
17use ureq::{Agent as HttpClient, AgentBuilder, Request};
18
19const ABSTRACTAPI_DOMAIN: &str = "abstractapi.com";
21
22#[derive(Debug, Eq, Hash, PartialEq)]
24pub enum ApiType {
25 Geolocation,
27 Holidays,
29 ExchangeRates,
31 CompanyEnrichment,
33 Timezone,
35 EmailValidation,
37 PhoneValidation,
39 Vat,
41}
42
43impl fmt::Display for ApiType {
44 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45 write!(
46 f,
47 "{}",
48 match self {
49 Self::Geolocation => "ipgeolocation",
50 Self::Holidays => "holidays",
51 Self::ExchangeRates => "exchange-rates",
52 Self::CompanyEnrichment => "companyenrichment",
53 Self::Timezone => "timezone",
54 Self::EmailValidation => "emailvalidation",
55 Self::PhoneValidation => "phonevalidation",
56 Self::Vat => "vat",
57 }
58 )
59 }
60}
61
62pub struct AbstractApi {
64 http_client: HttpClient,
65 api_keys: DashMap<ApiType, String>,
66}
67
68impl Default for AbstractApi {
69 fn default() -> Self {
70 Self::new_with_http_client(AgentBuilder::new().timeout(Duration::from_secs(15)).build())
71 }
72}
73
74impl AbstractApi {
75 pub fn new<S: Into<String>>(
77 http_client: HttpClient,
78 api_keys: Vec<(ApiType, S)>,
79 ) -> Result<Self> {
80 let mut abstractapi = Self::new_with_http_client(http_client);
81 abstractapi.set_api_keys(api_keys)?;
82 Ok(abstractapi)
83 }
84
85 pub fn new_with_http_client(http_client: HttpClient) -> Self {
87 Self {
88 http_client,
89 api_keys: DashMap::new(),
90 }
91 }
92
93 pub fn new_with_api_key<S: Into<String>>(api_type: ApiType, api_key: S) -> Result<Self> {
95 let mut abstractapi = Self::default();
96 abstractapi.set_api_key(api_type, api_key)?;
97 Ok(abstractapi)
98 }
99
100 pub fn new_with_api_keys<S: Into<String>>(api_keys: Vec<(ApiType, S)>) -> Result<Self> {
102 let mut abstractapi = Self::default();
103 abstractapi.set_api_keys(api_keys)?;
104 Ok(abstractapi)
105 }
106
107 pub fn set_api_key<S: Into<String>>(&mut self, api_type: ApiType, api_key: S) -> Result<()> {
109 match self.api_keys.insert(api_type, api_key.into()) {
110 Some(_) => Err(Error::ApiKeySetError),
111 _ => Ok(()),
112 }
113 }
114
115 pub fn set_api_keys<S: Into<String>>(&mut self, api_keys: Vec<(ApiType, S)>) -> Result<()> {
117 for (api_type, api_key) in api_keys {
118 self.set_api_key(api_type, api_key)?;
119 }
120 Ok(())
121 }
122
123 fn get_api_request(&self, api_type: ApiType, path: &str) -> Result<Request> {
125 Ok(self
126 .http_client
127 .get(&format!(
128 "https://{}.{}/{}/",
129 api_type, ABSTRACTAPI_DOMAIN, path
130 ))
131 .query(
132 "api_key",
133 self.api_keys
134 .get(&api_type)
135 .ok_or(Error::ApiKeyNotPresent(api_type))?
136 .value(),
137 ))
138 }
139
140 pub fn get_geolocation<S: AsRef<str>>(&self, ip_address: S) -> Result<Geolocation> {
142 Ok(self
143 .get_api_request(ApiType::Geolocation, "v1")?
144 .query("ip_address", ip_address.as_ref())
145 .call()
146 .map_err(Error::from)?
147 .into_json()?)
148 }
149
150 pub fn get_holidays<S: AsRef<str>>(
152 &self,
153 country: S,
154 year: S,
155 month: S,
156 day: S,
157 ) -> Result<Holidays> {
158 Ok(self
159 .get_api_request(ApiType::Holidays, "v1")?
160 .query("country", country.as_ref())
161 .query("year", year.as_ref())
162 .query("month", month.as_ref())
163 .query("day", day.as_ref())
164 .call()
165 .map_err(Error::from)?
166 .into_json()?)
167 }
168
169 pub fn get_latest_exchange_rates<S: AsRef<str>>(
171 &self,
172 base: S,
173 target: Option<S>,
174 ) -> Result<ExchangeRatesResult> {
175 let mut request = self
176 .get_api_request(ApiType::ExchangeRates, "v1/live")?
177 .query("base", base.as_ref());
178 if let Some(target) = target {
179 request = request.query("target", target.as_ref());
180 }
181 Ok(request.call().map_err(Error::from)?.into_json()?)
182 }
183
184 pub fn get_historical_exchange_rates<S: AsRef<str>>(
186 &self,
187 base: S,
188 target: Option<S>,
189 date: S,
190 ) -> Result<ExchangeRatesResult> {
191 let mut request = self
192 .get_api_request(ApiType::ExchangeRates, "v1/historical")?
193 .query("base", base.as_ref())
194 .query("date", date.as_ref());
195 if let Some(target) = target {
196 request = request.query("target", target.as_ref());
197 }
198 Ok(request.call().map_err(Error::from)?.into_json()?)
199 }
200
201 pub fn convert_currency<S: AsRef<str>>(
203 &self,
204 base: S,
205 target: S,
206 date: Option<S>,
207 base_amount: Option<u64>,
208 ) -> Result<ConvertedExchangeRate> {
209 let mut request = self
210 .get_api_request(ApiType::ExchangeRates, "v1/convert")?
211 .query("base", base.as_ref())
212 .query("target", target.as_ref());
213 if let Some(date) = date {
214 request = request.query("date", date.as_ref());
215 }
216 if let Some(base_amount) = base_amount {
217 request = request.query("base_amount", &base_amount.to_string());
218 }
219 Ok(request.call().map_err(Error::from)?.into_json()?)
220 }
221
222 pub fn get_company_details<S: AsRef<str>>(
224 &self,
225 domain: Option<S>,
226 email: Option<S>,
227 ) -> Result<CompanyDetails> {
228 let mut request = self.get_api_request(ApiType::CompanyEnrichment, "v1")?;
229 if let Some(domain) = domain {
230 request = request.query("domain", domain.as_ref());
231 }
232 if let Some(email) = email {
233 request = request.query("email", email.as_ref());
234 }
235 Ok(request.call().map_err(Error::from)?.into_json()?)
236 }
237
238 pub fn get_current_time<S: AsRef<str>>(&self, location: S) -> Result<LocationTime> {
240 Ok(self
241 .get_api_request(ApiType::Timezone, "v1/current_time")?
242 .query("location", location.as_ref())
243 .call()
244 .map_err(Error::from)?
245 .into_json()?)
246 }
247
248 pub fn convert_time<S: AsRef<str>>(
250 &self,
251 base_location: S,
252 base_datetime: S,
253 target_location: S,
254 ) -> Result<ConvertedTime> {
255 Ok(self
256 .get_api_request(ApiType::Timezone, "v1/convert_time")?
257 .query("base_location", base_location.as_ref())
258 .query("base_datetime", base_datetime.as_ref())
259 .query("target_location", target_location.as_ref())
260 .call()
261 .map_err(Error::from)?
262 .into_json()?)
263 }
264
265 pub fn validate_email<S: AsRef<str>>(
267 &self,
268 email: S,
269 auto_correct: bool,
270 ) -> Result<EmailDetails> {
271 Ok(self
272 .get_api_request(ApiType::EmailValidation, "v1")?
273 .query("email", email.as_ref())
274 .query("auto_correct", &auto_correct.to_string())
275 .call()
276 .map_err(Error::from)?
277 .into_json()?)
278 }
279
280 pub fn validate_phone<S: AsRef<str>>(&self, phone: S) -> Result<PhoneDetails> {
282 Ok(self
283 .get_api_request(ApiType::PhoneValidation, "v1")?
284 .query("phone", phone.as_ref())
285 .call()
286 .map_err(Error::from)?
287 .into_json()?)
288 }
289
290 pub fn validate_vat<S: AsRef<str>>(&self, vat_number: S) -> Result<VatDetails> {
292 Ok(self
293 .get_api_request(ApiType::Vat, "v1/validate")?
294 .query("vat_number", vat_number.as_ref())
295 .call()
296 .map_err(Error::from)?
297 .into_json()?)
298 }
299
300 pub fn calculate_vat<S: AsRef<str>>(
302 &self,
303 amount: f64,
304 country_code: S,
305 is_vat_incl: bool,
306 vat_category: Option<S>,
307 ) -> Result<Vat> {
308 let mut request = self
309 .get_api_request(ApiType::Vat, "v1/calculate")?
310 .query("amount", &amount.to_string())
311 .query("country_code", country_code.as_ref())
312 .query("is_vat_incl", &is_vat_incl.to_string());
313 if let Some(vat_category) = vat_category {
314 request = request.query("vat_category", vat_category.as_ref())
315 }
316 Ok(request.call().map_err(Error::from)?.into_json()?)
317 }
318
319 pub fn get_vat_rates<S: AsRef<str>>(&self, country_code: S) -> Result<VatRates> {
321 Ok(self
322 .get_api_request(ApiType::Vat, "v1/categories")?
323 .query("country_code", country_code.as_ref())
324 .call()
325 .map_err(Error::from)?
326 .into_json()?)
327 }
328}