postal_code/
postal_code.rs1crate::ix!();
2
3#[derive(Builder,Debug,Hash,Clone,PartialEq,Eq,Serialize,Deserialize,Getters,Ord,PartialOrd)]
5#[builder(build_fn(error = "PostalCodeConstructionError",validate = "Self::validate"))]
6pub struct PostalCode {
7
8 #[getset(get = "pub")]
10 country: Country,
11
12 #[getset(get = "pub")]
14 code: String,
15}
16
17impl PostalCode {
18
19 pub fn new(country: Country, code: &str) -> Result<Self,PostalCodeConstructionError> {
20 PostalCodeBuilder::default()
21 .country(country)
22 .code(code.to_string())
23 .build()
24 }
25}
26
27impl std::ops::Deref for PostalCode {
28
29 type Target = String;
30
31 fn deref(&self) -> &Self::Target {
32 &self.code
33 }
34}
35
36impl PostalCodeBuilder {
37 pub fn validate(&self) -> Result<(), PostalCodeConstructionError> {
38 if self.country.is_none() || self.code.is_none() {
39 return Err(PostalCodeConstructionError::InvalidFormat {
40 attempted_code: "<unset>".to_string(),
41 attempted_country: None,
42 });
43 }
44 let country = self.country.as_ref().unwrap();
45 let code = self.code.as_ref().unwrap();
46
47 if let Some(validator) = country.get_postal_code_validator() {
48 if validator.validate(code) {
49 Ok(())
50 } else {
51 Err(PostalCodeConstructionError::InvalidFormat {
52 attempted_code: code.clone(),
53 attempted_country: Some(country.clone()),
54 })
55 }
56 } else {
57 Err(PostalCodeConstructionError::UnsupportedCountry {
58 attempted_country: country.clone(),
59 })
60 }
61 }
62}
63
64#[cfg(test)]
65mod postal_code_tests {
66 use super::*;
67 use country::Country;
68 use rand::Rng; #[test]
71 fn test_us_valid() {
72 let pc = PostalCode::new(Country::USA, "12345");
73 assert!(pc.is_ok());
74 assert_eq!(pc.unwrap().code(), "12345");
75 }
76
77 #[test]
78 fn test_us_valid_zip_plus4() {
79 let pc = PostalCode::new(Country::USA, "12345-6789");
80 assert!(pc.is_ok());
81 }
82
83 #[test]
84 fn test_us_invalid_alphabetic() {
85 let pc = PostalCode::new(Country::USA, "ABCDE");
86 assert!(pc.is_err());
87 if let Err(PostalCodeConstructionError::InvalidFormat { attempted_code, attempted_country }) = pc {
88 assert_eq!(attempted_code, "ABCDE");
89 assert_eq!(attempted_country, Some(Country::USA));
90 } else {
91 panic!("Unexpected error type");
92 }
93 }
94
95 #[test]
96 fn test_us_invalid_length() {
97 let pc = PostalCode::new(Country::USA, "1234");
98 assert!(pc.is_err());
99 }
100
101 #[test]
102 fn test_ca_valid() {
103 let pc = PostalCode::new(Country::Canada, "K1A0B1");
104 assert!(pc.is_ok());
105 }
106
107 #[test]
108 fn test_ca_valid_with_space() {
109 let pc = PostalCode::new(Country::Canada, "K1A 0B1");
111 assert!(pc.is_ok());
112 }
113
114 #[test]
115 fn test_ca_invalid() {
116 let pc = PostalCode::new(Country::Canada, "123456");
117 assert!(pc.is_err());
118 }
119
120 #[test]
121 fn test_ca_invalid_non_alphanumeric() {
122 let pc = PostalCode::new(Country::Canada, "K1A!0B1");
124 assert!(pc.is_err());
125 }
126
127 #[test]
128 fn test_uk_valid() {
129 let pc = PostalCode::new(Country::UnitedKingdom, "SW1A 1AA");
131 assert!(pc.is_ok());
132 }
133
134 #[test]
135 fn test_uk_invalid_no_space() {
136 let pc = PostalCode::new(Country::UnitedKingdom, "SW1A1AA");
138 assert!(pc.is_err());
139 }
140
141 #[test]
142 fn test_uk_invalid_too_long() {
143 let pc = PostalCode::new(Country::UnitedKingdom, "SW1A 1AAA");
144 assert!(pc.is_err());
145 }
146
147 #[test]
148 fn test_fr_valid() {
149 let pc = PostalCode::new(Country::France, "75001");
150 assert!(pc.is_ok());
151 }
152
153 #[test]
154 fn test_fr_invalid_short() {
155 let pc = PostalCode::new(Country::France, "7500");
156 assert!(pc.is_err());
157 }
158
159 #[test]
160 fn test_fr_invalid_alpha() {
161 let pc = PostalCode::new(Country::France, "75A01");
162 assert!(pc.is_err());
163 }
164
165 #[test]
166 fn test_de_valid() {
167 let pc = PostalCode::new(Country::Germany, "10115");
168 assert!(pc.is_ok());
169 }
170
171 #[test]
172 fn test_de_invalid_short() {
173 let pc = PostalCode::new(Country::Germany, "101");
174 assert!(pc.is_err());
175 }
176
177 #[test]
178 fn test_de_invalid_alpha() {
179 let pc = PostalCode::new(Country::Germany, "10A15");
180 assert!(pc.is_err());
181 }
182
183 #[test]
184 fn test_it_valid() {
185 let pc = PostalCode::new(Country::Italy, "00144");
186 assert!(pc.is_ok());
187 }
188
189 #[test]
190 fn test_it_invalid_short() {
191 let pc = PostalCode::new(Country::Italy, "0144");
192 assert!(pc.is_err());
193 }
194
195 #[test]
196 fn test_it_invalid_alpha() {
197 let pc = PostalCode::new(Country::Italy, "00A44");
198 assert!(pc.is_err());
199 }
200
201 #[test]
202 fn test_unsupported_country() {
203 let pc = PostalCode::new(Country::Uzbekistan, "12345");
204 assert!(pc.is_err());
205 if let Err(PostalCodeConstructionError::UnsupportedCountry { attempted_country }) = pc {
206 assert_eq!(attempted_country, Country::Uzbekistan);
207 } else {
208 panic!("Expected UnsupportedCountry error");
209 }
210 }
211
212 #[test]
213 fn test_missing_fields_via_builder() {
214 let pc = PostalCodeBuilder::default().build();
216 assert!(pc.is_err());
217 if let Err(PostalCodeConstructionError::InvalidFormat { attempted_code, attempted_country }) = pc {
218 assert_eq!(attempted_code, "<unset>");
219 assert_eq!(attempted_country, None);
220 } else {
221 panic!("Expected InvalidFormat error due to missing fields");
222 }
223 }
224
225 #[test]
226 fn test_multiple_random_us_codes() {
227 let mut rng = rand::thread_rng();
230 for _ in 0..10 {
231 let base: u32 = rng.gen_range(0..100000);
232 let code = format!("{:05}", base);
233 let pc = PostalCode::new(Country::USA, &code);
234 assert!(pc.is_ok());
235 }
236 for _ in 0..5 {
238 let code = format!("{}ABCD", rng.gen_range(0..10000));
239 let pc = PostalCode::new(Country::USA, &code);
240 assert!(pc.is_err());
241 }
242 }
243
244 #[test]
245 fn test_space_and_hyphen_tolerance_in_ca_codes() {
246 let pc = PostalCode::new(Country::Canada, "K1A-0B1");
248 assert!(pc.is_err());
249 }
250
251 #[test]
252 fn test_uk_gir_special_case() {
253 let pc = PostalCode::new(Country::UnitedKingdom, "GIR 0AA");
255 assert!(pc.is_ok());
256 }
257}