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, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
11#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
12pub struct LocalAdministrativeUnit {
13 code: &'static str,
14 name: &'static str,
15 country: Country,
16}
17
18impl LocalAdministrativeUnit {
19 pub fn eu_code(&self) -> String {
20 format!("{}_{:0width$}", self.country.code(), self.code, width = 4)
21 }
22
23 pub fn local_code(&self) -> &str {
24 self.code
25 }
26
27 pub fn info(&'static self) -> LocalAdministrativeUnitInfo {
28 self.into()
29 }
30
31 pub fn name(&self) -> &str {
32 self.name
33 }
34
35 pub fn country(&self) -> Country {
36 self.country
37 }
38
39 pub fn get(country: Country, code: &str) -> Option<&'static Self> {
40 match country {
41 Country::SE => registry::SE.get(code),
42 }
43 }
44
45 pub fn taxes(&self) -> Taxes {
46 match self.country {
47 Country::SE => {
48 let code = self.code.parse().unwrap();
49 if registry::SE_REDUCED_ENERGY_TAX_SUBDIVISIONS.contains(&code) {
50 REDUCED_SE_TAXES
51 } else {
52 DEFAULT_SE_TAXES
53 }
54 }
55 }
56 }
57
58 pub fn tax_reductions(&self) -> TaxReductions {
59 match self.country {
60 Country::SE => DEFAULT_SE_TAX_REDUCTIONS,
61 }
62 }
63
64 pub fn from_eu_code(value: &str) -> Result<Self, &'static str> {
65 let country: Country = value[0..2]
66 .parse()
67 .map_err(|_| "failed to extract country code")?;
68 let lau_code = &value[3..];
69 match country {
70 Country::SE => registry::SE
71 .get(lau_code)
72 .ok_or("code not found in registry")
73 .copied(),
74 }
75 }
76}
77
78impl Serialize for LocalAdministrativeUnit {
79 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
80 where
81 S: serde::Serializer,
82 {
83 serializer.serialize_str(&self.eu_code())
84 }
85}
86
87impl FromStr for LocalAdministrativeUnit {
88 type Err = &'static str;
89
90 fn from_str(eu_lau_code: &str) -> Result<Self, Self::Err> {
91 Self::from_eu_code(eu_lau_code)
92 }
93}
94
95impl<'de> Deserialize<'de> for &'static LocalAdministrativeUnit {
96 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
97 where
98 D: serde::Deserializer<'de>,
99 {
100 let s = String::deserialize(deserializer)?;
101
102 if s.len() < 6 {
104 return Err(serde::de::Error::custom(format!(
105 "Invalid LAU code '{}': must be at least 6 characters",
106 s
107 )));
108 }
109
110 let country_code = &s[0..2];
111 let lau_code = &s[2..];
112
113 let country = Country::from_str(country_code).map_err(|_| {
114 serde::de::Error::custom(format!("Invalid country code '{}'", country_code))
115 })?;
116
117 let lau_code_static: &'static str = Box::leak(lau_code.to_string().into_boxed_str());
119
120 LocalAdministrativeUnit::get(country, lau_code_static).ok_or_else(|| {
121 serde::de::Error::custom(format!(
122 "Unknown LAU code '{}' for country {}",
123 lau_code, country_code
124 ))
125 })
126 }
127}
128
129impl<'de> Deserialize<'de> for LocalAdministrativeUnit {
130 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
131 where
132 D: serde::Deserializer<'de>,
133 {
134 let static_ref = <&'static LocalAdministrativeUnit>::deserialize(deserializer)?;
135 Ok(*static_ref)
136 }
137}
138
139#[derive(Debug, Clone, Copy, Serialize)]
140pub struct LocalAdministrativeUnitInfo {
141 pub code: &'static str,
142 pub name: &'static str,
143 pub country: Country,
144 pub taxes: Taxes,
145 pub tax_reductions: TaxReductions,
146}
147
148impl LocalAdministrativeUnitInfo {
149 pub fn only_current(mut self) -> Self {
150 self.taxes = self.taxes.with_current_only();
151 self.tax_reductions = self.tax_reductions.with_current_only();
152 self
153 }
154}
155
156impl From<&'static LocalAdministrativeUnit> for LocalAdministrativeUnitInfo {
157 fn from(value: &'static LocalAdministrativeUnit) -> Self {
158 Self {
159 code: value.local_code(),
160 name: value.name(),
161 country: value.country(),
162 taxes: value.taxes(),
163 tax_reductions: value.tax_reductions(),
164 }
165 }
166}
167
168pub(crate) mod registry {
169 static SE_DATA: &str = include_str!("../data/EU-27-LAU-2024-NUTS-2024-SE.csv");
170 use std::{
171 collections::{HashMap, HashSet},
172 str::FromStr,
173 sync::LazyLock,
174 };
175
176 use crate::{Country, local_administrative_unit::LocalAdministrativeUnit};
177
178 pub(crate) static SE: LazyLock<HashMap<&str, LocalAdministrativeUnit>> = LazyLock::new(|| {
179 let mut map = HashMap::with_capacity(290);
180
181 for line in SE_DATA.lines().skip(1) {
183 let mut parts = line.split(',');
185
186 let nuts_3_code = parts.next().unwrap();
187 let lau_code = parts.next().unwrap();
188 let name = parts.next().unwrap();
189 let country = Country::from_str(&nuts_3_code[0..2]).unwrap();
190 map.insert(
191 lau_code,
192 LocalAdministrativeUnit {
193 code: lau_code,
194 name,
195 country,
196 },
197 );
198 }
199 map
200 });
201
202 pub(crate) static SE_REDUCED_ENERGY_TAX_SUBDIVISIONS: LazyLock<HashSet<u16>> =
203 LazyLock::new(|| {
204 HashSet::from_iter([
205 2506, 2505, 2326, 2403, 2582, 2305, 2425, 2523, 2583, 2361, 2510, 2514, 2584, 2309,
206 2161, 2580, 2481, 2023, 2418, 2062, 2401, 2417, 2034, 2521, 2581, 2303, 2409, 2482,
207 2283, 2422, 2421, 2313, 1737, 2480, 2462, 2404, 2460, 2260, 2321, 2463, 2039, 2560,
208 2284, 2380, 2513, 2518,
209 ])
210 });
211}