use crate::error::SovereigntyError;
#[derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
Hash,
serde::Serialize,
serde::Deserialize,
strum::Display,
strum::EnumString,
)]
#[strum(serialize_all = "UPPERCASE")]
pub enum Jurisdiction {
EU,
AT,
BE,
BG,
HR,
CY,
CZ,
DK,
EE,
FI,
FR,
DE,
GR,
HU,
IE,
IT,
LV,
LT,
LU,
MT,
NL,
PL,
PT,
RO,
SK,
SI,
ES,
SE,
}
impl Jurisdiction {
#[must_use]
pub const fn is_country(&self) -> bool {
!matches!(self, Self::EU)
}
#[must_use]
pub const fn member_states() -> &'static [Self] {
&[
Self::AT,
Self::BE,
Self::BG,
Self::HR,
Self::CY,
Self::CZ,
Self::DK,
Self::EE,
Self::FI,
Self::FR,
Self::DE,
Self::GR,
Self::HU,
Self::IE,
Self::IT,
Self::LV,
Self::LT,
Self::LU,
Self::MT,
Self::NL,
Self::PL,
Self::PT,
Self::RO,
Self::SK,
Self::SI,
Self::ES,
Self::SE,
]
}
#[must_use]
pub const fn can_replicate(from: Self, to: Self) -> bool {
if matches!(from, Self::EU) && matches!(to, Self::EU) {
return false;
}
if from.is_country() && !to.is_country() {
return false;
}
if matches!(from, Self::EU) && to.is_country() {
return true;
}
from as u8 == to as u8
}
pub fn validate_replication(from: Self, to: Self) -> Result<(), SovereigntyError> {
if Self::can_replicate(from, to) {
Ok(())
} else {
Err(SovereigntyError::new(format!(
"{from} cannot replicate to {to}"
)))
}
}
}
#[cfg(test)]
#[allow(clippy::expect_used)]
mod tests {
use std::str::FromStr;
use super::*;
#[test]
fn all_27_member_states_exist() {
assert_eq!(Jurisdiction::member_states().len(), 27);
}
#[test]
fn eu_umbrella_is_not_a_country() {
assert!(!Jurisdiction::EU.is_country());
}
#[test]
fn every_member_state_is_a_country() {
for member in Jurisdiction::member_states() {
assert!(member.is_country(), "{member} should be a country");
}
}
#[test]
fn jurisdiction_parses_from_uppercase_string() {
let j = Jurisdiction::from_str("DE").expect("should parse DE");
assert_eq!(j, Jurisdiction::DE);
}
#[test]
fn jurisdiction_rejects_invalid_string() {
assert!(Jurisdiction::from_str("US").is_err());
assert!(Jurisdiction::from_str("").is_err());
assert!(Jurisdiction::from_str("de").is_err());
}
#[test]
fn jurisdiction_displays_as_uppercase() {
assert_eq!(Jurisdiction::DE.to_string(), "DE");
assert_eq!(Jurisdiction::FR.to_string(), "FR");
assert_eq!(Jurisdiction::EU.to_string(), "EU");
}
#[test]
fn jurisdiction_roundtrips_through_display_and_parse() {
for member in Jurisdiction::member_states() {
let s = member.to_string();
let roundtrip = Jurisdiction::from_str(&s).expect("should roundtrip");
assert_eq!(*member, roundtrip);
}
let s = Jurisdiction::EU.to_string();
let roundtrip = Jurisdiction::from_str(&s).expect("EU roundtrip");
assert_eq!(Jurisdiction::EU, roundtrip);
}
#[test]
fn country_replicates_to_same_country() {
assert!(Jurisdiction::can_replicate(
Jurisdiction::DE,
Jurisdiction::DE
));
assert!(Jurisdiction::can_replicate(
Jurisdiction::FR,
Jurisdiction::FR
));
}
#[test]
fn country_cannot_replicate_to_different_country() {
assert!(!Jurisdiction::can_replicate(
Jurisdiction::DE,
Jurisdiction::FR
));
}
#[test]
fn country_cannot_replicate_to_eu_umbrella() {
assert!(!Jurisdiction::can_replicate(
Jurisdiction::DE,
Jurisdiction::EU
));
}
#[test]
fn eu_umbrella_replicates_to_any_member_state() {
for member in Jurisdiction::member_states() {
assert!(
Jurisdiction::can_replicate(Jurisdiction::EU, *member),
"EU should replicate to {member}"
);
}
}
#[test]
fn eu_umbrella_cannot_replicate_to_eu_umbrella() {
assert!(!Jurisdiction::can_replicate(
Jurisdiction::EU,
Jurisdiction::EU
));
}
#[test]
fn no_country_replicates_to_any_other_country_exhaustive() {
let states = Jurisdiction::member_states();
for from in states {
for to in states {
if from != to {
assert!(
!Jurisdiction::can_replicate(*from, *to),
"{from} should not replicate to {to}"
);
}
}
}
}
#[test]
fn validate_replication_ok_for_same_country() {
assert!(Jurisdiction::validate_replication(Jurisdiction::DE, Jurisdiction::DE).is_ok());
}
#[test]
fn validate_replication_err_for_cross_country() {
let err = Jurisdiction::validate_replication(Jurisdiction::DE, Jurisdiction::FR)
.expect_err("should fail");
let msg = err.to_string();
assert!(msg.contains("DE"), "error should mention source");
assert!(msg.contains("FR"), "error should mention target");
}
}