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 Default for GridOperatorBuilder {
177    fn default() -> Self {
178        Self::new()
179    }
180}
181
182impl GridOperatorBuilder {
183    pub const fn new() -> Self {
184        Self {
185            name: None,
186            vat_number: None,
187            country: None,
188            main_fuses: None,
189            price_lists: None,
190            links: None,
191        }
192    }
193
194    pub const fn name(mut self, name: &'static str) -> Self {
195        self.name = Some(name);
196        self
197    }
198
199    pub const fn vat_number(mut self, vat_number: &'static str) -> Self {
200        self.vat_number = Some(vat_number);
201        self
202    }
203
204    pub const fn country(mut self, country: Country) -> Self {
205        self.country = Some(country);
206        self
207    }
208
209    pub const fn main_fuses(mut self, main_fuses: MainFuseSizes) -> Self {
210        self.main_fuses = Some(main_fuses);
211        self
212    }
213
214    pub const fn links(mut self, links: Links) -> Self {
215        self.links = Some(links);
216        self
217    }
218
219    pub const fn price_lists(mut self, price_lists: &'static [PriceList]) -> Self {
220        self.price_lists = Some(price_lists);
221        self
222    }
223
224    pub const fn build(self) -> GridOperator {
225        GridOperator {
226            name: self.name.expect("`name` required"),
227            vat_number: self.vat_number.expect("`vat_number` required"),
228            country: self.country.expect("`country` required"),
229            main_fuses: self.main_fuses.expect("`main_fuses` required"),
230            price_lists: self.price_lists.expect("`price_lists` expected"),
231            links: self.links.expect("`links` required"),
232        }
233    }
234}
235
236impl FromStr for &'static GridOperator {
237    type Err = &'static str;
238
239    fn from_str(s: &str) -> Result<Self, Self::Err> {
240        GridOperator::get_by_vat_id(s).ok_or("grid operator not found")
241    }
242}
243
244impl<'de> Deserialize<'de> for &'static GridOperator {
245    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
246    where
247        D: serde::Deserializer<'de>,
248    {
249        let s = String::deserialize(deserializer)?;
250        Self::from_str(&s).map_err(serde::de::Error::custom)
251    }
252}
253
254impl Serialize for GridOperator {
255    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
256    where
257        S: serde::Serializer,
258    {
259        serializer.serialize_str(self.vat_number())
260    }
261}
262
263impl PartialEq for GridOperator {
264    fn eq(&self, other: &Self) -> bool {
265        self.vat_number == other.vat_number
266    }
267}
268
269impl Eq for GridOperator {}
270
271impl PartialOrd for GridOperator {
272    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
273        Some(self.cmp(other))
274    }
275}
276
277impl Ord for GridOperator {
278    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
279        self.vat_number.cmp(other.vat_number)
280    }
281}
282
283impl Hash for GridOperator {
284    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
285        self.vat_number.hash(state);
286    }
287}
288
289#[cfg(test)]
290mod tests {
291    use super::*;
292
293    #[test]
294    fn test_grid_operator_serialize_deserialize() {
295        let operator: &GridOperator = "SE556037732601".parse().unwrap();
296
297        // Serialize to JSON string
298        let serialized = serde_json::to_string(operator).expect("serialization should succeed");
299
300        // The serialized value should be the VAT number wrapped in quotes
301        assert_eq!(serialized, format!("\"{}\"", operator.vat_number()));
302
303        // Deserialize back to GridOperator reference
304        let deserialized: &'static GridOperator =
305            serde_json::from_str(&serialized).expect("deserialization should succeed");
306
307        // Verify the deserialized operator matches the original
308        assert_eq!(deserialized.name(), operator.name());
309        assert_eq!(deserialized.vat_number(), operator.vat_number());
310        assert_eq!(deserialized.country(), operator.country());
311    }
312
313    #[test]
314    fn test_grid_operator_deserialize_invalid() {
315        // Try to deserialize an invalid VAT number
316        let result: Result<&'static GridOperator, _> = serde_json::from_str("\"SE000000000000\"");
317
318        assert!(
319            result.is_err(),
320            "deserialization should fail for invalid VAT number"
321        );
322    }
323
324    #[test]
325    fn test_grid_operator_partial_eq() {
326        let operator1: &GridOperator = "SE556037732601".parse().unwrap();
327        let operator2: &GridOperator = "SE556037732601".parse().unwrap();
328        let operator3: &GridOperator = "SE556532083401".parse().unwrap();
329
330        // Test equality - same VAT number
331        assert_eq!(operator1, operator2);
332
333        // Test inequality - different VAT numbers
334        assert_ne!(operator1, operator3);
335    }
336
337    #[test]
338    fn test_grid_operator_partial_ord() {
339        let operator1: &GridOperator = "SE556037732601".parse().unwrap();
340        let operator2: &GridOperator = "SE556532083401".parse().unwrap();
341
342        // Test ordering based on VAT number
343        assert!(operator1 < operator2);
344        assert!(operator2 > operator1);
345        assert!(operator1 <= operator1);
346        assert!(operator1 >= operator1);
347    }
348
349    #[test]
350    fn test_grid_operator_ord() {
351        let operator1: &GridOperator = "SE556037732601".parse().unwrap();
352        let operator2: &GridOperator = "SE556532083401".parse().unwrap();
353        let operator3: &GridOperator = "SE556037732601".parse().unwrap();
354
355        // Test Ord::cmp
356        assert_eq!(operator1.cmp(operator2), std::cmp::Ordering::Less);
357        assert_eq!(operator2.cmp(operator1), std::cmp::Ordering::Greater);
358        assert_eq!(operator1.cmp(operator3), std::cmp::Ordering::Equal);
359    }
360
361    #[test]
362    fn test_grid_operator_hash() {
363        use std::collections::hash_map::DefaultHasher;
364        use std::hash::{Hash, Hasher};
365
366        let operator1: &GridOperator = "SE556037732601".parse().unwrap();
367        let operator2: &GridOperator = "SE556037732601".parse().unwrap();
368        let operator3: &GridOperator = "SE556532083401".parse().unwrap();
369
370        // Helper function to compute hash
371        let compute_hash = |op: &GridOperator| {
372            let mut hasher = DefaultHasher::new();
373            op.hash(&mut hasher);
374            hasher.finish()
375        };
376
377        // Operators with same VAT number should have same hash
378        assert_eq!(compute_hash(operator1), compute_hash(operator2));
379
380        // Operators with different VAT numbers should have different hashes
381        assert_ne!(compute_hash(operator1), compute_hash(operator3));
382    }
383}