scim_server/resource/value_objects/
address.rs

1//! Address value object for SCIM user address components.
2//!
3//! This module provides a type-safe wrapper around SCIM address attributes with built-in validation.
4//! Address attributes represent physical mailing addresses as defined in RFC 7643 Section 4.1.2.
5
6use crate::error::{ValidationError, ValidationResult};
7use serde::{Deserialize, Serialize};
8use std::fmt;
9
10/// A validated SCIM address attribute.
11///
12/// Address represents a physical mailing address as defined in RFC 7643.
13/// It enforces validation rules at construction time, ensuring that only valid address
14/// attributes can exist in the system.
15///
16/// ## Validation Rules
17///
18/// - At least one address component must be provided (not all fields can be empty/None)
19/// - Individual address components cannot be empty strings
20/// - Country code must be valid ISO 3166-1 "alpha-2" format when provided
21/// - Type must be one of canonical values: "work", "home", "other" when provided
22/// - Primary can only be true for one address in a collection
23///
24/// ## Examples
25///
26/// ```rust
27/// use scim_server::resource::value_objects::Address;
28///
29/// fn main() -> Result<(), Box<dyn std::error::Error>> {
30///     // Create with full address components
31///     let address = Address::new(
32///         Some("100 Universal City Plaza\nHollywood, CA 91608 USA".to_string()),
33///         Some("100 Universal City Plaza".to_string()),
34///         Some("Hollywood".to_string()),
35///         Some("CA".to_string()),
36///         Some("91608".to_string()),
37///         Some("US".to_string()),
38///         Some("work".to_string()),
39///         Some(true)
40///     )?;
41///
42///     // Create with minimal components
43///     let simple_address = Address::new_simple(
44///         "123 Main St".to_string(),
45///         "Anytown".to_string(),
46///         "US".to_string()
47///     )?;
48///
49///     Ok(())
50/// }
51/// ```
52#[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    /// Create a new Address with all components.
69    ///
70    /// This is the primary constructor that enforces all validation rules.
71    /// Use this method when creating Address instances from untrusted input.
72    ///
73    /// # Arguments
74    ///
75    /// * `formatted` - The full mailing address, formatted for display
76    /// * `street_address` - The full street address component
77    /// * `locality` - The city or locality component
78    /// * `region` - The state or region component
79    /// * `postal_code` - The zip code or postal code component
80    /// * `country` - The country name component (ISO 3166-1 alpha-2)
81    /// * `address_type` - The type of address ("work", "home", "other")
82    /// * `primary` - Whether this is the primary address
83    ///
84    /// # Returns
85    ///
86    /// * `Ok(Address)` - If at least one field is provided and all provided fields are valid
87    /// * `Err(ValidationError)` - If all fields are None/empty or any field violates validation rules
88    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        // Validate individual components
99        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        // Ensure at least one meaningful component is provided
122        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    /// Create a simple Address with basic components.
147    ///
148    /// Convenience constructor for creating basic address structures.
149    ///
150    /// # Arguments
151    ///
152    /// * `street_address` - The street address
153    /// * `locality` - The city or locality
154    /// * `country` - The country code (ISO 3166-1 alpha-2)
155    ///
156    /// # Returns
157    ///
158    /// * `Ok(Address)` - If the address components are valid
159    /// * `Err(ValidationError)` - If any component violates validation rules
160    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    /// Create a work Address.
178    ///
179    /// Convenience constructor for work addresses.
180    ///
181    /// # Arguments
182    ///
183    /// * `street_address` - The street address
184    /// * `locality` - The city or locality
185    /// * `region` - The state or region
186    /// * `postal_code` - The postal code
187    /// * `country` - The country code (ISO 3166-1 alpha-2)
188    ///
189    /// # Returns
190    ///
191    /// * `Ok(Address)` - If the address components are valid
192    /// * `Err(ValidationError)` - If any component violates validation rules
193    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    /// Create an Address instance without validation for internal use.
213    ///
214    /// This method bypasses validation and should only be used when the data
215    /// is known to be valid, such as when deserializing from trusted sources.
216    ///
217    /// # Safety
218    ///
219
220    /// Get the formatted address.
221    pub fn formatted(&self) -> Option<&str> {
222        self.formatted.as_deref()
223    }
224
225    /// Get the street address.
226    pub fn street_address(&self) -> Option<&str> {
227        self.street_address.as_deref()
228    }
229
230    /// Get the locality.
231    pub fn locality(&self) -> Option<&str> {
232        self.locality.as_deref()
233    }
234
235    /// Get the region.
236    pub fn region(&self) -> Option<&str> {
237        self.region.as_deref()
238    }
239
240    /// Get the postal code.
241    pub fn postal_code(&self) -> Option<&str> {
242        self.postal_code.as_deref()
243    }
244
245    /// Get the country code.
246    pub fn country(&self) -> Option<&str> {
247        self.country.as_deref()
248    }
249
250    /// Get the address type.
251    pub fn address_type(&self) -> Option<&str> {
252        self.address_type.as_deref()
253    }
254
255    /// Get whether this is the primary address.
256    pub fn is_primary(&self) -> bool {
257        self.primary.unwrap_or(false)
258    }
259
260    /// Generate a formatted display address from components.
261    ///
262    /// Creates a formatted address string from the available address components
263    /// if no explicit formatted address is provided.
264    ///
265    /// # Returns
266    ///
267    /// The formatted address if available, otherwise a constructed address from components,
268    /// or None if no meaningful components are available.
269    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    /// Check if the address has any meaningful content.
313    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    /// Validate an address component.
323    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        // Check for reasonable length
332        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    /// Validate country code according to ISO 3166-1 alpha-2.
343    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        // Must be exactly 2 characters for ISO 3166-1 alpha-2
351        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        // Must be alphabetic
358        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        // Convert to uppercase for validation (we'll store as provided)
365        let country_upper = country.to_uppercase();
366
367        // Validate against common ISO 3166-1 alpha-2 codes
368        // This is a subset of the most common codes - in practice you might want a complete list
369        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    /// Validate address type against canonical values.
401    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        // SCIM canonical values for address type
409        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(), // Should be US
534        );
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()), // Should be work, home, or other
586            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        // Test display with minimal address
686        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(), // lowercase
783        );
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(), // mixed case
790        );
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}