1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
use std::collections::HashMap;

use serde::Serialize;
use serde_json::from_str;
use serde_json::{Map as SerdeMap, Value};

use crate::{ZeroBounce,  ZBResult};
use crate::utility::{ENDPOINT_VALIDATE, ZBError, ENDPOINT_BATCH_VALIDATE};
use crate::utility::structures::validation::{ZBValidation, ZBBatchValidation};


impl ZeroBounce {

    pub fn validate_email_and_ip(&self, email: &str, ip_address: &str) -> ZBResult<ZBValidation> {
        let mut query_args = HashMap::from([
            ("api_key", self.api_key.as_str()),
            ("email", email),
        ]);

        if ip_address.len() != 0 {
            query_args.insert("ip_address", ip_address);
        }

        let response_content = self.generic_get_request(
            ENDPOINT_VALIDATE, query_args
        )?;

        let validation = from_str::<ZBValidation>(&response_content)?;
        Ok(validation)
    }

    pub fn validate_email(&self, email: &str) -> ZBResult<ZBValidation> {
        return self.validate_email_and_ip(email, "");
    }

    // Represent a list of tuples (containing email and ip_address) into a
    // serializable `serde_json::Value` that respects the expected structure
    // of the batch validation endpoint.
    //
    // Said structure:
    // ```json
    // {
    //     "api_key": {{apikey}},
    //     "email_batch": [
    //         {"email_address": "valid@example.com", "ip_address": "0.0.0.0"},
    //         {"email_address": "invalid@example.com", "ip_address": "1.1.1.1"}
    //     ]
    // }
    // ```
    // After the value is built, serialize and return the resulted string.
    fn batch_validate_prepare_body(&self, emails_and_ip_addresses: Vec<(String, String)>) -> ZBResult<String> {
        let email_batch = emails_and_ip_addresses
            .into_iter()
            .map(|(email, ip_address)| 
                [
                    ("email_address".to_string(), Value::String(email)),
                    ("ip_address".to_string(), Value::String(ip_address)),
                ]
            )
            .map(SerdeMap::<String, Value>::from_iter)
            .map(Value::Object)
            .collect::<Vec<Value>>();

        let request_body_map = SerdeMap::from_iter([
            ("api_key".to_string(), Value::String(self.api_key.clone())),
            ("email_batch".to_string(), Value::Array(email_batch)),
        ]);

        // let request_body_object = Value::Object(request_body_map);
        let mut serializer = serde_json::Serializer::new(Vec::new());
        Value::Object(request_body_map)
            .serialize(&mut serializer)
            .map_err(ZBError::JsonError)?;

        let final_string = String::from_utf8(serializer.into_inner())
            .map_err(|error| ZBError::explicit(error.to_string().as_str()))?;

        Ok(final_string)
    }

    pub fn batch_validate(&self, emails_and_ip_addresses: Vec<(String, String)>) -> ZBResult<ZBBatchValidation> {
        let body_content = self.batch_validate_prepare_body(emails_and_ip_addresses)?;
        let url = self.url_provider.url_of(ENDPOINT_BATCH_VALIDATE);
        
        let response = self.client.post(url)
            .body(body_content)
            .header("content-type", "application/json")
            .send()?;

        let response_ok = response.status().is_success();
        let response_content = response.text()?;

        if !response_ok {
            return Err(ZBError::explicit(response_content.as_str()));
        }

        let validation = from_str::<ZBBatchValidation>(response_content.as_str())?;
        Ok(validation)
    }

}

#[cfg(test)]
mod test {
    use crate::ZeroBounce;

    #[test]
    fn test_serializing_example() {
        let emails_and_ip_addresses = vec![
            ("valid@example.com".to_string(), "123.123.123.123".to_string()),
            ("invalid@example.com".to_string(), "".to_string()),
        ];

        let body_result = ZeroBounce::new("some_api_key")
            .batch_validate_prepare_body(emails_and_ip_addresses);

        assert!(body_result.is_ok())
    }

}