use metering::*;
macro_rules! assert_contract {
($ty:ty) => {{
let name = stringify!($ty);
let all = <$ty>::ALL;
let codes = <$ty>::CODES;
assert_eq!(
all.len(),
codes.len(),
"{name}: ALL and CODES differ in length"
);
assert!(!all.is_empty(), "{name}: ALL is empty");
let mut seen = std::collections::BTreeSet::new();
for (value, code) in all.iter().zip(codes) {
assert_eq!(
value.as_str(),
*code,
"{name}: CODES is out of step with as_str"
);
assert_eq!(&value.to_string(), *code, "{name}: Display is not as_str");
assert_eq!(
&code
.parse::<$ty>()
.unwrap_or_else(|e| panic!("{name}/{code}: {e}")),
value,
"{name}: FromStr does not invert as_str"
);
let messy = format!(" {}\t", code.to_lowercase());
assert_eq!(
&messy
.parse::<$ty>()
.unwrap_or_else(|e| panic!("{name}/{messy:?}: {e}")),
value,
"{name}: FromStr is not lenient about case and whitespace"
);
assert!(seen.insert(*code), "{name}: duplicate code {code}");
#[cfg(feature = "serde")]
{
let json = serde_json::to_string(value).expect("serialises");
assert_eq!(
json,
format!("\"{code}\""),
"{name}: the serde tag is not the code — a CHECK constraint \
generated from CODES would reject rows this crate writes"
);
let back: $ty = serde_json::from_str(&json).expect("deserialises");
assert_eq!(&back, value, "{name}: serde does not round-trip");
}
let width = code.len() + 3;
let padded = format!("{value:>width$}");
assert!(
padded.ends_with(code) && padded.len() == width,
"{name}: Display ignores width and alignment, so a code cannot \
be lined up in a table"
);
}
assert!(
"__NOT_A_CODE__".parse::<$ty>().is_err(),
"{name}: an unrecognised code must be an error, never a default"
);
let err = "__NOT_A_CODE__".parse::<$ty>().unwrap_err();
assert_eq!(
err.type_name(),
name,
"{name}: the error names the wrong type"
);
assert_eq!(
err.expected_values(),
Some(codes),
"{name}: the error does not carry the accepted set"
);
}};
}
#[test]
fn every_coded_enum_holds_the_contract() {
use metering::calendar::{DayBoundary, DayKind};
use metering::classification::SeriesOrigin;
use metering::conversion::G685FinalRounding;
use metering::holiday::Holiday;
use metering::ids::MaloIssuer;
use metering::lifecycle::{MeterLifecycleEventType, MeterStatus};
use metering::load_profile::SlpDayType;
use metering::measurement_series::ProvenanceEventType;
use metering::obis::RegisterUnit;
use metering::reading::AnomalyKind;
use metering::rollout::QuotaScope;
use metering::zaehlzeit::DayGroup;
assert_contract!(Sparte);
assert_contract!(QualityFlag);
assert_contract!(MaloIssuer);
assert_contract!(RegisterUnit);
assert_contract!(DayKind);
assert_contract!(DayBoundary);
assert_contract!(Bundesland);
assert_contract!(Holiday);
assert_contract!(LoadProfile);
assert_contract!(SlpDayType);
assert_contract!(AnomalyKind);
assert_contract!(ValidationRuleId);
assert_contract!(ValidationSeverity);
assert_contract!(QualityGrade);
assert_contract!(SubstituteMethod);
assert_contract!(SubstitutionReason);
assert_contract!(Messtyp);
assert_contract!(SeriesOrigin);
assert_contract!(DayGroup);
assert_contract!(G685FinalRounding);
assert_contract!(MarktRolle);
assert_contract!(EnergyFlow);
assert_contract!(ProvenanceEventType);
assert_contract!(MeterStatus);
assert_contract!(MeterLifecycleEventType);
assert_contract!(VirtualMeterKind);
assert_contract!(RolloutObligation);
assert_contract!(QuotaScope);
assert_contract!(EligibilityBasis);
assert_contract!(Finding);
assert_contract!(Zaehlertyp);
assert_contract!(Bilanzierungsmethode);
assert_contract!(Delivery);
assert_contract!(SharingReadiness);
}
#[test]
fn measurement_unit_writes_two_codes_and_reads_many() {
assert_eq!(MeasurementUnit::ALL.len(), MeasurementUnit::CODES.len());
for (v, code) in MeasurementUnit::ALL.iter().zip(MeasurementUnit::CODES) {
assert_eq!(v.as_str(), *code);
assert_eq!(&v.to_string(), *code);
assert_eq!(&code.parse::<MeasurementUnit>().unwrap(), v);
#[cfg(feature = "serde")]
assert_eq!(serde_json::to_string(v).unwrap(), format!("\"{code}\""));
}
for wide in ["m³", "kWh_th", "MTQ", "cbm"] {
assert!(wide.parse::<MeasurementUnit>().is_ok(), "{wide}");
}
}
#[test]
fn input_aliases_normalise_onto_the_canonical_code() {
assert_eq!("WÄRME".parse::<Sparte>().unwrap(), Sparte::Waerme);
assert_eq!("wärme".parse::<Sparte>().unwrap(), Sparte::Waerme);
assert_eq!(
Sparte::Waerme.to_string(),
"WAERME",
"one spelling comes out"
);
assert!(!Sparte::CODES.contains(&"WÄRME"), "an alias is not a code");
assert_eq!("DE-BY".parse::<Bundesland>().unwrap(), Bundesland::By);
assert_eq!("de-by".parse::<Bundesland>().unwrap(), Bundesland::By);
assert_eq!(Bundesland::By.to_string(), "BY");
assert_eq!("EF".parse::<LoadProfile>().unwrap(), LoadProfile::GasHEF);
assert_eq!("MF".parse::<LoadProfile>().unwrap(), LoadProfile::GasHMF);
assert_eq!(LoadProfile::GasHEF.to_string(), "HEF");
assert_eq!("ÜNB".parse::<MarktRolle>().unwrap(), MarktRolle::Uenb);
assert_eq!(MarktRolle::Uenb.abbreviation(), "ÜNB");
assert_eq!(MarktRolle::Uenb.as_str(), "UENB");
}
#[test]
fn display_forms_are_codes_and_descriptions_are_prose() {
use metering::holiday::Holiday;
use metering::obis::RegisterUnit;
assert_eq!(Holiday::BussUndBettag.as_str(), "BUSS_UND_BETTAG");
assert_eq!(Holiday::BussUndBettag.name(), "Buß- und Bettag");
assert_eq!(RegisterUnit::KiloWattHour.as_str(), "KILO_WATT_HOUR");
assert_eq!(RegisterUnit::KiloWattHour.symbol(), "kWh");
assert_eq!(SubstituteMethod::ZeroFill.as_str(), "ZERO_FILL");
assert_eq!(
SubstituteMethod::ZeroFill.description(),
"Nullwert (dokumentierter Lieferstopp)"
);
for m in SubstituteMethod::ALL {
assert!(!m.description().is_empty());
assert_ne!(m.description(), m.as_str());
}
for r in SubstitutionReason::ALL {
assert!(!r.description().is_empty());
assert_ne!(r.description(), r.as_str());
}
}
#[test]
fn validation_rule_ids_are_the_vnn_codes_everywhere() {
assert_eq!(ValidationRuleId::GapDetected.as_str(), "V01");
assert_eq!(ValidationRuleId::ImplausiblePower.to_string(), "V12");
assert_eq!(
"v01".parse::<ValidationRuleId>().unwrap(),
ValidationRuleId::GapDetected
);
#[cfg(feature = "serde")]
assert_eq!(
serde_json::to_string(&ValidationRuleId::DstAmbiguity).unwrap(),
"\"V07\""
);
assert!("V10".parse::<ValidationRuleId>().is_err());
assert!(!ValidationRuleId::CODES.contains(&"V10"));
assert_eq!(ValidationRuleId::CODES.len(), 11);
}