zero_bounce/utility/structures/
bulk.rs1use std::fmt::Debug;
2
3use bytes::Bytes;
4use chrono::{DateTime, FixedOffset};
5use reqwest::blocking::multipart::{Form, Part};
6
7use serde::Deserialize;
8
9use crate::utility::{ZBResult, ZBError};
10use crate::utility::structures::custom_deserialize::deserialize_date_rfc;
11use crate::utility::structures::custom_deserialize::deserialize_percentage_float;
12
13
14#[derive(Clone, Debug, Deserialize)]
15pub struct ZBFileFeedback {
16 pub success: bool,
17 pub message: String,
18 pub file_name: Option<String>,
19 pub file_id: Option<String>,
20}
21
22
23#[derive(Clone, Debug, Deserialize)]
24pub struct ZBFileStatus {
25 pub success: bool,
26 pub file_id: String,
27 pub file_name: String,
28 pub file_status: String,
29 pub error_reason: Option<String>,
30 pub return_url: Option<String>,
31
32 #[serde(deserialize_with="deserialize_date_rfc")]
33 pub upload_date: DateTime<FixedOffset>,
34
35 #[serde(deserialize_with="deserialize_percentage_float")]
36 pub complete_percentage: f32,
37}
38
39pub enum ZBBulkResponse {
40 Content(Bytes),
41 Feedback(ZBFileFeedback),
42}
43
44impl Debug for ZBBulkResponse {
45 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
46 match &self {
47 Self::Content(cnt) => {
48 write!(f, "<ZBBulkResponse::Content | size {}>", cnt.len())
49 },
50 Self::Feedback(feedback) => {
51 write!(f, "<ZBBulkResponse::Feedback | {:#?}>", feedback)
52 },
53 }
54 }
55}
56
57#[derive(Debug, Clone)]
58pub enum ZBFileContentType {
59 FilePath(String),
60 RawContent(Vec<u8>),
61 Empty,
62}
63
64impl ZBFileContentType {
65 pub fn is_empty(&self) -> bool {
66 match self {
67 Self::Empty => true,
68 Self::RawContent(vec) => vec.len() == 0,
69 _ => false,
70 }
71 }
72}
73
74pub struct ZBFile {
75 content_type: ZBFileContentType,
76 has_header_row: bool,
77 remove_duplicate: bool,
78 email_address_column: u32,
79 first_name_column: Option<u32>,
80 last_name_column: Option<u32>,
81 gender_column: Option<u32>,
82 ip_address_column: Option<u32>,
83}
84
85impl Default for ZBFile {
86 fn default() -> Self {
87 ZBFile {
88 content_type: ZBFileContentType::Empty,
89 has_header_row: true,
90 remove_duplicate: false,
91 email_address_column: 1,
92 first_name_column: None,
93 last_name_column: None,
94 gender_column: None,
95 ip_address_column: None,
96 }
97 }
98}
99
100impl ZBFile {
101
102 pub fn from_path(path_to_file: String) -> ZBFile {
103 ZBFile {
104 content_type: ZBFileContentType::FilePath(path_to_file),
105 ..Default::default()
106 }
107 }
108
109 pub fn from_content(content: Vec<u8>) -> ZBFile {
110 ZBFile {
111 content_type: ZBFileContentType::RawContent(content),
112 ..Default::default()
113 }
114 }
115
116 fn file_content_multipart(&self) -> ZBResult<Part> {
117 match self.content_type.clone() {
118 ZBFileContentType::Empty => Err(ZBError::explicit("bulk content cannot be empty")),
119 ZBFileContentType::FilePath(file_path) => Ok(
120 Part::file(file_path.clone())?
121 ),
122 ZBFileContentType::RawContent(value) => Ok(
123 Part::bytes(value.clone())
124 .file_name("file.csv")
125 .mime_str("text/csv")?
126 ),
127 }
128 }
129
130 pub fn generate_multipart(&self) -> ZBResult<Form> {
131 let content_part = self.file_content_multipart()?;
132 let mut multipart_form = Form::new()
133 .part("file", content_part)
134 .text("has_header_row", self.has_header_row.to_string())
135 .text("remove_duplicate", self.remove_duplicate.to_string())
136 .text("email_address_column", self.email_address_column.to_string());
137
138 if let Some(amount) = self.first_name_column {
139 multipart_form = multipart_form.text("first_name_column", amount.to_string());
140 }
141 if let Some(amount) = self.last_name_column {
142 multipart_form = multipart_form.text("last_name_column", amount.to_string());
143 }
144 if let Some(amount) = self.gender_column {
145 multipart_form = multipart_form.text("gender_column", amount.to_string());
146 }
147 if let Some(amount) = self.ip_address_column {
148 multipart_form = multipart_form.text("ip_address_column", amount.to_string());
149 }
150
151 Ok(multipart_form)
152 }
153
154 pub fn set_has_header_row(mut self, has_header_row: bool) -> Self {
155 self.has_header_row = has_header_row;
156 self
157 }
158
159 pub fn set_remove_duplicate(mut self, remove_duplicate: bool) -> Self {
160 self.remove_duplicate = remove_duplicate;
161 self
162 }
163
164 pub fn set_email_address_column(mut self, email_address_column: u32) -> Self {
165 self.email_address_column = email_address_column;
166 self
167 }
168
169 pub fn set_first_name_column(mut self, first_name_column: Option<u32>) -> Self {
170 self.first_name_column = first_name_column;
171 self
172 }
173
174 pub fn set_last_name_column(mut self, last_name_column: Option<u32>) -> Self {
175 self.last_name_column = last_name_column;
176 self
177 }
178
179 pub fn set_gender_column(mut self, gender_column: Option<u32>) -> Self {
180 self.gender_column = gender_column;
181 self
182 }
183
184 pub fn set_ip_address_column(mut self, ip_address_column: Option<u32>) -> Self {
185 self.ip_address_column = ip_address_column;
186 self
187 }
188}
189
190
191#[cfg(test)]
192
193mod test {
194 use chrono::{NaiveDate, NaiveTime, NaiveDateTime};
195 use serde_json::{Result as SerdeResult, from_str};
196
197 use super::*;
198 use crate::utility::mock_constants::BULK_VALIDATION_SUBMIT_OK;
199 use crate::utility::mock_constants::BULK_VALIDATION_STATUS_OK;
200 use crate::utility::mock_constants::BULK_VALIDATION_STATUS_DELETED;
201 use crate::utility::mock_constants::BULK_VALIDATION_RESULT_DELETED;
202 use crate::utility::mock_constants::BULK_VALIDATION_DELETE_OK;
203
204
205 #[test]
206 fn test_parsing_file_submit_response_ok() {
207 let validation: SerdeResult<ZBFileFeedback> = from_str(BULK_VALIDATION_SUBMIT_OK);
208 assert!(validation.is_ok());
209
210 let validation_obj = validation.unwrap();
211 assert_eq!(validation_obj.success, true);
212 assert!(validation_obj.file_id.is_some());
213 assert!(validation_obj.file_name.is_some());
214 assert_eq!(validation_obj.message, "File Accepted");
215 }
216
217 #[test]
218 fn test_parse_file_status_response_ok() {
219 let file_status: SerdeResult<ZBFileStatus> = from_str(BULK_VALIDATION_STATUS_OK);
220 assert!(file_status.is_ok());
221
222 let expected_date_time = NaiveDateTime::new(
223 NaiveDate::from_ymd_opt(2023, 4, 26).unwrap(),
224 NaiveTime::from_hms_opt(17, 52, 23).unwrap(),
225 );
226
227 let file_status_obj = file_status.unwrap();
228 assert_eq!(file_status_obj.success, true);
229 assert_eq!(file_status_obj.complete_percentage, 100.);
230 assert_eq!(file_status_obj.upload_date.naive_utc(), expected_date_time);
231 assert!(file_status_obj.return_url.is_some());
232 assert!(file_status_obj.error_reason.is_none());
233 }
234
235 #[test]
236 fn test_parse_file_status_response_deleted() {
237 let file_status: SerdeResult<ZBFileStatus> = from_str(BULK_VALIDATION_STATUS_DELETED);
238 assert!(file_status.is_ok());
239
240 let expected_date_time = NaiveDateTime::new(
241 NaiveDate::from_ymd_opt(2023, 4, 26).unwrap(),
242 NaiveTime::from_hms_opt(17, 52, 23).unwrap(),
243 );
244
245 let file_status_obj = file_status.unwrap();
246 assert_eq!(file_status_obj.success, true);
247 assert_eq!(file_status_obj.complete_percentage, 0.);
248 assert_eq!(file_status_obj.upload_date.naive_utc(), expected_date_time);
249 assert!(file_status_obj.return_url.is_none());
250 assert!(file_status_obj.error_reason.is_some());
251
252 }
253
254 #[test]
255 fn test_parse_file_result_deleted() {
256 let feedback: SerdeResult<ZBFileFeedback> = from_str(BULK_VALIDATION_RESULT_DELETED);
257 assert!(feedback.is_ok());
258
259 let feedback_obj = feedback.unwrap();
260 assert_eq!(feedback_obj.success, false);
261 assert!(feedback_obj.file_id.is_none());
262 assert!(feedback_obj.file_name.is_none());
263 }
264
265 #[test]
266 fn test_parse_file_delete_ok() {
267 let feedback: SerdeResult<ZBFileFeedback> = from_str(BULK_VALIDATION_DELETE_OK);
268 assert!(feedback.is_ok());
269
270 let feedback_obj = feedback.unwrap();
271 assert_eq!(feedback_obj.success, true);
272 assert!(feedback_obj.file_id.is_some());
273 assert!(feedback_obj.file_name.is_some());
274 assert_eq!(feedback_obj.message, "File Deleted");
275 }
276
277}