Skip to main content

zero_bounce/utility/structures/
validation.rs

1use chrono::NaiveDateTime;
2use serde::Deserialize;
3
4use crate::utility::structures::custom_deserialize::deserialize_naive_date;
5use crate::utility::structures::validate_enums::{ZBValidateStatus, ZBValidateSubStatus};
6
7
8#[derive(Clone, Debug, Deserialize)]
9pub struct ZBValidation {
10    pub address: String,
11    pub status: String,
12    pub sub_status: String,
13    pub free_email: bool,
14    pub did_you_mean: Option<String>,
15    pub account: Option<String>,
16    pub domain: Option<String>,
17    pub domain_age_days: Option<String>,
18    pub smtp_provider: Option<String>,
19    pub mx_record: Option<String>,
20    pub mx_found: Option<String>,
21    pub firstname: Option<String>,
22    pub lastname: Option<String>,
23    pub gender: Option<String>,
24    pub country: Option<String>,
25    pub region: Option<String>,
26    pub city: Option<String>,
27    pub zipcode: Option<String>,
28
29    #[serde(deserialize_with="deserialize_naive_date")]
30    pub processed_at: NaiveDateTime,
31}
32
33impl ZBValidation {
34    /// Parse `status` string into typed enum (unknown API values become `ZBValidateStatus::UnknownValue`).
35    pub fn status_enum(&self) -> ZBValidateStatus {
36        self.status.parse().unwrap_or_else(|_| ZBValidateStatus::UnknownValue(self.status.clone()))
37    }
38
39    /// Parse `sub_status` string into typed enum (unknown API values become `ZBValidateSubStatus::UnknownValue`).
40    pub fn sub_status_enum(&self) -> ZBValidateSubStatus {
41        self.sub_status.parse().unwrap_or_else(|_| ZBValidateSubStatus::UnknownValue(self.sub_status.clone()))
42    }
43}
44
45
46#[derive(Clone, Debug, Deserialize)]
47pub struct ZBBatchError {
48    pub error: String,
49    pub email_address: String,
50}
51
52
53#[derive(Clone, Debug, Deserialize)]
54pub struct ZBBatchValidation {
55    pub email_batch: Vec<ZBValidation>,
56    pub errors: Vec<ZBBatchError>,
57}
58
59#[cfg(test)]
60mod test {
61    use serde_json::{Result as SerdeResult, from_str};
62
63    use super::*;
64    use crate::utility::mock_constants::VALIDATION_RESPONSE_VALID;
65    use crate::utility::mock_constants::VALIDATION_RESPONSE_INVALID;
66    use crate::utility::mock_constants::VALIDATION_RESPONSE_NULL_FIELDS;
67    use crate::utility::mock_constants::BATCH_VALIDATION_WITH_ERROR;
68    use crate::utility::mock_constants::BATCH_VALIDATION_ERROR_ONLY;
69    use crate::utility::mock_constants::BATCH_VALIDATION_NO_ERROR;
70
71    #[test]
72    fn test_validation_invalid_json() {
73        let validation_res: SerdeResult<ZBValidation> = from_str("");
74        assert!(validation_res.is_err());
75    }
76
77    #[test]
78    fn test_validation_missing_expected_fields() {
79        let validation_res: SerdeResult<ZBValidation> = from_str(VALIDATION_RESPONSE_NULL_FIELDS);
80        assert!(validation_res.is_ok());
81
82        let validation = validation_res.unwrap();
83        assert_eq!(validation.did_you_mean, None);
84        assert_eq!(validation.domain_age_days, Some("".to_string()));
85    }
86
87    #[test]
88    fn test_validation_invalid_email_status() {
89        let validation_res: SerdeResult<ZBValidation> = from_str(VALIDATION_RESPONSE_INVALID);
90        assert!(validation_res.is_ok());
91
92        let validation = validation_res.unwrap();
93        assert_eq!(validation.status, "invalid".to_string());
94        assert_eq!(validation.sub_status, "mailbox_not_found".to_string());
95        assert_eq!(validation.did_you_mean, None);
96        assert_eq!(validation.smtp_provider, Some("example".to_string()));
97        assert_eq!(validation.free_email, false);
98
99        let expected_date = NaiveDateTime::new(
100            chrono::NaiveDate::from_ymd_opt(2023, 3, 23).unwrap(),
101            chrono::NaiveTime::from_hms_milli_opt(12, 30, 28, 3).unwrap(),
102        );
103        assert_eq!(validation.processed_at, expected_date);
104    }
105
106    #[test]
107    fn test_validation_valid_email_status() {
108        let validation_res: SerdeResult<ZBValidation> = from_str(VALIDATION_RESPONSE_VALID);
109        assert!(validation_res.is_ok());
110
111        let validation = validation_res.unwrap();
112        assert_eq!(validation.status, "valid".to_string());
113        assert_eq!(validation.sub_status, "".to_string());
114        assert_eq!(validation.did_you_mean, None);
115        assert_eq!(validation.smtp_provider, Some("example".to_string()));
116        assert_eq!(validation.free_email, false);
117
118        let expected_date = NaiveDateTime::new(
119            chrono::NaiveDate::from_ymd_opt(2023, 3, 23).unwrap(),
120            chrono::NaiveTime::from_hms_milli_opt(13, 30, 28, 105).unwrap(),
121        );
122        assert_eq!(validation.processed_at, expected_date);
123    }
124
125    #[test]
126    fn test_batch_error_only_content() {
127        let batch: SerdeResult<ZBBatchValidation> = from_str(BATCH_VALIDATION_ERROR_ONLY);
128        assert!(batch.is_ok());
129
130        let batch_object = batch.unwrap();
131        assert_eq!(batch_object.email_batch.len(), 0);
132        assert_eq!(batch_object.errors.len(), 1);
133    }
134
135    #[test]
136    fn test_batch_validation_and_error_content() {
137        let batch: SerdeResult<ZBBatchValidation> = from_str(BATCH_VALIDATION_WITH_ERROR);
138        assert!(batch.is_ok());
139
140        let batch_object = batch.unwrap();
141        assert_eq!(batch_object.email_batch.len(), 1);
142        assert_eq!(batch_object.errors.len(), 1);
143    }
144
145    #[test]
146    fn test_batch_validation_only_content() {
147        let batch: SerdeResult<ZBBatchValidation> = from_str(BATCH_VALIDATION_NO_ERROR);
148        assert!(batch.is_ok());
149
150        let batch_object = batch.unwrap();
151        assert_eq!(batch_object.email_batch.len(), 1);
152        assert_eq!(batch_object.errors.len(), 0);
153    }
154
155}