1use crate::error::{ValidationError, ValidationResult};
7use serde::{Deserialize, Serialize};
8use std::fmt;
9
10#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
53pub struct Address {
54 pub formatted: Option<String>,
55 #[serde(rename = "streetAddress")]
56 pub street_address: Option<String>,
57 pub locality: Option<String>,
58 pub region: Option<String>,
59 #[serde(rename = "postalCode")]
60 pub postal_code: Option<String>,
61 pub country: Option<String>,
62 #[serde(rename = "type")]
63 pub address_type: Option<String>,
64 pub primary: Option<bool>,
65}
66
67impl Address {
68 pub fn new(
89 formatted: Option<String>,
90 street_address: Option<String>,
91 locality: Option<String>,
92 region: Option<String>,
93 postal_code: Option<String>,
94 country: Option<String>,
95 address_type: Option<String>,
96 primary: Option<bool>,
97 ) -> ValidationResult<Self> {
98 if let Some(ref f) = formatted {
100 Self::validate_address_component(f, "formatted")?;
101 }
102 if let Some(ref sa) = street_address {
103 Self::validate_address_component(sa, "streetAddress")?;
104 }
105 if let Some(ref l) = locality {
106 Self::validate_address_component(l, "locality")?;
107 }
108 if let Some(ref r) = region {
109 Self::validate_address_component(r, "region")?;
110 }
111 if let Some(ref pc) = postal_code {
112 Self::validate_address_component(pc, "postalCode")?;
113 }
114 if let Some(ref c) = country {
115 Self::validate_country_code(c)?;
116 }
117 if let Some(ref at) = address_type {
118 Self::validate_address_type(at)?;
119 }
120
121 if formatted.is_none()
123 && street_address.is_none()
124 && locality.is_none()
125 && region.is_none()
126 && postal_code.is_none()
127 && country.is_none()
128 {
129 return Err(ValidationError::custom(
130 "At least one address component must be provided",
131 ));
132 }
133
134 Ok(Self {
135 formatted,
136 street_address,
137 locality,
138 region,
139 postal_code,
140 country,
141 address_type,
142 primary,
143 })
144 }
145
146 pub fn new_simple(
161 street_address: String,
162 locality: String,
163 country: String,
164 ) -> ValidationResult<Self> {
165 Self::new(
166 None,
167 Some(street_address),
168 Some(locality),
169 None,
170 None,
171 Some(country),
172 None,
173 None,
174 )
175 }
176
177 pub fn new_work(
194 street_address: String,
195 locality: String,
196 region: String,
197 postal_code: String,
198 country: String,
199 ) -> ValidationResult<Self> {
200 Self::new(
201 None,
202 Some(street_address),
203 Some(locality),
204 Some(region),
205 Some(postal_code),
206 Some(country),
207 Some("work".to_string()),
208 None,
209 )
210 }
211
212 pub fn formatted(&self) -> Option<&str> {
222 self.formatted.as_deref()
223 }
224
225 pub fn street_address(&self) -> Option<&str> {
227 self.street_address.as_deref()
228 }
229
230 pub fn locality(&self) -> Option<&str> {
232 self.locality.as_deref()
233 }
234
235 pub fn region(&self) -> Option<&str> {
237 self.region.as_deref()
238 }
239
240 pub fn postal_code(&self) -> Option<&str> {
242 self.postal_code.as_deref()
243 }
244
245 pub fn country(&self) -> Option<&str> {
247 self.country.as_deref()
248 }
249
250 pub fn address_type(&self) -> Option<&str> {
252 self.address_type.as_deref()
253 }
254
255 pub fn is_primary(&self) -> bool {
257 self.primary.unwrap_or(false)
258 }
259
260 pub fn display_address(&self) -> Option<String> {
270 if let Some(ref formatted) = self.formatted {
271 return Some(formatted.clone());
272 }
273
274 let mut parts = Vec::new();
275
276 if let Some(ref street) = self.street_address {
277 parts.push(street.as_str());
278 }
279
280 let mut city_line = Vec::new();
281 if let Some(ref locality) = self.locality {
282 city_line.push(locality.as_str());
283 }
284 if let Some(ref region) = self.region {
285 city_line.push(region.as_str());
286 }
287 if let Some(ref postal) = self.postal_code {
288 city_line.push(postal.as_str());
289 }
290
291 let city_line_str = if !city_line.is_empty() {
292 Some(city_line.join(", "))
293 } else {
294 None
295 };
296
297 if let Some(ref city_str) = city_line_str {
298 parts.push(city_str.as_str());
299 }
300
301 if let Some(ref country) = self.country {
302 parts.push(country.as_str());
303 }
304
305 if parts.is_empty() {
306 None
307 } else {
308 Some(parts.join("\n"))
309 }
310 }
311
312 pub fn is_empty(&self) -> bool {
314 self.formatted.is_none()
315 && self.street_address.is_none()
316 && self.locality.is_none()
317 && self.region.is_none()
318 && self.postal_code.is_none()
319 && self.country.is_none()
320 }
321
322 fn validate_address_component(value: &str, field_name: &str) -> ValidationResult<()> {
324 if value.trim().is_empty() {
325 return Err(ValidationError::custom(format!(
326 "{}: Address component cannot be empty or contain only whitespace",
327 field_name
328 )));
329 }
330
331 if value.len() > 1024 {
333 return Err(ValidationError::custom(format!(
334 "{}: Address component exceeds maximum length of 1024 characters",
335 field_name
336 )));
337 }
338
339 Ok(())
340 }
341
342 fn validate_country_code(country: &str) -> ValidationResult<()> {
344 if country.trim().is_empty() {
345 return Err(ValidationError::custom(
346 "country: Country code cannot be empty",
347 ));
348 }
349
350 if country.len() != 2 {
352 return Err(ValidationError::custom(
353 "country: Country code must be exactly 2 characters (ISO 3166-1 alpha-2 format)",
354 ));
355 }
356
357 if !country.chars().all(|c| c.is_ascii_alphabetic()) {
359 return Err(ValidationError::custom(
360 "country: Country code must contain only alphabetic characters",
361 ));
362 }
363
364 let country_upper = country.to_uppercase();
366
367 let valid_codes = [
370 "AD", "AE", "AF", "AG", "AI", "AL", "AM", "AO", "AQ", "AR", "AS", "AT", "AU", "AW",
371 "AX", "AZ", "BA", "BB", "BD", "BE", "BF", "BG", "BH", "BI", "BJ", "BL", "BM", "BN",
372 "BO", "BQ", "BR", "BS", "BT", "BV", "BW", "BY", "BZ", "CA", "CC", "CD", "CF", "CG",
373 "CH", "CI", "CK", "CL", "CM", "CN", "CO", "CR", "CU", "CV", "CW", "CX", "CY", "CZ",
374 "DE", "DJ", "DK", "DM", "DO", "DZ", "EC", "EE", "EG", "EH", "ER", "ES", "ET", "FI",
375 "FJ", "FK", "FM", "FO", "FR", "GA", "GB", "GD", "GE", "GF", "GG", "GH", "GI", "GL",
376 "GM", "GN", "GP", "GQ", "GR", "GS", "GT", "GU", "GW", "GY", "HK", "HM", "HN", "HR",
377 "HT", "HU", "ID", "IE", "IL", "IM", "IN", "IO", "IQ", "IR", "IS", "IT", "JE", "JM",
378 "JO", "JP", "KE", "KG", "KH", "KI", "KM", "KN", "KP", "KR", "KW", "KY", "KZ", "LA",
379 "LB", "LC", "LI", "LK", "LR", "LS", "LT", "LU", "LV", "LY", "MA", "MC", "MD", "ME",
380 "MF", "MG", "MH", "MK", "ML", "MM", "MN", "MO", "MP", "MQ", "MR", "MS", "MT", "MU",
381 "MV", "MW", "MX", "MY", "MZ", "NA", "NC", "NE", "NF", "NG", "NI", "NL", "NO", "NP",
382 "NR", "NU", "NZ", "OM", "PA", "PE", "PF", "PG", "PH", "PK", "PL", "PM", "PN", "PR",
383 "PS", "PT", "PW", "PY", "QA", "RE", "RO", "RS", "RU", "RW", "SA", "SB", "SC", "SD",
384 "SE", "SG", "SH", "SI", "SJ", "SK", "SL", "SM", "SN", "SO", "SR", "SS", "ST", "SV",
385 "SX", "SY", "SZ", "TC", "TD", "TF", "TG", "TH", "TJ", "TK", "TL", "TM", "TN", "TO",
386 "TR", "TT", "TV", "TW", "TZ", "UA", "UG", "UM", "US", "UY", "UZ", "VA", "VC", "VE",
387 "VG", "VI", "VN", "VU", "WF", "WS", "YE", "YT", "ZA", "ZM", "ZW",
388 ];
389
390 if !valid_codes.contains(&country_upper.as_str()) {
391 return Err(ValidationError::custom(format!(
392 "country: '{}' is not a valid ISO 3166-1 alpha-2 country code",
393 country
394 )));
395 }
396
397 Ok(())
398 }
399
400 fn validate_address_type(address_type: &str) -> ValidationResult<()> {
402 if address_type.trim().is_empty() {
403 return Err(ValidationError::custom(
404 "type: Address type cannot be empty",
405 ));
406 }
407
408 let valid_types = ["work", "home", "other"];
410 if !valid_types.contains(&address_type) {
411 return Err(ValidationError::custom(format!(
412 "type: '{}' is not a valid address type. Valid types are: {:?}",
413 address_type, valid_types
414 )));
415 }
416
417 Ok(())
418 }
419}
420
421impl fmt::Display for Address {
422 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
423 match self.display_address() {
424 Some(address) => {
425 if let Some(address_type) = &self.address_type {
426 write!(f, "{} ({})", address, address_type)
427 } else {
428 write!(f, "{}", address)
429 }
430 }
431 None => write!(f, "[Empty Address]"),
432 }
433 }
434}
435
436#[cfg(test)]
437mod tests {
438 use super::*;
439
440 #[test]
441 fn test_valid_address_full() {
442 let address = Address::new(
443 Some("100 Universal City Plaza\nHollywood, CA 91608 USA".to_string()),
444 Some("100 Universal City Plaza".to_string()),
445 Some("Hollywood".to_string()),
446 Some("CA".to_string()),
447 Some("91608".to_string()),
448 Some("US".to_string()),
449 Some("work".to_string()),
450 Some(true),
451 );
452
453 assert!(address.is_ok());
454 let address = address.unwrap();
455 assert_eq!(
456 address.formatted(),
457 Some("100 Universal City Plaza\nHollywood, CA 91608 USA")
458 );
459 assert_eq!(address.street_address(), Some("100 Universal City Plaza"));
460 assert_eq!(address.locality(), Some("Hollywood"));
461 assert_eq!(address.region(), Some("CA"));
462 assert_eq!(address.postal_code(), Some("91608"));
463 assert_eq!(address.country(), Some("US"));
464 assert_eq!(address.address_type(), Some("work"));
465 assert!(address.is_primary());
466 }
467
468 #[test]
469 fn test_valid_address_simple() {
470 let address = Address::new_simple(
471 "123 Main St".to_string(),
472 "Anytown".to_string(),
473 "US".to_string(),
474 );
475
476 assert!(address.is_ok());
477 let address = address.unwrap();
478 assert_eq!(address.street_address(), Some("123 Main St"));
479 assert_eq!(address.locality(), Some("Anytown"));
480 assert_eq!(address.country(), Some("US"));
481 assert!(!address.is_primary());
482 }
483
484 #[test]
485 fn test_valid_address_work() {
486 let address = Address::new_work(
487 "456 Business Ave".to_string(),
488 "Corporate City".to_string(),
489 "NY".to_string(),
490 "10001".to_string(),
491 "US".to_string(),
492 );
493
494 assert!(address.is_ok());
495 let address = address.unwrap();
496 assert_eq!(address.address_type(), Some("work"));
497 assert_eq!(address.region(), Some("NY"));
498 assert_eq!(address.postal_code(), Some("10001"));
499 }
500
501 #[test]
502 fn test_empty_address_components() {
503 let result = Address::new(
504 Some("".to_string()),
505 None,
506 None,
507 None,
508 None,
509 None,
510 None,
511 None,
512 );
513 assert!(result.is_err());
514 }
515
516 #[test]
517 fn test_all_none_components() {
518 let result = Address::new(None, None, None, None, None, None, None, None);
519 assert!(result.is_err());
520 assert!(
521 result
522 .unwrap_err()
523 .to_string()
524 .contains("At least one address component")
525 );
526 }
527
528 #[test]
529 fn test_invalid_country_code() {
530 let result = Address::new_simple(
531 "123 Main St".to_string(),
532 "Anytown".to_string(),
533 "USA".to_string(), );
535 assert!(result.is_err());
536 assert!(
537 result
538 .unwrap_err()
539 .to_string()
540 .contains("must be exactly 2 characters")
541 );
542 }
543
544 #[test]
545 fn test_invalid_country_code_non_alphabetic() {
546 let result = Address::new_simple(
547 "123 Main St".to_string(),
548 "Anytown".to_string(),
549 "U1".to_string(),
550 );
551 assert!(result.is_err());
552 assert!(
553 result
554 .unwrap_err()
555 .to_string()
556 .contains("must contain only alphabetic")
557 );
558 }
559
560 #[test]
561 fn test_invalid_country_code_unknown() {
562 let result = Address::new_simple(
563 "123 Main St".to_string(),
564 "Anytown".to_string(),
565 "XX".to_string(),
566 );
567 assert!(result.is_err());
568 assert!(
569 result
570 .unwrap_err()
571 .to_string()
572 .contains("not a valid ISO 3166-1")
573 );
574 }
575
576 #[test]
577 fn test_invalid_address_type() {
578 let result = Address::new(
579 None,
580 Some("123 Main St".to_string()),
581 Some("Anytown".to_string()),
582 None,
583 None,
584 Some("US".to_string()),
585 Some("business".to_string()), None,
587 );
588 assert!(result.is_err());
589 assert!(
590 result
591 .unwrap_err()
592 .to_string()
593 .contains("not a valid address type")
594 );
595 }
596
597 #[test]
598 fn test_too_long_component() {
599 let long_street = "a".repeat(1100);
600 let result = Address::new_simple(long_street, "Anytown".to_string(), "US".to_string());
601 assert!(result.is_err());
602 assert!(
603 result
604 .unwrap_err()
605 .to_string()
606 .contains("exceeds maximum length")
607 );
608 }
609
610 #[test]
611 fn test_display_address_with_formatted() {
612 let address = Address::new(
613 Some("100 Main St\nAnytown, NY 12345\nUSA".to_string()),
614 None,
615 None,
616 None,
617 None,
618 None,
619 None,
620 None,
621 )
622 .unwrap();
623
624 assert_eq!(
625 address.display_address(),
626 Some("100 Main St\nAnytown, NY 12345\nUSA".to_string())
627 );
628 }
629
630 #[test]
631 fn test_display_address_from_components() {
632 let address = Address::new(
633 None,
634 Some("123 Main St".to_string()),
635 Some("Anytown".to_string()),
636 Some("NY".to_string()),
637 Some("12345".to_string()),
638 Some("US".to_string()),
639 None,
640 None,
641 )
642 .unwrap();
643
644 assert_eq!(
645 address.display_address(),
646 Some("123 Main St\nAnytown, NY, 12345\nUS".to_string())
647 );
648 }
649
650 #[test]
651 fn test_display_address_partial_components() {
652 let address = Address::new(
653 None,
654 Some("456 Oak Ave".to_string()),
655 Some("Springfield".to_string()),
656 None,
657 None,
658 Some("US".to_string()),
659 None,
660 None,
661 )
662 .unwrap();
663
664 assert_eq!(
665 address.display_address(),
666 Some("456 Oak Ave\nSpringfield\nUS".to_string())
667 );
668 }
669
670 #[test]
671 fn test_display() {
672 let address = Address::new_work(
673 "100 Business Blvd".to_string(),
674 "Corporate City".to_string(),
675 "NY".to_string(),
676 "10001".to_string(),
677 "US".to_string(),
678 )
679 .unwrap();
680
681 let display = format!("{}", address);
682 assert!(display.contains("100 Business Blvd"));
683 assert!(display.contains("(work)"));
684
685 let minimal_address = Address::new_simple(
687 "123 Main St".to_string(),
688 "Anytown".to_string(),
689 "US".to_string(),
690 )
691 .unwrap();
692 let minimal_display = format!("{}", minimal_address);
693 assert!(minimal_display.contains("123 Main St"));
694 assert!(minimal_display.contains("Anytown"));
695 }
696
697 #[test]
698 fn test_serialization() {
699 let address = Address::new_work(
700 "100 Business Blvd".to_string(),
701 "Corporate City".to_string(),
702 "NY".to_string(),
703 "10001".to_string(),
704 "US".to_string(),
705 )
706 .unwrap();
707
708 let json = serde_json::to_string(&address).unwrap();
709 assert!(json.contains("\"streetAddress\":\"100 Business Blvd\""));
710 assert!(json.contains("\"locality\":\"Corporate City\""));
711 assert!(json.contains("\"type\":\"work\""));
712 }
713
714 #[test]
715 fn test_deserialization() {
716 let json = r#"{
717 "formatted": "100 Universal City Plaza\nHollywood, CA 91608 USA",
718 "streetAddress": "100 Universal City Plaza",
719 "locality": "Hollywood",
720 "region": "CA",
721 "postalCode": "91608",
722 "country": "US",
723 "type": "work",
724 "primary": true
725 }"#;
726
727 let address: Address = serde_json::from_str(json).unwrap();
728 assert_eq!(address.street_address(), Some("100 Universal City Plaza"));
729 assert_eq!(address.locality(), Some("Hollywood"));
730 assert_eq!(address.country(), Some("US"));
731 assert_eq!(address.address_type(), Some("work"));
732 assert!(address.is_primary());
733 }
734
735 #[test]
736 fn test_equality() {
737 let addr1 = Address::new_simple(
738 "123 Main St".to_string(),
739 "Anytown".to_string(),
740 "US".to_string(),
741 )
742 .unwrap();
743 let addr2 = Address::new_simple(
744 "123 Main St".to_string(),
745 "Anytown".to_string(),
746 "US".to_string(),
747 )
748 .unwrap();
749 let addr3 = Address::new_simple(
750 "456 Oak Ave".to_string(),
751 "Anytown".to_string(),
752 "US".to_string(),
753 )
754 .unwrap();
755
756 assert_eq!(addr1, addr2);
757 assert_ne!(addr1, addr3);
758 }
759
760 #[test]
761 fn test_clone() {
762 let original = Address::new_work(
763 "100 Business Blvd".to_string(),
764 "Corporate City".to_string(),
765 "NY".to_string(),
766 "10001".to_string(),
767 "US".to_string(),
768 )
769 .unwrap();
770
771 let cloned = original.clone();
772 assert_eq!(original, cloned);
773 assert_eq!(cloned.street_address(), Some("100 Business Blvd"));
774 assert_eq!(cloned.address_type(), Some("work"));
775 }
776
777 #[test]
778 fn test_country_code_case_insensitive() {
779 let address = Address::new_simple(
780 "123 Main St".to_string(),
781 "Anytown".to_string(),
782 "us".to_string(), );
784 assert!(address.is_ok());
785
786 let address = Address::new_simple(
787 "123 Main St".to_string(),
788 "Anytown".to_string(),
789 "Us".to_string(), );
791 assert!(address.is_ok());
792 }
793
794 #[test]
795 fn test_valid_address_types() {
796 for addr_type in ["work", "home", "other"] {
797 let address = Address::new(
798 None,
799 Some("123 Main St".to_string()),
800 Some("Anytown".to_string()),
801 None,
802 None,
803 Some("US".to_string()),
804 Some(addr_type.to_string()),
805 None,
806 );
807 assert!(
808 address.is_ok(),
809 "Address type '{}' should be valid",
810 addr_type
811 );
812 }
813 }
814}