grid_tariffs/
operator.rs

1use std::{hash::Hash, str::FromStr};
2
3use chrono::Utc;
4use indexmap::IndexMap;
5use serde::{Deserialize, Serialize};
6
7use crate::{
8    Country, Currency, Language, Links, PriceList, fuse::MainFuseSizes,
9    price_list::PriceListSimplified, registry::sweden,
10};
11
12#[derive(Debug, Clone)]
13#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
14pub struct GridOperator {
15    name: &'static str,
16    vat_number: &'static str,
17    /// Costs are specified in this currency
18    country: Country,
19    /// The main fuse size range that this info covers
20    main_fuses: MainFuseSizes,
21    price_lists: &'static [PriceList],
22    links: Links,
23}
24
25impl GridOperator {
26    pub const fn name(&self) -> &str {
27        self.name
28    }
29
30    pub const fn vat_number(&self) -> &str {
31        self.vat_number
32    }
33
34    pub const fn country(&self) -> Country {
35        self.country
36    }
37
38    pub const fn links(&self) -> &Links {
39        &self.links
40    }
41
42    pub fn active_price_lists(&self) -> Vec<&'static PriceList> {
43        let now = Utc::now().date_naive();
44        let mut map: IndexMap<Option<&str>, &PriceList> = IndexMap::new();
45        for pl in self.price_lists {
46            if now >= pl.from_date() {
47                if let Some(current_max_date) = map.get(&pl.variant()).map(|pl| pl.from_date()) {
48                    if pl.from_date() > current_max_date {
49                        map.insert(pl.variant(), pl);
50                    }
51                } else {
52                    map.insert(pl.variant(), pl);
53                }
54            }
55        }
56        map.into_values().collect()
57    }
58
59    pub fn active_price_list(&self, variant: Option<&str>) -> Option<&'static PriceList> {
60        self.active_price_lists()
61            .iter()
62            .filter(|pl| pl.variant() == variant)
63            .next_back()
64            .copied()
65    }
66
67    pub fn price_lists(&self) -> &'static [PriceList] {
68        self.price_lists
69    }
70
71    pub const fn currency(&self) -> Currency {
72        match self.country {
73            Country::SE => Currency::SEK,
74        }
75    }
76
77    pub fn get(country: Country, name: &str) -> Option<&'static Self> {
78        match country {
79            Country::SE => sweden::GRID_OPERATORS
80                .iter()
81                .find(|o| o.name == name)
82                .copied(),
83        }
84    }
85
86    pub fn get_by_vat_id(vat_id: &str) -> Option<&'static Self> {
87        let country: Country = (&vat_id[0..2]).parse().ok()?;
88        match country {
89            Country::SE => sweden::GRID_OPERATORS
90                .iter()
91                .find(|o| o.vat_number == vat_id)
92                .copied(),
93        }
94    }
95
96    pub fn all() -> Vec<&'static Self> {
97        sweden::GRID_OPERATORS.to_vec()
98    }
99
100    pub fn all_for_country(country: Country) -> &'static [&'static Self] {
101        match country {
102            Country::SE => sweden::GRID_OPERATORS,
103        }
104    }
105
106    pub const fn builder() -> GridOperatorBuilder {
107        GridOperatorBuilder::new()
108    }
109
110    pub fn simplified(
111        &self,
112        fuse_size: u16,
113        yearly_consumption: u32,
114        language: Language,
115    ) -> GridOperatorSimplified {
116        GridOperatorSimplified::new(self, fuse_size, yearly_consumption, language)
117    }
118}
119
120/// Grid operator with only current prices
121#[derive(Debug, Clone, Serialize)]
122#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
123pub struct GridOperatorSimplified {
124    name: &'static str,
125    vat_number: &'static str,
126    /// Costs are specified in this currency
127    country: Country,
128    price_lists: Vec<PriceListSimplified>,
129}
130
131impl GridOperatorSimplified {
132    pub fn name(&self) -> &'static str {
133        self.name
134    }
135
136    pub fn vat_number(&self) -> &'static str {
137        self.vat_number
138    }
139
140    pub fn country(&self) -> Country {
141        self.country
142    }
143
144    pub fn price_lists(&self) -> &[PriceListSimplified] {
145        &self.price_lists
146    }
147}
148
149impl GridOperatorSimplified {
150    fn new(op: &GridOperator, fuse_size: u16, yearly_consumption: u32, language: Language) -> Self {
151        Self {
152            name: op.name,
153            vat_number: op.vat_number,
154            country: op.country(),
155            price_lists: op
156                .active_price_lists()
157                .into_iter()
158                .map(|pl| pl.simplified(fuse_size, yearly_consumption, language))
159                .collect(),
160        }
161    }
162}
163
164#[derive(Debug, Clone)]
165pub struct GridOperatorBuilder {
166    name: Option<&'static str>,
167    vat_number: Option<&'static str>,
168    /// Costs are specified in this currency
169    country: Option<Country>,
170    /// The main fuse size range that this info covers
171    main_fuses: Option<MainFuseSizes>,
172    price_lists: Option<&'static [PriceList]>,
173    links: Option<Links>,
174}
175
176impl GridOperatorBuilder {
177    pub const fn new() -> Self {
178        Self {
179            name: None,
180            vat_number: None,
181            country: None,
182            main_fuses: None,
183            price_lists: None,
184            links: None,
185        }
186    }
187
188    pub const fn name(mut self, name: &'static str) -> Self {
189        self.name = Some(name);
190        self
191    }
192
193    pub const fn vat_number(mut self, vat_number: &'static str) -> Self {
194        self.vat_number = Some(vat_number);
195        self
196    }
197
198    pub const fn country(mut self, country: Country) -> Self {
199        self.country = Some(country);
200        self
201    }
202
203    pub const fn main_fuses(mut self, main_fuses: MainFuseSizes) -> Self {
204        self.main_fuses = Some(main_fuses);
205        self
206    }
207
208    pub const fn links(mut self, links: Links) -> Self {
209        self.links = Some(links);
210        self
211    }
212
213    pub const fn price_lists(mut self, price_lists: &'static [PriceList]) -> Self {
214        self.price_lists = Some(price_lists);
215        self
216    }
217
218    pub const fn build(self) -> GridOperator {
219        GridOperator {
220            name: self.name.expect("`name` required"),
221            vat_number: self.vat_number.expect("`vat_number` required"),
222            country: self.country.expect("`country` required"),
223            main_fuses: self.main_fuses.expect("`main_fuses` required"),
224            price_lists: self.price_lists.expect("`price_lists` expected"),
225            links: self.links.expect("`links` required"),
226        }
227    }
228}
229
230impl FromStr for &'static GridOperator {
231    type Err = &'static str;
232
233    fn from_str(s: &str) -> Result<Self, Self::Err> {
234        GridOperator::get_by_vat_id(s).ok_or("grid operator not found")
235    }
236}
237
238impl<'de> Deserialize<'de> for &'static GridOperator {
239    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
240    where
241        D: serde::Deserializer<'de>,
242    {
243        let s = String::deserialize(deserializer)?;
244        Self::from_str(&s).map_err(serde::de::Error::custom)
245    }
246}
247
248impl Serialize for GridOperator {
249    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
250    where
251        S: serde::Serializer,
252    {
253        serializer.serialize_str(self.vat_number())
254    }
255}
256
257impl PartialEq for GridOperator {
258    fn eq(&self, other: &Self) -> bool {
259        self.vat_number == other.vat_number
260    }
261}
262
263impl Eq for GridOperator {}
264
265impl PartialOrd for GridOperator {
266    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
267        self.vat_number.partial_cmp(&other.vat_number)
268    }
269}
270
271impl Ord for GridOperator {
272    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
273        self.vat_number.cmp(other.vat_number)
274    }
275}
276
277impl Hash for GridOperator {
278    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
279        self.vat_number.hash(state);
280    }
281}
282
283#[cfg(test)]
284mod tests {
285    use super::*;
286
287    #[test]
288    fn test_grid_operator_serialize_deserialize() {
289        let operator: &GridOperator = "SE556037732601".parse().unwrap();
290
291        // Serialize to JSON string
292        let serialized = serde_json::to_string(operator).expect("serialization should succeed");
293
294        // The serialized value should be the VAT number wrapped in quotes
295        assert_eq!(serialized, format!("\"{}\"", operator.vat_number()));
296
297        // Deserialize back to GridOperator reference
298        let deserialized: &'static GridOperator =
299            serde_json::from_str(&serialized).expect("deserialization should succeed");
300
301        // Verify the deserialized operator matches the original
302        assert_eq!(deserialized.name(), operator.name());
303        assert_eq!(deserialized.vat_number(), operator.vat_number());
304        assert_eq!(deserialized.country(), operator.country());
305    }
306
307    #[test]
308    fn test_grid_operator_deserialize_invalid() {
309        // Try to deserialize an invalid VAT number
310        let result: Result<&'static GridOperator, _> = serde_json::from_str("\"SE000000000000\"");
311
312        assert!(
313            result.is_err(),
314            "deserialization should fail for invalid VAT number"
315        );
316    }
317
318    #[test]
319    fn test_grid_operator_partial_eq() {
320        let operator1: &GridOperator = "SE556037732601".parse().unwrap();
321        let operator2: &GridOperator = "SE556037732601".parse().unwrap();
322        let operator3: &GridOperator = "SE556532083401".parse().unwrap();
323
324        // Test equality - same VAT number
325        assert_eq!(operator1, operator2);
326
327        // Test inequality - different VAT numbers
328        assert_ne!(operator1, operator3);
329    }
330
331    #[test]
332    fn test_grid_operator_partial_ord() {
333        let operator1: &GridOperator = "SE556037732601".parse().unwrap();
334        let operator2: &GridOperator = "SE556532083401".parse().unwrap();
335
336        // Test ordering based on VAT number
337        assert!(operator1 < operator2);
338        assert!(operator2 > operator1);
339        assert!(operator1 <= operator1);
340        assert!(operator1 >= operator1);
341    }
342
343    #[test]
344    fn test_grid_operator_ord() {
345        let operator1: &GridOperator = "SE556037732601".parse().unwrap();
346        let operator2: &GridOperator = "SE556532083401".parse().unwrap();
347        let operator3: &GridOperator = "SE556037732601".parse().unwrap();
348
349        // Test Ord::cmp
350        assert_eq!(operator1.cmp(operator2), std::cmp::Ordering::Less);
351        assert_eq!(operator2.cmp(operator1), std::cmp::Ordering::Greater);
352        assert_eq!(operator1.cmp(operator3), std::cmp::Ordering::Equal);
353    }
354
355    #[test]
356    fn test_grid_operator_hash() {
357        use std::collections::hash_map::DefaultHasher;
358        use std::hash::{Hash, Hasher};
359
360        let operator1: &GridOperator = "SE556037732601".parse().unwrap();
361        let operator2: &GridOperator = "SE556037732601".parse().unwrap();
362        let operator3: &GridOperator = "SE556532083401".parse().unwrap();
363
364        // Helper function to compute hash
365        let compute_hash = |op: &GridOperator| {
366            let mut hasher = DefaultHasher::new();
367            op.hash(&mut hasher);
368            hasher.finish()
369        };
370
371        // Operators with same VAT number should have same hash
372        assert_eq!(compute_hash(operator1), compute_hash(operator2));
373
374        // Operators with different VAT numbers should have different hashes
375        assert_ne!(compute_hash(operator1), compute_hash(operator3));
376    }
377}