1use std::collections::{HashMap, HashSet};
8
9use super::evaluator::{ConditionResult, ExternalConditionProvider};
10
11const COUNTRIES_WITH_PLZ: &[&str] = &[
13 "DE", "AT", "CH", "BE", "BG", "CZ", "DK", "EE", "FI", "FR", "GR", "HR", "HU", "IE", "IT", "LT",
14 "LU", "LV", "MT", "NL", "PL", "PT", "RO", "SE", "SI", "SK", "ES", "GB", "NO", "CY", "IS", "LI",
15];
16
17const COUNTRY_PLZ_CONDITIONS: &[&str] = &[
18 "country_code_has_plz",
19 "country_has_postal_code",
20 "country_has_postal_code_requirement",
21];
22
23pub struct CountryPostalCodeProvider {
28 country_code: Option<String>,
29}
30
31impl CountryPostalCodeProvider {
32 pub fn new(country_code: Option<String>) -> Self {
34 Self { country_code }
35 }
36}
37
38impl ExternalConditionProvider for CountryPostalCodeProvider {
39 fn evaluate(&self, condition_name: &str) -> ConditionResult {
40 if !COUNTRY_PLZ_CONDITIONS.contains(&condition_name) {
41 return ConditionResult::Unknown;
42 }
43 match &self.country_code {
44 Some(code) => ConditionResult::from(COUNTRIES_WITH_PLZ.contains(&code.as_str())),
45 None => ConditionResult::Unknown,
46 }
47 }
48}
49
50pub struct MapExternalProvider {
71 conditions: HashMap<String, bool>,
72}
73
74impl MapExternalProvider {
75 pub fn new(conditions: HashMap<String, bool>) -> Self {
77 Self { conditions }
78 }
79}
80
81impl ExternalConditionProvider for MapExternalProvider {
82 fn evaluate(&self, condition_name: &str) -> ConditionResult {
83 match self.conditions.get(condition_name) {
84 Some(true) => ConditionResult::True,
85 Some(false) => ConditionResult::False,
86 None => ConditionResult::Unknown,
87 }
88 }
89}
90
91pub struct CompositeExternalProvider {
101 providers: Vec<Box<dyn ExternalConditionProvider>>,
102}
103
104impl CompositeExternalProvider {
105 pub fn new(providers: Vec<Box<dyn ExternalConditionProvider>>) -> Self {
109 Self { providers }
110 }
111
112 pub fn with_defaults(
119 sector: Option<Sector>,
120 roles: Option<(Vec<MarketRole>, Vec<MarketRole>)>,
121 code_list_json: Option<&str>,
122 ) -> Self {
123 Self::builder()
124 .sector(sector)
125 .roles(roles)
126 .code_list_json(code_list_json)
127 .build()
128 }
129
130 pub fn builder() -> CompositeProviderBuilder {
132 CompositeProviderBuilder::default()
133 }
134}
135
136#[derive(Default)]
138pub struct CompositeProviderBuilder {
139 sector: Option<Sector>,
140 roles: Option<(Vec<MarketRole>, Vec<MarketRole>)>,
141 code_list_json: Option<String>,
142 konfigurationen_json: Option<String>,
143 product_code: Option<String>,
144 country_code: Option<String>,
145}
146
147impl CompositeProviderBuilder {
148 pub fn sector(mut self, sector: Option<Sector>) -> Self {
149 self.sector = sector;
150 self
151 }
152
153 pub fn roles(mut self, roles: Option<(Vec<MarketRole>, Vec<MarketRole>)>) -> Self {
154 self.roles = roles;
155 self
156 }
157
158 pub fn code_list_json(mut self, json: Option<&str>) -> Self {
159 self.code_list_json = json.map(String::from);
160 self
161 }
162
163 pub fn konfigurationen_json(mut self, json: &str) -> Self {
164 self.konfigurationen_json = Some(json.to_string());
165 self
166 }
167
168 pub fn product_code(mut self, code: Option<String>) -> Self {
169 self.product_code = code;
170 self
171 }
172
173 pub fn country_code(mut self, code: Option<String>) -> Self {
174 self.country_code = code;
175 self
176 }
177
178 pub fn build(self) -> CompositeExternalProvider {
179 let mut providers: Vec<Box<dyn ExternalConditionProvider>> = Vec::new();
180
181 if let Some(sector) = self.sector {
182 providers.push(Box::new(SectorProvider::new(sector)));
183 }
184 if let Some((sender, recipient)) = self.roles {
185 providers.push(Box::new(MarketRoleProvider::new(sender, recipient)));
186 }
187 if let Some(json) = &self.code_list_json {
188 if let Ok(provider) = CodeListProvider::from_json(json) {
189 providers.push(Box::new(provider));
190 }
191 }
192 if let Some(json) = &self.konfigurationen_json {
193 if let Ok(provider) = KonfigurationenProvider::from_json(json, self.product_code) {
194 providers.push(Box::new(provider));
195 }
196 }
197 if self.country_code.is_some() {
198 providers.push(Box::new(CountryPostalCodeProvider::new(self.country_code)));
199 }
200
201 CompositeExternalProvider::new(providers)
202 }
203}
204
205impl ExternalConditionProvider for CompositeExternalProvider {
206 fn evaluate(&self, condition_name: &str) -> ConditionResult {
207 for provider in &self.providers {
208 let result = provider.evaluate(condition_name);
209 if !result.is_unknown() {
210 return result;
211 }
212 }
213 ConditionResult::Unknown
214 }
215}
216
217pub struct CodeListProvider {
222 lists: HashMap<String, HashSet<String>>,
223}
224
225impl CodeListProvider {
226 pub fn new(lists: HashMap<String, Vec<String>>) -> Self {
227 Self {
228 lists: lists
229 .into_iter()
230 .map(|(k, v)| (k, v.into_iter().collect()))
231 .collect(),
232 }
233 }
234
235 pub fn from_json(json: &str) -> Result<Self, serde_json::Error> {
237 #[derive(serde::Deserialize)]
238 struct Entry {
239 codes: Vec<CodeValue>,
240 }
241 #[derive(serde::Deserialize)]
242 struct CodeValue {
243 value: String,
244 }
245
246 let raw: HashMap<String, Entry> = serde_json::from_str(json)?;
247 let lists = raw
248 .into_iter()
249 .map(|(k, v)| (k, v.codes.into_iter().map(|c| c.value).collect()))
250 .collect();
251 Ok(Self { lists })
252 }
253
254 pub fn from_json_file(
256 path: &std::path::Path,
257 ) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
258 let json = std::fs::read_to_string(path)?;
259 Ok(Self::from_json(&json)?)
260 }
261}
262
263impl ExternalConditionProvider for CodeListProvider {
264 fn evaluate(&self, condition_name: &str) -> ConditionResult {
265 let rest = match condition_name.strip_prefix("code_in_") {
266 Some(r) => r,
267 None => return ConditionResult::Unknown,
268 };
269 let (de_id, value) = match rest.split_once(':') {
270 Some(pair) => pair,
271 None => return ConditionResult::Unknown,
272 };
273 match self.lists.get(de_id) {
274 Some(set) => ConditionResult::from(set.contains(value)),
275 None => ConditionResult::Unknown,
276 }
277 }
278}
279
280#[derive(Debug, Clone, Copy, PartialEq, Eq)]
282pub enum Sector {
283 Strom,
284 Gas,
285}
286
287pub struct SectorProvider {
289 sector: Sector,
290}
291
292impl SectorProvider {
293 pub fn new(sector: Sector) -> Self {
294 Self { sector }
295 }
296
297 pub fn from_variant(variant: &str) -> Option<Self> {
299 match variant {
300 "Strom" => Some(Self::new(Sector::Strom)),
301 "Gas" => Some(Self::new(Sector::Gas)),
302 _ => None,
303 }
304 }
305}
306
307const STROM_CONDITIONS: &[&str] = &[
308 "recipient_is_strom",
309 "recipient_market_sector_is_strom",
310 "recipient_is_electricity_sector",
311 "sender_is_strom",
312 "mp_id_is_strom",
313 "mp_id_is_strom_sector",
314 "mp_id_is_electricity_sector",
315 "mp_id_from_electricity_sector",
316 "mp_id_only_strom",
317 "mp_id_strom_only",
318 "mp_id_sparte_strom",
319 "market_location_is_electricity",
320 "marktpartner_is_strom",
321 "location_is_strom",
322 "metering_point_is_strom",
323 "network_location_is_strom",
324];
325
326const GAS_CONDITIONS: &[&str] = &[
327 "recipient_is_gas",
328 "recipient_market_sector_is_gas",
329 "recipient_is_gas_sector",
330 "sender_is_gas",
331 "sender_is_gas_sector",
332 "mp_id_is_gas",
333 "mp_id_is_gas_sector",
334 "market_location_is_gas",
335 "marktpartner_is_gas",
336 "location_is_gas",
337 "metering_point_is_gas",
338 "network_location_is_gas",
339 "recipient_is_msb_gas",
340];
341
342impl ExternalConditionProvider for SectorProvider {
343 fn evaluate(&self, condition_name: &str) -> ConditionResult {
344 if STROM_CONDITIONS.contains(&condition_name) {
345 return ConditionResult::from(self.sector == Sector::Strom);
346 }
347 if GAS_CONDITIONS.contains(&condition_name) {
348 return ConditionResult::from(self.sector == Sector::Gas);
349 }
350 ConditionResult::Unknown
351 }
352}
353
354#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
356pub enum MarketRole {
357 LF,
359 NB,
361 MSB,
363 MDL,
365 UENB,
367 BKV,
369 BIKO,
371 ESA,
373 MGV,
375 KN,
377}
378
379impl MarketRole {
380 fn from_suffix(s: &str) -> Option<Self> {
381 match s {
382 "lf" => Some(Self::LF),
383 "nb" => Some(Self::NB),
384 "msb" => Some(Self::MSB),
385 "mdl" => Some(Self::MDL),
386 "uenb" => Some(Self::UENB),
387 "bkv" => Some(Self::BKV),
388 "biko" => Some(Self::BIKO),
389 "esa" => Some(Self::ESA),
390 "mgv" => Some(Self::MGV),
391 "kn" => Some(Self::KN),
392 _ => None,
393 }
394 }
395
396 fn parse_compound(s: &str) -> Option<Vec<Self>> {
400 if s.contains("_or_") {
402 let parts: Vec<&str> = s.split("_or_").collect();
403 let mut roles = Vec::new();
404 for part in parts {
405 roles.push(Self::from_suffix(part)?);
406 }
407 if !roles.is_empty() {
408 return Some(roles);
409 }
410 }
411 let parts: Vec<&str> = s.split('_').collect();
413 let mut roles = Vec::new();
414 for part in parts {
415 roles.push(Self::from_suffix(part)?);
416 }
417 if roles.is_empty() {
418 None
419 } else {
420 Some(roles)
421 }
422 }
423}
424
425pub struct MarketRoleProvider {
427 sender_roles: HashSet<MarketRole>,
428 recipient_roles: HashSet<MarketRole>,
429}
430
431impl MarketRoleProvider {
432 pub fn new(sender_roles: Vec<MarketRole>, recipient_roles: Vec<MarketRole>) -> Self {
433 Self {
434 sender_roles: sender_roles.into_iter().collect(),
435 recipient_roles: recipient_roles.into_iter().collect(),
436 }
437 }
438}
439
440impl ExternalConditionProvider for MarketRoleProvider {
441 fn evaluate(&self, condition_name: &str) -> ConditionResult {
442 if let Some(role_str) = condition_name.strip_prefix("sender_is_") {
444 if let Some(role) = MarketRole::from_suffix(role_str) {
445 return ConditionResult::from(self.sender_roles.contains(&role));
446 }
447 if let Some(roles) = MarketRole::parse_compound(role_str) {
449 return ConditionResult::from(roles.iter().any(|r| self.sender_roles.contains(r)));
450 }
451 }
452 if let Some(role_str) = condition_name.strip_prefix("recipient_is_") {
453 if let Some(role) = MarketRole::from_suffix(role_str) {
454 return ConditionResult::from(self.recipient_roles.contains(&role));
455 }
456 if let Some(roles) = MarketRole::parse_compound(role_str) {
458 return ConditionResult::from(
459 roles.iter().any(|r| self.recipient_roles.contains(r)),
460 );
461 }
462 }
463 if let Some(role_str) = condition_name.strip_prefix("sender_role_is_not_") {
464 if let Some(role) = MarketRole::from_suffix(role_str) {
465 return ConditionResult::from(!self.sender_roles.contains(&role));
466 }
467 }
468 if let Some(role_str) = condition_name.strip_prefix("recipient_role_is_not_") {
469 if let Some(role) = MarketRole::from_suffix(role_str) {
470 return ConditionResult::from(!self.recipient_roles.contains(&role));
471 }
472 }
473 if let Some(role_str) = condition_name.strip_prefix("recipient_not_") {
475 if let Some(role) = MarketRole::from_suffix(role_str) {
476 return ConditionResult::from(!self.recipient_roles.contains(&role));
477 }
478 if let Some(roles) = MarketRole::parse_compound(role_str) {
479 return ConditionResult::from(
480 !roles.iter().any(|r| self.recipient_roles.contains(r)),
481 );
482 }
483 }
484 if let Some(role_str) = condition_name.strip_prefix("sender_not_") {
486 if let Some(role) = MarketRole::from_suffix(role_str) {
487 return ConditionResult::from(!self.sender_roles.contains(&role));
488 }
489 if let Some(roles) = MarketRole::parse_compound(role_str) {
490 return ConditionResult::from(!roles.iter().any(|r| self.sender_roles.contains(r)));
491 }
492 }
493 if let Some(role_str) = condition_name.strip_prefix("mp_id_role_is_") {
495 if let Some(role) = MarketRole::from_suffix(role_str) {
496 return ConditionResult::from(self.sender_roles.contains(&role));
497 }
498 }
499 ConditionResult::Unknown
500 }
501}
502
503enum KonfigurationenAlias {
516 AllCodes,
518 Category(&'static str),
520 MessproduktUnion,
522}
523
524fn resolve_konfigurationen_alias(condition_name: &str) -> Option<KonfigurationenAlias> {
525 match condition_name {
526 "is_konfigurationsprodukt_code" | "lin_prefix_is_valid_product_code" => {
527 Some(KonfigurationenAlias::AllCodes)
528 }
529 "is_messprodukt_code" => Some(KonfigurationenAlias::MessproduktUnion),
530 "messprodukts_typ2_smgw"
531 | "product_in_typ2_smgw_codelist"
532 | "order_contains_smgw_type2_product" => {
533 Some(KonfigurationenAlias::Category("messprodukt_typ2_smgw"))
534 }
535 "valid_adhoc_steuerkanal_product" => Some(KonfigurationenAlias::Category(
536 "config_product_adhoc_steuerkanal",
537 )),
538 "product_code_level_messlokation" => Some(KonfigurationenAlias::Category(
539 "messprodukt_standard_messlokation",
540 )),
541 "product_code_level_netzlokation" => Some(KonfigurationenAlias::Category(
542 "messprodukt_standard_netzlokation",
543 )),
544 "product_code_abrechnungsdaten_valid" => Some(KonfigurationenAlias::Category(
545 "produkte_aenderung_abrechnungsdaten",
546 )),
547 "lin_product_code_is_lokationsaenderung_strom" => Some(KonfigurationenAlias::Category(
548 "produkte_aenderung_lokation",
549 )),
550 _ => None,
551 }
552}
553
554const MESSPRODUKT_CATEGORY_PREFIXES: &[&str] = &[
556 "messprodukt_standard_",
557 "messprodukt_typ2_smgw",
558 "messprodukt_esa",
559];
560
561pub struct KonfigurationenProvider {
562 categories: HashMap<String, HashSet<String>>,
564 all_codes: HashSet<String>,
566 messprodukt_union: HashSet<String>,
568 product_code: Option<String>,
570}
571
572impl KonfigurationenProvider {
573 pub fn new(categories: HashMap<String, Vec<String>>, product_code: Option<String>) -> Self {
575 let mut all_codes = HashSet::new();
576 let categories: HashMap<String, HashSet<String>> = categories
577 .into_iter()
578 .map(|(k, v)| {
579 for code in &v {
580 all_codes.insert(code.clone());
581 }
582 (k, v.into_iter().collect())
583 })
584 .collect();
585
586 let mut messprodukt_union = HashSet::new();
588 for (name, codes) in &categories {
589 let is_messprodukt = MESSPRODUKT_CATEGORY_PREFIXES
590 .iter()
591 .any(|prefix| name.starts_with(prefix) || name == prefix);
592 if is_messprodukt {
593 messprodukt_union.extend(codes.iter().cloned());
594 }
595 }
596
597 Self {
598 categories,
599 all_codes,
600 messprodukt_union,
601 product_code,
602 }
603 }
604
605 pub fn from_json(json: &str, product_code: Option<String>) -> Result<Self, serde_json::Error> {
607 #[derive(serde::Deserialize)]
608 struct KonfigurationenFile {
609 categories: HashMap<String, Vec<String>>,
610 #[serde(default)]
611 all_product_codes: Vec<String>,
612 }
613 let file: KonfigurationenFile = serde_json::from_str(json)?;
614 let mut provider = Self::new(file.categories, product_code);
615 for code in file.all_product_codes {
617 provider.all_codes.insert(code);
618 }
619 Ok(provider)
620 }
621
622 pub fn from_json_file(
624 path: &std::path::Path,
625 product_code: Option<String>,
626 ) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
627 let json = std::fs::read_to_string(path)?;
628 Ok(Self::from_json(&json, product_code)?)
629 }
630}
631
632impl ExternalConditionProvider for KonfigurationenProvider {
633 fn evaluate(&self, condition_name: &str) -> ConditionResult {
634 let code = match &self.product_code {
635 Some(c) => c,
636 None => return ConditionResult::Unknown,
637 };
638
639 if condition_name == "code_list_membership_check" {
641 return ConditionResult::from(self.all_codes.contains(code.as_str()));
642 }
643
644 if let Some(alias) = resolve_konfigurationen_alias(condition_name) {
646 return match alias {
647 KonfigurationenAlias::AllCodes => {
648 ConditionResult::from(self.all_codes.contains(code.as_str()))
649 }
650 KonfigurationenAlias::Category(cat) => {
651 if let Some(set) = self.categories.get(cat) {
652 ConditionResult::from(set.contains(code.as_str()))
653 } else {
654 ConditionResult::Unknown
655 }
656 }
657 KonfigurationenAlias::MessproduktUnion => {
658 ConditionResult::from(self.messprodukt_union.contains(code.as_str()))
659 }
660 };
661 }
662
663 if let Some(set) = self.categories.get(condition_name) {
665 return ConditionResult::from(set.contains(code.as_str()));
666 }
667
668 ConditionResult::Unknown
669 }
670}
671
672#[cfg(test)]
673mod tests {
674 use super::*;
675
676 #[test]
679 fn map_provider_returns_true_for_true_entry() {
680 let mut conditions = HashMap::new();
681 conditions.insert("DateKnown".to_string(), true);
682 let provider = MapExternalProvider::new(conditions);
683
684 assert_eq!(provider.evaluate("DateKnown"), ConditionResult::True);
685 }
686
687 #[test]
688 fn map_provider_returns_false_for_false_entry() {
689 let mut conditions = HashMap::new();
690 conditions.insert("MessageSplitting".to_string(), false);
691 let provider = MapExternalProvider::new(conditions);
692
693 assert_eq!(
694 provider.evaluate("MessageSplitting"),
695 ConditionResult::False
696 );
697 }
698
699 #[test]
700 fn map_provider_returns_unknown_for_missing_key() {
701 let mut conditions = HashMap::new();
702 conditions.insert("DateKnown".to_string(), true);
703 let provider = MapExternalProvider::new(conditions);
704
705 assert_eq!(provider.evaluate("NonExistent"), ConditionResult::Unknown);
706 }
707
708 #[test]
709 fn map_provider_empty_map_returns_unknown() {
710 let provider = MapExternalProvider::new(HashMap::new());
711
712 assert_eq!(provider.evaluate("Anything"), ConditionResult::Unknown);
713 }
714
715 #[test]
718 fn composite_first_known_wins() {
719 let mut p1_map = HashMap::new();
721 p1_map.insert("A".to_string(), true);
722 let p1 = MapExternalProvider::new(p1_map);
723
724 let mut p2_map = HashMap::new();
726 p2_map.insert("B".to_string(), false);
727 let p2 = MapExternalProvider::new(p2_map);
728
729 let composite = CompositeExternalProvider::new(vec![Box::new(p1), Box::new(p2)]);
730
731 assert_eq!(composite.evaluate("A"), ConditionResult::True);
733 assert_eq!(composite.evaluate("B"), ConditionResult::False);
735 }
736
737 #[test]
738 fn composite_all_unknown_returns_unknown() {
739 let p1 = MapExternalProvider::new(HashMap::new());
741 let p2 = MapExternalProvider::new(HashMap::new());
742
743 let composite = CompositeExternalProvider::new(vec![Box::new(p1), Box::new(p2)]);
744
745 assert_eq!(composite.evaluate("X"), ConditionResult::Unknown);
746 }
747
748 #[test]
749 fn composite_empty_returns_unknown() {
750 let composite = CompositeExternalProvider::new(vec![]);
751
752 assert_eq!(composite.evaluate("Anything"), ConditionResult::Unknown);
753 }
754
755 #[test]
758 fn test_code_list_provider_known_code() {
759 let mut lists = HashMap::new();
760 lists.insert(
761 "7111".to_string(),
762 vec!["Z91".to_string(), "Z90".to_string()],
763 );
764 let provider = CodeListProvider::new(lists);
765 assert_eq!(provider.evaluate("code_in_7111:Z91"), ConditionResult::True);
766 assert_eq!(
767 provider.evaluate("code_in_7111:ZZZ"),
768 ConditionResult::False
769 );
770 assert_eq!(
771 provider.evaluate("code_in_9999:Z91"),
772 ConditionResult::Unknown
773 );
774 }
775
776 #[test]
777 fn test_code_list_provider_loads_json() {
778 let json = r#"{
779 "7111": { "name": "Eigenschaft", "codes": [{"value": "Z91", "name": "MSB"}] },
780 "3225": { "name": "Ort", "codes": [{"value": "Z16", "name": "MaLo"}] }
781 }"#;
782 let provider = CodeListProvider::from_json(json).unwrap();
783 assert_eq!(provider.evaluate("code_in_7111:Z91"), ConditionResult::True);
784 assert_eq!(provider.evaluate("code_in_3225:Z16"), ConditionResult::True);
785 assert_eq!(
786 provider.evaluate("code_in_3225:Z99"),
787 ConditionResult::False
788 );
789 }
790
791 #[test]
792 fn test_code_list_provider_invalid_format() {
793 let mut lists = HashMap::new();
794 lists.insert("7111".to_string(), vec!["Z91".to_string()]);
795 let provider = CodeListProvider::new(lists);
796 assert_eq!(
797 provider.evaluate("not_a_code_check"),
798 ConditionResult::Unknown
799 );
800 assert_eq!(provider.evaluate("code_in_7111"), ConditionResult::Unknown);
801 }
803
804 #[test]
807 fn test_sector_provider_strom() {
808 let provider = SectorProvider::new(Sector::Strom);
809 assert_eq!(
810 provider.evaluate("recipient_is_strom"),
811 ConditionResult::True
812 );
813 assert_eq!(
814 provider.evaluate("recipient_is_gas"),
815 ConditionResult::False
816 );
817 assert_eq!(provider.evaluate("mp_id_is_strom"), ConditionResult::True);
818 assert_eq!(provider.evaluate("mp_id_is_gas"), ConditionResult::False);
819 assert_eq!(
820 provider.evaluate("market_location_is_electricity"),
821 ConditionResult::True
822 );
823 assert_eq!(
824 provider.evaluate("market_location_is_gas"),
825 ConditionResult::False
826 );
827 assert_eq!(
828 provider.evaluate("unrelated_condition"),
829 ConditionResult::Unknown
830 );
831 }
832
833 #[test]
834 fn test_sector_provider_gas() {
835 let provider = SectorProvider::new(Sector::Gas);
836 assert_eq!(
837 provider.evaluate("recipient_is_strom"),
838 ConditionResult::False
839 );
840 assert_eq!(provider.evaluate("recipient_is_gas"), ConditionResult::True);
841 assert_eq!(
842 provider.evaluate("recipient_is_msb_gas"),
843 ConditionResult::True
844 );
845 }
846
847 #[test]
848 fn test_sector_from_variant() {
849 assert!(SectorProvider::from_variant("Strom").is_some());
850 assert!(SectorProvider::from_variant("Gas").is_some());
851 assert!(SectorProvider::from_variant("Water").is_none());
852 }
853
854 #[test]
857 fn test_market_role_provider() {
858 let provider =
859 MarketRoleProvider::new(vec![MarketRole::LF], vec![MarketRole::NB, MarketRole::MSB]);
860 assert_eq!(provider.evaluate("sender_is_lf"), ConditionResult::True);
861 assert_eq!(provider.evaluate("sender_is_nb"), ConditionResult::False);
862 assert_eq!(provider.evaluate("recipient_is_nb"), ConditionResult::True);
863 assert_eq!(provider.evaluate("recipient_is_msb"), ConditionResult::True);
864 assert_eq!(provider.evaluate("recipient_is_lf"), ConditionResult::False);
865 assert_eq!(provider.evaluate("unrelated"), ConditionResult::Unknown);
866 }
867
868 #[test]
869 fn test_market_role_provider_negated() {
870 let provider = MarketRoleProvider::new(vec![MarketRole::MSB], vec![MarketRole::NB]);
871 assert_eq!(
872 provider.evaluate("sender_role_is_not_msb"),
873 ConditionResult::False
874 );
875 assert_eq!(
876 provider.evaluate("sender_role_is_not_lf"),
877 ConditionResult::True
878 );
879 assert_eq!(
880 provider.evaluate("recipient_role_is_not_nb"),
881 ConditionResult::False
882 );
883 assert_eq!(
884 provider.evaluate("recipient_role_is_not_lf"),
885 ConditionResult::True
886 );
887 }
888
889 #[test]
890 fn test_market_role_unknown_suffix() {
891 let provider = MarketRoleProvider::new(vec![MarketRole::LF], vec![]);
892 assert_eq!(provider.evaluate("sender_is_xyz"), ConditionResult::Unknown);
894 }
895
896 #[test]
897 fn test_market_role_compound() {
898 let provider = MarketRoleProvider::new(vec![MarketRole::LF], vec![MarketRole::NB]);
899 assert_eq!(
901 provider.evaluate("recipient_is_lf_msb"),
902 ConditionResult::False
903 );
904 assert_eq!(
906 provider.evaluate("recipient_is_nb_lf"),
907 ConditionResult::True
908 );
909 assert_eq!(
911 provider.evaluate("recipient_is_lf_nb_msb"),
912 ConditionResult::True
913 );
914 assert_eq!(provider.evaluate("mp_id_role_is_lf"), ConditionResult::True);
916 }
917
918 #[test]
919 fn test_sector_provider_additional_names() {
920 let provider = SectorProvider::new(Sector::Strom);
921 assert_eq!(
922 provider.evaluate("mp_id_is_strom_sector"),
923 ConditionResult::True
924 );
925 assert_eq!(
926 provider.evaluate("marktpartner_is_strom"),
927 ConditionResult::True
928 );
929 assert_eq!(
930 provider.evaluate("recipient_is_gas_sector"),
931 ConditionResult::False
932 );
933 }
934
935 #[test]
936 fn test_composite_with_defaults() {
937 let composite = CompositeExternalProvider::with_defaults(
938 Some(Sector::Strom),
939 Some((vec![MarketRole::LF], vec![MarketRole::NB])),
940 None,
941 );
942 assert_eq!(
944 composite.evaluate("recipient_is_strom"),
945 ConditionResult::True
946 );
947 assert_eq!(composite.evaluate("sender_is_lf"), ConditionResult::True);
949 assert_eq!(composite.evaluate("some_unknown"), ConditionResult::Unknown);
951 }
952
953 fn make_konfigurationen_provider(product_code: Option<&str>) -> KonfigurationenProvider {
956 let mut categories = HashMap::new();
957 categories.insert(
958 "messprodukt_standard_marktlokation".to_string(),
959 vec!["9991000000044".to_string(), "9991000000052".to_string()],
960 );
961 categories.insert(
962 "messprodukt_standard_tranche".to_string(),
963 vec!["9991000000143".to_string()],
964 );
965 categories.insert(
966 "config_product_leistungskurve".to_string(),
967 vec!["9991000000721".to_string()],
968 );
969 KonfigurationenProvider::new(categories, product_code.map(String::from))
970 }
971
972 #[test]
973 fn test_konfigurationen_category_match() {
974 let provider = make_konfigurationen_provider(Some("9991000000044"));
975 assert_eq!(
976 provider.evaluate("messprodukt_standard_marktlokation"),
977 ConditionResult::True
978 );
979 assert_eq!(
980 provider.evaluate("messprodukt_standard_tranche"),
981 ConditionResult::False
982 );
983 assert_eq!(
984 provider.evaluate("config_product_leistungskurve"),
985 ConditionResult::False
986 );
987 }
988
989 #[test]
990 fn test_konfigurationen_generic_membership() {
991 let provider = make_konfigurationen_provider(Some("9991000000044"));
992 assert_eq!(
993 provider.evaluate("code_list_membership_check"),
994 ConditionResult::True
995 );
996
997 let provider2 = make_konfigurationen_provider(Some("0000000000000"));
998 assert_eq!(
999 provider2.evaluate("code_list_membership_check"),
1000 ConditionResult::False
1001 );
1002 }
1003
1004 #[test]
1005 fn test_konfigurationen_no_product_code() {
1006 let provider = make_konfigurationen_provider(None);
1007 assert_eq!(
1008 provider.evaluate("messprodukt_standard_marktlokation"),
1009 ConditionResult::Unknown
1010 );
1011 assert_eq!(
1012 provider.evaluate("code_list_membership_check"),
1013 ConditionResult::Unknown
1014 );
1015 }
1016
1017 #[test]
1018 fn test_konfigurationen_unknown_condition() {
1019 let provider = make_konfigurationen_provider(Some("9991000000044"));
1020 assert_eq!(
1021 provider.evaluate("completely_unrelated"),
1022 ConditionResult::Unknown
1023 );
1024 }
1025
1026 #[test]
1027 fn test_konfigurationen_from_json() {
1028 let json = r#"{
1029 "source": "Test",
1030 "categories": {
1031 "messprodukt_standard_marktlokation": ["9991000000044", "9991000000052"],
1032 "config_product_leistungskurve": ["9991000000721"]
1033 },
1034 "all_product_codes": ["9991000000044", "9991000000052", "9991000000721"]
1035 }"#;
1036 let provider =
1037 KonfigurationenProvider::from_json(json, Some("9991000000721".to_string())).unwrap();
1038 assert_eq!(
1039 provider.evaluate("messprodukt_standard_marktlokation"),
1040 ConditionResult::False
1041 );
1042 assert_eq!(
1043 provider.evaluate("config_product_leistungskurve"),
1044 ConditionResult::True
1045 );
1046 assert_eq!(
1047 provider.evaluate("code_list_membership_check"),
1048 ConditionResult::True
1049 );
1050 }
1051
1052 #[test]
1053 fn test_konfigurationen_specific_product() {
1054 let provider = make_konfigurationen_provider(Some("9991000000721"));
1056 assert_eq!(
1057 provider.evaluate("config_product_leistungskurve"),
1058 ConditionResult::True
1059 );
1060 assert_eq!(
1061 provider.evaluate("messprodukt_standard_marktlokation"),
1062 ConditionResult::False
1063 );
1064 }
1065
1066 #[test]
1069 fn test_country_plz_provider_de() {
1070 let provider = CountryPostalCodeProvider::new(Some("DE".to_string()));
1071 assert_eq!(
1072 provider.evaluate("country_code_has_plz"),
1073 ConditionResult::True
1074 );
1075 assert_eq!(
1076 provider.evaluate("country_has_postal_code"),
1077 ConditionResult::True
1078 );
1079 assert_eq!(
1080 provider.evaluate("country_has_postal_code_requirement"),
1081 ConditionResult::True
1082 );
1083 }
1084
1085 #[test]
1086 fn test_country_plz_provider_various_countries() {
1087 for code in &["AT", "CH", "FR", "NL", "GB", "NO", "IS", "LI"] {
1088 let provider = CountryPostalCodeProvider::new(Some(code.to_string()));
1089 assert_eq!(
1090 provider.evaluate("country_code_has_plz"),
1091 ConditionResult::True,
1092 "{code} should have PLZ"
1093 );
1094 }
1095 }
1096
1097 #[test]
1098 fn test_country_plz_provider_unknown_country() {
1099 let provider = CountryPostalCodeProvider::new(Some("XX".to_string()));
1100 assert_eq!(
1101 provider.evaluate("country_code_has_plz"),
1102 ConditionResult::False
1103 );
1104 }
1105
1106 #[test]
1107 fn test_country_plz_provider_no_country() {
1108 let provider = CountryPostalCodeProvider::new(None);
1109 assert_eq!(
1110 provider.evaluate("country_code_has_plz"),
1111 ConditionResult::Unknown
1112 );
1113 }
1114
1115 #[test]
1116 fn test_country_plz_provider_unrelated_condition() {
1117 let provider = CountryPostalCodeProvider::new(Some("DE".to_string()));
1118 assert_eq!(
1119 provider.evaluate("unrelated_condition"),
1120 ConditionResult::Unknown
1121 );
1122 }
1123
1124 #[test]
1127 fn test_sector_provider_sender_is_gas_sector() {
1128 let provider = SectorProvider::new(Sector::Gas);
1129 assert_eq!(
1130 provider.evaluate("sender_is_gas_sector"),
1131 ConditionResult::True
1132 );
1133 let provider_strom = SectorProvider::new(Sector::Strom);
1134 assert_eq!(
1135 provider_strom.evaluate("sender_is_gas_sector"),
1136 ConditionResult::False
1137 );
1138 }
1139
1140 #[test]
1143 fn test_market_role_mgv_kn() {
1144 let provider = MarketRoleProvider::new(vec![MarketRole::MGV], vec![MarketRole::KN]);
1145 assert_eq!(provider.evaluate("sender_is_mgv"), ConditionResult::True);
1146 assert_eq!(provider.evaluate("recipient_is_kn"), ConditionResult::True);
1147 assert_eq!(provider.evaluate("sender_is_kn"), ConditionResult::False);
1148 assert_eq!(
1149 provider.evaluate("recipient_is_mgv"),
1150 ConditionResult::False
1151 );
1152 }
1153
1154 #[test]
1157 fn test_market_role_or_compound() {
1158 let provider = MarketRoleProvider::new(vec![MarketRole::LF], vec![MarketRole::NB]);
1159 assert_eq!(
1161 provider.evaluate("recipient_is_lf_or_nb"),
1162 ConditionResult::True
1163 );
1164 assert_eq!(
1166 provider.evaluate("recipient_is_msb_or_mdl"),
1167 ConditionResult::False
1168 );
1169 }
1170
1171 #[test]
1174 fn test_market_role_recipient_not() {
1175 let provider = MarketRoleProvider::new(vec![MarketRole::LF], vec![MarketRole::NB]);
1176 assert_eq!(
1178 provider.evaluate("recipient_not_mgv_or_kn"),
1179 ConditionResult::True
1180 );
1181 assert_eq!(
1183 provider.evaluate("recipient_not_nb"),
1184 ConditionResult::False
1185 );
1186 assert_eq!(provider.evaluate("recipient_not_lf"), ConditionResult::True);
1188 assert_eq!(provider.evaluate("sender_not_lf"), ConditionResult::False);
1190 assert_eq!(
1192 provider.evaluate("sender_not_nb_or_msb"),
1193 ConditionResult::True
1194 );
1195 }
1196
1197 fn make_konfigurationen_provider_with_messprodukt(
1200 product_code: Option<&str>,
1201 ) -> KonfigurationenProvider {
1202 let mut categories = HashMap::new();
1203 categories.insert(
1204 "messprodukt_standard_marktlokation".to_string(),
1205 vec!["9991000000044".to_string(), "9991000000052".to_string()],
1206 );
1207 categories.insert(
1208 "messprodukt_standard_tranche".to_string(),
1209 vec!["9991000000143".to_string()],
1210 );
1211 categories.insert(
1212 "messprodukt_standard_messlokation".to_string(),
1213 vec!["9991000000200".to_string()],
1214 );
1215 categories.insert(
1216 "messprodukt_standard_netzlokation".to_string(),
1217 vec!["9991000000300".to_string()],
1218 );
1219 categories.insert(
1220 "messprodukt_typ2_smgw".to_string(),
1221 vec!["9991000000500".to_string()],
1222 );
1223 categories.insert(
1224 "messprodukt_esa".to_string(),
1225 vec!["9991000000600".to_string()],
1226 );
1227 categories.insert(
1228 "config_product_leistungskurve".to_string(),
1229 vec!["9991000000721".to_string()],
1230 );
1231 categories.insert(
1232 "config_product_adhoc_steuerkanal".to_string(),
1233 vec!["9991000000800".to_string()],
1234 );
1235 categories.insert(
1236 "produkte_aenderung_abrechnungsdaten".to_string(),
1237 vec!["9991000000900".to_string()],
1238 );
1239 categories.insert(
1240 "produkte_aenderung_lokation".to_string(),
1241 vec!["9991000001000".to_string()],
1242 );
1243 KonfigurationenProvider::new(categories, product_code.map(String::from))
1244 }
1245
1246 #[test]
1247 fn test_konfigurationen_alias_all_codes() {
1248 let provider = make_konfigurationen_provider_with_messprodukt(Some("9991000000044"));
1249 assert_eq!(
1251 provider.evaluate("is_konfigurationsprodukt_code"),
1252 ConditionResult::True
1253 );
1254 assert_eq!(
1256 provider.evaluate("lin_prefix_is_valid_product_code"),
1257 ConditionResult::True
1258 );
1259
1260 let provider2 = make_konfigurationen_provider_with_messprodukt(Some("0000000000000"));
1261 assert_eq!(
1262 provider2.evaluate("is_konfigurationsprodukt_code"),
1263 ConditionResult::False
1264 );
1265 }
1266
1267 #[test]
1268 fn test_konfigurationen_alias_messprodukt_union() {
1269 let provider = make_konfigurationen_provider_with_messprodukt(Some("9991000000044"));
1271 assert_eq!(
1272 provider.evaluate("is_messprodukt_code"),
1273 ConditionResult::True
1274 );
1275
1276 let provider2 = make_konfigurationen_provider_with_messprodukt(Some("9991000000500"));
1278 assert_eq!(
1279 provider2.evaluate("is_messprodukt_code"),
1280 ConditionResult::True
1281 );
1282
1283 let provider3 = make_konfigurationen_provider_with_messprodukt(Some("9991000000600"));
1285 assert_eq!(
1286 provider3.evaluate("is_messprodukt_code"),
1287 ConditionResult::True
1288 );
1289
1290 let provider4 = make_konfigurationen_provider_with_messprodukt(Some("9991000000721"));
1292 assert_eq!(
1293 provider4.evaluate("is_messprodukt_code"),
1294 ConditionResult::False
1295 );
1296 }
1297
1298 #[test]
1299 fn test_konfigurationen_alias_single_category() {
1300 let provider = make_konfigurationen_provider_with_messprodukt(Some("9991000000500"));
1301 assert_eq!(
1303 provider.evaluate("messprodukts_typ2_smgw"),
1304 ConditionResult::True
1305 );
1306 assert_eq!(
1308 provider.evaluate("product_in_typ2_smgw_codelist"),
1309 ConditionResult::True
1310 );
1311 assert_eq!(
1313 provider.evaluate("order_contains_smgw_type2_product"),
1314 ConditionResult::True
1315 );
1316
1317 let provider2 = make_konfigurationen_provider_with_messprodukt(Some("9991000000800"));
1318 assert_eq!(
1320 provider2.evaluate("valid_adhoc_steuerkanal_product"),
1321 ConditionResult::True
1322 );
1323
1324 let provider3 = make_konfigurationen_provider_with_messprodukt(Some("9991000000200"));
1325 assert_eq!(
1327 provider3.evaluate("product_code_level_messlokation"),
1328 ConditionResult::True
1329 );
1330
1331 let provider4 = make_konfigurationen_provider_with_messprodukt(Some("9991000000300"));
1332 assert_eq!(
1334 provider4.evaluate("product_code_level_netzlokation"),
1335 ConditionResult::True
1336 );
1337
1338 let provider5 = make_konfigurationen_provider_with_messprodukt(Some("9991000000900"));
1339 assert_eq!(
1341 provider5.evaluate("product_code_abrechnungsdaten_valid"),
1342 ConditionResult::True
1343 );
1344
1345 let provider6 = make_konfigurationen_provider_with_messprodukt(Some("9991000001000"));
1346 assert_eq!(
1348 provider6.evaluate("lin_product_code_is_lokationsaenderung_strom"),
1349 ConditionResult::True
1350 );
1351 }
1352
1353 #[test]
1356 fn test_builder_with_country_code() {
1357 let composite = CompositeExternalProvider::builder()
1358 .country_code(Some("DE".to_string()))
1359 .build();
1360 assert_eq!(
1361 composite.evaluate("country_code_has_plz"),
1362 ConditionResult::True
1363 );
1364 }
1365}