1use std::str::FromStr;
2
3use serde::{Deserialize, Serialize};
4
5use crate::{
6 Country, TaxReductions, Taxes,
7 constants::{DEFAULT_SE_TAX_REDUCTIONS, DEFAULT_SE_TAXES, REDUCED_SE_TAXES},
8};
9
10#[derive(Debug, thiserror::Error)]
12pub enum LauParseError {
13 #[error("Unsupported or invalid country code")]
14 UnsupportedCountryCode,
15 #[error("Invalid LAU code")]
16 InvalidCode,
17}
18
19#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
20#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
21pub struct LocalAdministrativeUnit {
22 code: &'static str,
23 name: &'static str,
24 country: Country,
25}
26
27impl LocalAdministrativeUnit {
28 pub fn eu_code(&self) -> String {
29 format!("{}_{:0width$}", self.country.code(), self.code, width = 4)
30 }
31
32 pub fn local_code(&self) -> &str {
33 self.code
34 }
35
36 pub fn info(&'static self) -> LocalAdministrativeUnitInfo {
37 self.into()
38 }
39
40 pub fn name(&self) -> &str {
41 self.name
42 }
43
44 pub fn country(&self) -> Country {
45 self.country
46 }
47
48 pub fn get(country: Country, code: &str) -> Option<&'static Self> {
49 match country {
50 Country::SE => registry::SE.get(code),
51 }
52 }
53
54 pub fn taxes(&self) -> Taxes {
55 match self.country {
56 Country::SE => {
57 let code = self.code.parse().unwrap();
58 if registry::SE_REDUCED_ENERGY_TAX_SUBDIVISIONS.contains(&code) {
59 REDUCED_SE_TAXES
60 } else {
61 DEFAULT_SE_TAXES
62 }
63 }
64 }
65 }
66
67 pub fn tax_reductions(&self) -> TaxReductions {
68 match self.country {
69 Country::SE => DEFAULT_SE_TAX_REDUCTIONS,
70 }
71 }
72
73 pub fn from_eu_code(value: &str) -> Result<&'static Self, LauParseError> {
74 let country: Country = value[0..2]
75 .parse()
76 .map_err(|_| LauParseError::UnsupportedCountryCode)?;
77 let lau_code = &value[3..];
78 match country {
79 Country::SE => registry::SE.get(lau_code).ok_or(LauParseError::InvalidCode),
80 }
81 }
82}
83
84impl Serialize for LocalAdministrativeUnit {
85 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
86 where
87 S: serde::Serializer,
88 {
89 serializer.serialize_str(&self.eu_code())
90 }
91}
92
93impl FromStr for LocalAdministrativeUnit {
94 type Err = LauParseError;
95
96 fn from_str(eu_lau_code: &str) -> Result<Self, Self::Err> {
97 Self::from_eu_code(eu_lau_code).copied()
98 }
99}
100
101impl<'de> Deserialize<'de> for &'static LocalAdministrativeUnit {
102 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
103 where
104 D: serde::Deserializer<'de>,
105 {
106 let s = String::deserialize(deserializer)?;
107 LocalAdministrativeUnit::from_eu_code(&s).map_err(serde::de::Error::custom)
108 }
109}
110
111impl<'de> Deserialize<'de> for LocalAdministrativeUnit {
112 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
113 where
114 D: serde::Deserializer<'de>,
115 {
116 let static_ref = <&'static LocalAdministrativeUnit>::deserialize(deserializer)?;
117 Ok(*static_ref)
118 }
119}
120
121#[derive(Debug, Clone, Copy, Serialize)]
122pub struct LocalAdministrativeUnitInfo {
123 pub code: &'static str,
124 pub name: &'static str,
125 pub country: Country,
126 pub taxes: Taxes,
127 pub tax_reductions: TaxReductions,
128}
129
130impl LocalAdministrativeUnitInfo {
131 pub fn only_current(mut self) -> Self {
132 self.taxes = self.taxes.with_current_only();
133 self.tax_reductions = self.tax_reductions.with_current_only();
134 self
135 }
136}
137
138impl From<&'static LocalAdministrativeUnit> for LocalAdministrativeUnitInfo {
139 fn from(value: &'static LocalAdministrativeUnit) -> Self {
140 Self {
141 code: value.local_code(),
142 name: value.name(),
143 country: value.country(),
144 taxes: value.taxes(),
145 tax_reductions: value.tax_reductions(),
146 }
147 }
148}
149
150pub(crate) mod registry {
151 static SE_DATA: &str = include_str!("../data/EU-27-LAU-2024-NUTS-2024-SE.csv");
152 use std::{
153 collections::{HashMap, HashSet},
154 str::FromStr,
155 sync::LazyLock,
156 };
157
158 use crate::{Country, local_administrative_unit::LocalAdministrativeUnit};
159
160 pub(crate) static SE: LazyLock<HashMap<&str, LocalAdministrativeUnit>> = LazyLock::new(|| {
161 let mut map = HashMap::with_capacity(290);
162
163 for line in SE_DATA.lines().skip(1) {
165 let mut parts = line.split(',');
167
168 let nuts_3_code = parts.next().unwrap();
169 let lau_code = parts.next().unwrap();
170 let name = parts.next().unwrap();
171 let country = Country::from_str(&nuts_3_code[0..2]).unwrap();
172 map.insert(
173 lau_code,
174 LocalAdministrativeUnit {
175 code: lau_code,
176 name,
177 country,
178 },
179 );
180 }
181 map
182 });
183
184 pub(crate) static SE_REDUCED_ENERGY_TAX_SUBDIVISIONS: LazyLock<HashSet<u16>> =
185 LazyLock::new(|| {
186 HashSet::from_iter([
187 2506, 2505, 2326, 2403, 2582, 2305, 2425, 2523, 2583, 2361, 2510, 2514, 2584, 2309,
188 2161, 2580, 2481, 2023, 2418, 2062, 2401, 2417, 2034, 2521, 2581, 2303, 2409, 2482,
189 2283, 2422, 2421, 2313, 1737, 2480, 2462, 2404, 2460, 2260, 2321, 2463, 2039, 2560,
190 2284, 2380, 2513, 2518,
191 ])
192 });
193}
194
195#[cfg(test)]
196mod tests {
197 use super::*;
198 use std::str::FromStr;
199
200 #[test]
201 fn test_eu_code_formatting() {
202 let lau = LocalAdministrativeUnit::get(Country::SE, "0180").unwrap();
204 assert_eq!(lau.eu_code(), "SE_0180");
205 }
206
207 #[test]
208 fn test_local_code() {
209 let lau = LocalAdministrativeUnit::get(Country::SE, "0180").unwrap();
210 assert_eq!(lau.local_code(), "0180");
211 }
212
213 #[test]
214 fn test_name() {
215 let lau = LocalAdministrativeUnit::get(Country::SE, "0180").unwrap();
216 assert_eq!(lau.name(), "Stockholm");
217 }
218
219 #[test]
220 fn test_country() {
221 let lau = LocalAdministrativeUnit::get(Country::SE, "0180").unwrap();
222 assert_eq!(lau.country(), Country::SE);
223 }
224
225 #[test]
226 fn test_get_valid_code() {
227 let lau = LocalAdministrativeUnit::get(Country::SE, "0180");
228 assert!(lau.is_some());
229 }
230
231 #[test]
232 fn test_get_invalid_code() {
233 let lau = LocalAdministrativeUnit::get(Country::SE, "9999");
234 assert!(lau.is_none());
235 }
236
237 #[test]
238 fn test_taxes_default() {
239 let lau = LocalAdministrativeUnit::get(Country::SE, "0180").unwrap();
241 let taxes = lau.taxes();
242 assert_eq!(taxes, DEFAULT_SE_TAXES);
243 }
244
245 #[test]
246 fn test_taxes_reduced() {
247 let lau = LocalAdministrativeUnit::get(Country::SE, "2506").unwrap();
249 let taxes = lau.taxes();
250 assert_eq!(taxes, REDUCED_SE_TAXES);
251 assert_ne!(taxes, DEFAULT_SE_TAXES);
252 }
253
254 #[test]
255 fn test_tax_reductions() {
256 let lau = LocalAdministrativeUnit::get(Country::SE, "0180").unwrap();
257 let tax_reductions = lau.tax_reductions();
258 assert_eq!(tax_reductions, DEFAULT_SE_TAX_REDUCTIONS);
259 }
260
261 #[test]
262 fn test_from_eu_code_valid() {
263 let result = LocalAdministrativeUnit::from_eu_code("SE_0180");
264 assert!(result.is_ok());
265 let lau = result.unwrap();
266 assert_eq!(lau.local_code(), "0180");
267 assert_eq!(lau.name(), "Stockholm");
268 }
269
270 #[test]
271 fn test_from_eu_code_invalid_country() {
272 let result = LocalAdministrativeUnit::from_eu_code("XX_0180");
273 assert!(result.is_err());
274 assert!(matches!(
275 result.unwrap_err(),
276 LauParseError::UnsupportedCountryCode
277 ));
278 }
279
280 #[test]
281 fn test_from_eu_code_invalid_lau() {
282 let result = LocalAdministrativeUnit::from_eu_code("SE_9999");
283 assert!(result.is_err());
284 assert!(matches!(result.unwrap_err(), LauParseError::InvalidCode));
285 }
286
287 #[test]
288 fn test_from_str_valid() {
289 let result = LocalAdministrativeUnit::from_str("SE_0180");
290 assert!(result.is_ok());
291 let lau = result.unwrap();
292 assert_eq!(lau.local_code(), "0180");
293 }
294
295 #[test]
296 fn test_from_str_invalid() {
297 let result = LocalAdministrativeUnit::from_str("INVALID");
298 assert!(result.is_err());
299 }
300
301 #[test]
302 fn test_serialize() {
303 let lau = LocalAdministrativeUnit::get(Country::SE, "0180").unwrap();
304 let serialized = serde_json::to_string(lau).unwrap();
305 assert_eq!(serialized, "\"SE_0180\"");
306 }
307
308 #[test]
309 fn test_deserialize_static_ref() {
310 let json = "\"SE_0180\"";
311 let lau: &'static LocalAdministrativeUnit = serde_json::from_str(json).unwrap();
312 assert_eq!(lau.local_code(), "0180");
313 assert_eq!(lau.name(), "Stockholm");
314 }
315
316 #[test]
317 fn test_deserialize_owned() {
318 let json = "\"SE_0180\"";
319 let lau: LocalAdministrativeUnit = serde_json::from_str(json).unwrap();
320 assert_eq!(lau.local_code(), "0180");
321 assert_eq!(lau.name(), "Stockholm");
322 }
323
324 #[test]
325 fn test_deserialize_invalid() {
326 let json = "\"SE_9999\"";
327 let result: Result<&'static LocalAdministrativeUnit, _> = serde_json::from_str(json);
328 assert!(result.is_err());
329 }
330
331 #[test]
332 fn test_info_conversion() {
333 let lau = LocalAdministrativeUnit::get(Country::SE, "0180").unwrap();
334 let info = lau.info();
335 assert_eq!(info.code, "0180");
336 assert_eq!(info.name, "Stockholm");
337 assert_eq!(info.country, Country::SE);
338 assert_eq!(info.taxes, lau.taxes());
339 assert_eq!(info.tax_reductions, lau.tax_reductions());
340 }
341
342 #[test]
343 fn test_lau_ordering() {
344 let lau1 = LocalAdministrativeUnit::get(Country::SE, "0180").unwrap();
345 let lau2 = LocalAdministrativeUnit::get(Country::SE, "1480").unwrap();
346 assert!(lau1 < lau2 || lau1 > lau2 || lau1 == lau1);
348 }
349
350 #[test]
351 fn test_lau_hash() {
352 use std::collections::HashSet;
353 let lau1 = LocalAdministrativeUnit::get(Country::SE, "0180").unwrap();
354 let lau2 = LocalAdministrativeUnit::get(Country::SE, "0180").unwrap();
355
356 let mut set = HashSet::new();
357 set.insert(lau1);
358 assert!(set.contains(lau2));
360 }
361
362 #[test]
363 fn test_registry_loaded() {
364 let se_registry = &*registry::SE;
366 assert!(!se_registry.is_empty());
367 assert!(se_registry.contains_key("0180"));
369 }
370
371 #[test]
372 fn test_reduced_energy_tax_subdivisions() {
373 let reduced = &*registry::SE_REDUCED_ENERGY_TAX_SUBDIVISIONS;
375 assert!(!reduced.is_empty());
376 assert!(reduced.contains(&2506));
378 assert!(!reduced.contains(&180));
380 }
381
382 #[test]
383 fn test_roundtrip_serialization() {
384 let lau = LocalAdministrativeUnit::get(Country::SE, "0180").unwrap();
385 let serialized = serde_json::to_string(lau).unwrap();
386 let deserialized: LocalAdministrativeUnit = serde_json::from_str(&serialized).unwrap();
387 assert_eq!(lau.local_code(), deserialized.local_code());
388 assert_eq!(lau.name(), deserialized.name());
389 assert_eq!(lau.country(), deserialized.country());
390 }
391}