1pub mod bulk;
2pub mod validation;
3
4use std::collections::HashMap;
5
6use chrono::{NaiveDate, Utc};
7use serde_json::from_str;
8
9pub use crate::ZeroBounce;
10use crate::utility::structures::generic::{FindEmailResponse, FindEmailResponseV2, DomainSearchResponseV2};
11use crate::utility::{ZBError, ZBResult, ENDPOINT_EMAIL_FINDER};
12use crate::utility::structures::{ActivityData, ApiUsage};
13use crate::utility::{ENDPOINT_ACTIVITY_DATA, ENDPOINT_API_USAGE, ENDPOINT_CREDITS};
14
15pub struct FindEmailV2Builder<'a> {
33 client: &'a ZeroBounce,
34 first_name: Option<&'a str>,
35 domain: Option<&'a str>,
36 company_name: Option<&'a str>,
37 middle_name: Option<&'a str>,
38 last_name: Option<&'a str>,
39}
40
41pub struct DomainSearchV2Builder<'a> {
57 client: &'a ZeroBounce,
58 domain: Option<&'a str>,
59 company_name: Option<&'a str>,
60}
61
62impl ZeroBounce {
63
64 fn get_credits_from_string(string_value: String) -> ZBResult<i64> {
65 from_str::<serde_json::Value>(string_value.as_ref())?
66 .get("Credits")
67 .and_then(serde_json::Value::as_str)
68 .map(str::parse::<i64>)
69 .ok_or(ZBError::explicit("credits value not in json"))?
70 .map_err(ZBError::IntParseError)
71 }
72
73 pub fn get_credits(&self) -> ZBResult<i64> {
74 let query_args = HashMap::new();
75
76 let response_content = self.generic_get_request(
77 self.url_provider.url_of(ENDPOINT_CREDITS), query_args
78 )?;
79
80 Self::get_credits_from_string(response_content)
81 }
82
83 pub fn get_api_usage(&self, start_date: NaiveDate, end_date: NaiveDate) -> ZBResult<ApiUsage> {
84 let start_date_str = start_date.format("%F").to_string();
85 let end_date_str = end_date.format("%F").to_string();
86 let query_args = HashMap::from([
87 ("start_date", start_date_str.as_str()),
88 ("end_date", end_date_str.as_str()),
89 ]);
90
91 let response_content = self.generic_get_request(
92 self.url_provider.url_of(ENDPOINT_API_USAGE), query_args
93 )?;
94
95 let api_usage = from_str::<ApiUsage>(&response_content)?;
96 Ok(api_usage)
97 }
98
99 pub fn get_api_usage_overall(&self) -> ZBResult<ApiUsage> {
100 let start_date = NaiveDate::from_ymd_opt(2000, 1, 1).unwrap();
101 let end_date = Utc::now().naive_local().date();
102 self.get_api_usage(start_date, end_date)
103 }
104
105 pub fn get_activity_data(&self, email: &str) -> ZBResult<ActivityData> {
106 let query_args = HashMap::from([
107 ("email", email),
108 ]);
109
110 let response_content = self.generic_get_request(
111 self.url_provider.url_of(ENDPOINT_ACTIVITY_DATA), query_args
112 )?;
113
114 let activity_data = from_str::<ActivityData>(&response_content)?;
115 Ok(activity_data)
116 }
117
118 #[deprecated(
122 since = "1.2.0",
123 note = "Use `find_email_v2` instead. The new version supports both domain and company_name parameters."
124 )]
125 pub fn find_email(&self, domain: &str, first_name: &str, middle_name: &str, last_name: &str) -> ZBResult<FindEmailResponse> {
126 let mut query_args = HashMap::from([
127 ("domain", domain),
128 ]);
129 if !first_name.is_empty() {
130 query_args.insert("first_name", first_name);
131 }
132 if !middle_name.is_empty() {
133 query_args.insert("middle_name", middle_name);
134 }
135 if !last_name.is_empty() {
136 query_args.insert("last_name", last_name);
137 }
138
139 let response_content = self.generic_get_request(
140 self.url_provider.url_of(ENDPOINT_EMAIL_FINDER), query_args
141 )?;
142
143 let activity_data = from_str::<FindEmailResponse>(&response_content)?;
144 Ok(activity_data)
145 }
146
147 pub fn find_email_v2(&self) -> FindEmailV2Builder<'_> {
178 FindEmailV2Builder {
179 client: self,
180 first_name: None,
181 domain: None,
182 company_name: None,
183 middle_name: None,
184 last_name: None,
185 }
186 }
187
188 #[deprecated(
192 since = "1.2.0",
193 note = "Use `domain_search_v2` instead. The new version supports both domain and company_name parameters."
194 )]
195 #[allow(deprecated)] pub fn domain_search(&self, domain: &str) -> ZBResult<FindEmailResponse> {
197 self.find_email(domain, "", "", "")
198 }
199
200 pub fn domain_search_v2(&self) -> DomainSearchV2Builder<'_> {
226 DomainSearchV2Builder {
227 client: self,
228 domain: None,
229 company_name: None,
230 }
231 }
232
233}
234
235impl<'a> FindEmailV2Builder<'a> {
236 pub fn first_name(mut self, name: &'a str) -> Self {
238 self.first_name = Some(name);
239 self
240 }
241
242 pub fn domain(mut self, domain: &'a str) -> Self {
244 self.domain = Some(domain);
245 self
246 }
247
248 pub fn company_name(mut self, company: &'a str) -> Self {
250 self.company_name = Some(company);
251 self
252 }
253
254 pub fn middle_name(mut self, name: &'a str) -> Self {
256 self.middle_name = Some(name);
257 self
258 }
259
260 pub fn last_name(mut self, name: &'a str) -> Self {
262 self.last_name = Some(name);
263 self
264 }
265
266 pub fn call(self) -> ZBResult<FindEmailResponseV2> {
268 let first_name = self.first_name.ok_or_else(|| ZBError::explicit("first_name is mandatory and must be set"))?;
269
270 if first_name.is_empty() {
271 return Err(ZBError::explicit("first_name cannot be empty"));
272 }
273
274 match (self.domain, self.company_name) {
276 (Some(d), None) => {
277 if d.is_empty() {
278 return Err(ZBError::explicit("domain cannot be empty"));
279 }
280 }
281 (None, Some(c)) => {
282 if c.is_empty() {
283 return Err(ZBError::explicit("company_name cannot be empty"));
284 }
285 }
286 (Some(_), Some(_)) => {
287 return Err(ZBError::explicit("exactly one of domain or company_name must be provided, not both"));
288 }
289 (None, None) => {
290 return Err(ZBError::explicit("either domain or company_name must be provided"));
291 }
292 }
293
294 let mut query_args = HashMap::from([
295 ("first_name", first_name),
296 ]);
297
298 if let Some(d) = self.domain {
299 query_args.insert("domain", d);
300 }
301
302 if let Some(c) = self.company_name {
303 query_args.insert("company_name", c);
304 }
305
306 if let Some(middle) = self.middle_name {
307 if !middle.is_empty() {
308 query_args.insert("middle_name", middle);
309 }
310 }
311
312 if let Some(last) = self.last_name {
313 if !last.is_empty() {
314 query_args.insert("last_name", last);
315 }
316 }
317
318 let response_content = self.client.generic_get_request(
319 self.client.url_provider.url_of(ENDPOINT_EMAIL_FINDER), query_args
320 )?;
321
322 let find_email_response = from_str::<FindEmailResponseV2>(&response_content)?;
323 Ok(find_email_response)
324 }
325}
326
327impl<'a> DomainSearchV2Builder<'a> {
328 pub fn domain(mut self, domain: &'a str) -> Self {
330 self.domain = Some(domain);
331 self
332 }
333
334 pub fn company_name(mut self, company: &'a str) -> Self {
336 self.company_name = Some(company);
337 self
338 }
339
340 pub fn call(self) -> ZBResult<DomainSearchResponseV2> {
342 match (self.domain, self.company_name) {
344 (Some(d), None) => {
345 if d.is_empty() {
346 return Err(ZBError::explicit("domain cannot be empty"));
347 }
348 }
349 (None, Some(c)) => {
350 if c.is_empty() {
351 return Err(ZBError::explicit("company_name cannot be empty"));
352 }
353 }
354 (Some(_), Some(_)) => {
355 return Err(ZBError::explicit("exactly one of domain or company_name must be provided, not both"));
356 }
357 (None, None) => {
358 return Err(ZBError::explicit("either domain or company_name must be provided"));
359 }
360 }
361
362 let mut query_args = HashMap::new();
363
364 if let Some(d) = self.domain {
365 query_args.insert("domain", d);
366 }
367
368 if let Some(c) = self.company_name {
369 query_args.insert("company_name", c);
370 }
371
372 let response_content = self.client.generic_get_request(
373 self.client.url_provider.url_of(ENDPOINT_EMAIL_FINDER), query_args
374 )?;
375
376 let domain_search_response = from_str::<DomainSearchResponseV2>(&response_content)?;
377 Ok(domain_search_response)
378 }
379}
380
381#[cfg(test)]
382mod tests {
383 use crate::utility::mock_constants::CREDITS_RESPONSE_OK;
384 use crate::utility::mock_constants::CREDITS_RESPONSE_NEGATIVE;
385
386 use super::*;
387
388 #[test]
389 fn test_credits_negative() {
390 let credits = ZeroBounce::get_credits_from_string(CREDITS_RESPONSE_NEGATIVE.to_string());
391 assert!(credits.is_ok());
392
393 let amount = credits.unwrap();
394 assert_eq!(amount, -1);
395 }
396
397 #[test]
398 fn test_credits_ok() {
399 let credits = ZeroBounce::get_credits_from_string(CREDITS_RESPONSE_OK.to_string());
400 assert!(credits.is_ok());
401
402 let amount = credits.unwrap();
403 assert_eq!(amount, 123456);
404 }
405
406}