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