1use crate::customer_email::CustomerEmail;
2use crate::customer_id::CustomerId;
3use crate::customer_name::CustomerName;
4use crate::risk::{RiskLevel, RiskScore};
5use crate::tax_id::{CnpjOrCpf, DocumentType};
6use crate::types::shared::date_iso_8601::DateIso8601;
7use crate::types::shared::phone_number::PhoneNumber;
8use crate::types::validation_errors::ValidationError;
9use serde::{Deserialize, Serialize};
10
11pub mod customer_email;
12pub mod customer_id;
13pub mod customer_name;
14pub mod risk;
15pub mod tax_id;
16
17#[derive(Debug, Clone, Serialize, Deserialize)]
22pub struct Customer {
23 pub id: CustomerId,
25
26 pub name: CustomerName,
28
29 pub email: CustomerEmail,
31
32 #[serde(skip_serializing_if = "Option::is_none")]
34 pub phone1: Option<PhoneNumber>,
35
36 #[serde(skip_serializing_if = "Option::is_none")]
38 pub phone2: Option<PhoneNumber>,
39
40 #[serde(skip_serializing_if = "Option::is_none")]
42 pub tax_id: Option<CnpjOrCpf>,
43
44 #[serde(skip_serializing_if = "Option::is_none")]
46 pub document_type: Option<DocumentType>,
47
48 #[serde(skip_serializing_if = "Option::is_none")]
50 pub dob: Option<DateIso8601>,
51
52 #[serde(skip_serializing_if = "Option::is_none")]
54 pub created_at: Option<DateIso8601>,
55
56 #[serde(skip_serializing_if = "Option::is_none")]
58 pub new: Option<bool>,
59
60 #[serde(skip_serializing_if = "Option::is_none")]
62 pub vip: Option<bool>,
63
64 #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
66 pub customer_type: Option<String>,
67
68 #[serde(skip_serializing_if = "Option::is_none")]
70 pub risk_level: Option<RiskLevel>,
71
72 #[serde(skip_serializing_if = "Option::is_none")]
74 pub risk_score: Option<RiskScore>,
75
76 #[serde(skip_serializing_if = "Option::is_none")]
78 pub mother_name: Option<CustomerName>,
79}
80
81impl Customer {
82 pub fn builder() -> CustomerBuilder {
84 CustomerBuilder::default()
85 }
86}
87
88#[derive(Default)]
89pub struct CustomerBuilder {
90 id: Option<CustomerId>,
91 name: Option<CustomerName>,
92 email: Option<CustomerEmail>,
93 phone1: Option<PhoneNumber>,
94 phone2: Option<PhoneNumber>,
95 tax_id: Option<CnpjOrCpf>,
96 document_type: Option<DocumentType>,
97 dob: Option<DateIso8601>,
98 created_at: Option<DateIso8601>,
99 new: Option<bool>,
100 vip: Option<bool>,
101 customer_type: Option<String>,
102 risk_level: Option<RiskLevel>,
103 risk_score: Option<RiskScore>,
104 mother_name: Option<CustomerName>,
105}
106
107impl CustomerBuilder {
108 pub fn id(mut self, id: impl Into<String>) -> Result<Self, ValidationError> {
110 self.id = Some(CustomerId::try_new(id)?);
111 Ok(self)
112 }
113
114 pub fn name(mut self, name: impl Into<String>) -> Result<Self, ValidationError> {
116 self.name = Some(CustomerName::try_new(name)?);
117 Ok(self)
118 }
119
120 pub fn email(mut self, email: impl Into<String>) -> Result<Self, ValidationError> {
122 self.email = Some(CustomerEmail::new(email)?);
123 Ok(self)
124 }
125
126 pub fn phone1(mut self, phone: impl Into<String>) -> Result<Self, ValidationError> {
128 self.phone1 = Some(PhoneNumber::new(phone)?);
129 Ok(self)
130 }
131
132 pub fn phone2(mut self, phone: impl Into<String>) -> Result<Self, ValidationError> {
134 self.phone2 = Some(PhoneNumber::new(phone)?);
135 Ok(self)
136 }
137
138 pub fn tax_id(mut self, tax_id: impl Into<String>) -> Result<Self, ValidationError> {
140 self.tax_id = Some(CnpjOrCpf::try_from(tax_id.into())?);
141 Ok(self)
142 }
143
144 pub fn document_type(mut self, doc_type: DocumentType) -> Self {
146 self.document_type = Some(doc_type);
147 self
148 }
149
150 pub fn dob(mut self, dob: impl Into<String>) -> Result<Self, ValidationError> {
152 self.dob = Some(DateIso8601::new(dob)?);
153 Ok(self)
154 }
155
156 pub fn created_at(mut self, date: impl Into<String>) -> Result<Self, ValidationError> {
158 self.created_at = Some(DateIso8601::new(date)?);
159 Ok(self)
160 }
161
162 pub fn new(mut self, is_new: bool) -> Self {
164 self.new = Some(is_new);
165 self
166 }
167
168 pub fn vip(mut self, is_vip: bool) -> Self {
170 self.vip = Some(is_vip);
171 self
172 }
173
174 pub fn customer_type(mut self, customer_type: impl Into<String>) -> Self {
176 self.customer_type = Some(customer_type.into());
177 self
178 }
179
180 pub fn risk_level(mut self, level: RiskLevel) -> Self {
182 self.risk_level = Some(level);
183 self
184 }
185
186 pub fn risk_score(mut self, score: f64) -> Result<Self, ValidationError> {
188 self.risk_score = Some(RiskScore::new(score)?);
189 Ok(self)
190 }
191
192 pub fn mother_name(mut self, name: impl Into<String>) -> Result<Self, ValidationError> {
194 self.mother_name = Some(CustomerName::try_new(name)?);
195 Ok(self)
196 }
197
198 pub fn build(self) -> Result<Customer, ValidationError> {
204 let id = self
205 .id
206 .ok_or_else(|| ValidationError::EmptyField("customer_id (required)".to_string()))?;
207
208 let name = self
209 .name
210 .ok_or_else(|| ValidationError::EmptyField("customer_id (required)".to_string()))?;
211
212 let email = self
213 .email
214 .ok_or_else(|| ValidationError::EmptyField("customer_id (required)".to_string()))?;
215
216 Ok(Customer {
217 id,
218 name,
219 email,
220 phone1: self.phone1,
221 phone2: self.phone2,
222 tax_id: self.tax_id,
223 document_type: self.document_type,
224 dob: self.dob,
225 created_at: self.created_at,
226 new: self.new,
227 vip: self.vip,
228 customer_type: self.customer_type,
229 risk_level: self.risk_level,
230 risk_score: self.risk_score,
231 mother_name: self.mother_name,
232 })
233 }
234}
235
236#[cfg(test)]
237mod tests {
238 use super::*;
239 use crate::customer_id::CustomerId;
240 use crate::types::validation_errors::ValidationError;
241
242 #[test]
243 fn test_customer_id_valid() {
244 let id = CustomerId::try_new("CUSTOMER123").unwrap();
245 assert_eq!(id.as_str(), "CUSTOMER123");
246 }
247
248 #[test]
249 fn test_customer_id_empty() {
250 let result = CustomerId::try_new("");
251 assert!(result.is_err());
252 }
253
254 #[test]
255 fn test_customer_id_too_long() {
256 let long_id = "A".repeat(101);
257 let result = CustomerId::try_new(long_id);
258 assert!(result.is_err());
259 }
260
261 #[test]
262 fn test_customer_name_valid() {
263 let name = CustomerName::try_new("João Silva").unwrap();
264 assert_eq!(name.as_str(), "João Silva");
265 }
266
267 #[test]
268 fn test_customer_name_trim() {
269 let name = CustomerName::try_new(" João Silva ").unwrap();
270 assert_eq!(name.as_str(), "João Silva");
271 }
272
273 #[test]
274 fn test_email_valid() {
275 let email = CustomerEmail::new("joao@example.com").unwrap();
276 assert_eq!(email.as_str(), "joao@example.com");
277 }
278
279 #[test]
280 fn test_email_lowercase() {
281 let email = CustomerEmail::new("JOAO@EXAMPLE.COM").unwrap();
282 assert_eq!(email.as_str(), "joao@example.com");
283 }
284
285 #[test]
286 fn test_email_invalid() {
287 assert!(CustomerEmail::new("invalid").is_err());
288 assert!(CustomerEmail::new("invalid@").is_err());
289 assert!(CustomerEmail::new("@invalid").is_err());
290 }
291
292 #[test]
293 fn test_date_iso8601_valid() {
294 let date = DateIso8601::new("2025-11-15").unwrap();
295 assert_eq!(date.as_str(), "2025-11-15");
296 }
297
298 #[test]
299 fn test_date_iso8601_invalid() {
300 assert!(DateIso8601::new("2025/11/15").is_err());
301 assert!(DateIso8601::new("15-11-2025").is_err());
302 assert!(DateIso8601::new("2025-11").is_err());
303 }
304
305 #[test]
306 fn test_risk_score_valid() {
307 let score = RiskScore::new(0.5).unwrap();
308 assert_eq!(score.value(), 0.5);
309 }
310
311 #[test]
312 fn test_risk_score_out_of_range() {
313 assert!(RiskScore::new(-0.1).is_err());
314 assert!(RiskScore::new(1.1).is_err());
315 }
316
317 #[test]
318 fn test_customer_builder_complete() -> Result<(), ValidationError> {
319 let customer = Customer::builder()
320 .id("CUSTOMER123")?
321 .name("João Silva")?
322 .email("joao@example.com")?
323 .phone1("+55 11 98765-4321")?
324 .tax_id("80.232.738/0001-13")?
325 .document_type(DocumentType::Cnpj)
326 .dob("1990-01-15")?
327 .created_at("2020-05-10")?
328 .new(false)
329 .vip(true)
330 .customer_type("premium")
331 .risk_level(RiskLevel::Low)
332 .risk_score(0.2)?
333 .mother_name("Maria Silva")?
334 .build()?;
335
336 assert_eq!(customer.id.as_str(), "CUSTOMER123");
337 assert_eq!(customer.name.as_str(), "João Silva");
338 assert_eq!(customer.email.as_str(), "joao@example.com");
339 assert!(customer.vip.unwrap());
340
341 Ok(())
342 }
343
344 #[test]
345 fn test_customer_builder_minimal() -> Result<(), ValidationError> {
346 let customer = Customer::builder()
347 .id("CUSTOMER456")?
348 .name("some name")?
349 .email("some@email.com")?
350 .build()?;
351
352 assert_eq!(customer.id.as_str(), "CUSTOMER456");
353 assert_eq!(customer.name.as_str(), "some name");
354 assert_eq!(customer.email.as_str(), "some@email.com");
355
356 Ok(())
357 }
358
359 #[test]
360 fn test_customer_builder_no_id() {
361 let result = Customer::builder().build();
362 assert!(result.is_err());
363 }
364
365 #[test]
366 fn test_serde_serialization() -> Result<(), Box<dyn std::error::Error>> {
367 let customer = Customer::builder()
368 .id("TEST123")?
369 .name("Test User")?
370 .email("test@example.com")?
371 .tax_id("47568576043")?
372 .build()?;
373
374 let json = serde_json::to_string(&customer)?;
375 assert!(json.contains("TEST123"));
376 assert!(json.contains("test@example.com"));
377
378 Ok(())
379 }
380
381 #[test]
382 fn test_serde_deserialization() -> Result<(), Box<dyn std::error::Error>> {
383 let json = r#"{
384 "id": "CUSTOMER789",
385 "name": "Maria Santos",
386 "email": "maria@example.com",
387 "tax_id": "98765432100"
388 }"#;
389
390 let customer: Customer = serde_json::from_str(json)?;
391 assert_eq!(customer.id.as_str(), "CUSTOMER789");
392 assert_eq!(customer.name.as_str(), "Maria Santos");
393
394 Ok(())
395 }
396
397 #[test]
398 fn test_document_type_serialization() -> Result<(), Box<dyn std::error::Error>> {
399 assert_eq!(serde_json::to_string(&DocumentType::Cpf)?, "\"CPF\"");
400 assert_eq!(serde_json::to_string(&DocumentType::Cnpj)?, "\"CNPJ\"");
401 assert_eq!(
402 serde_json::to_string(&DocumentType::CpfCnpj)?,
403 "\"CPF_CNPJ\""
404 );
405
406 Ok(())
407 }
408
409 #[test]
410 fn test_risk_level_serialization() -> Result<(), Box<dyn std::error::Error>> {
411 assert_eq!(serde_json::to_string(&RiskLevel::Low)?, "\"low\"");
412 assert_eq!(serde_json::to_string(&RiskLevel::Medium)?, "\"medium\"");
413 assert_eq!(serde_json::to_string(&RiskLevel::High)?, "\"high\"");
414
415 Ok(())
416 }
417}