pub fn is_valid_region(code: &str) -> bool {
matches!(
code,
"AD" | "AE" | "AF" | "AG" | "AI" | "AL" | "AM" | "AO" | "AQ" | "AR"
| "AS" | "AT" | "AU" | "AW" | "AX" | "AZ"
| "BA" | "BB" | "BD" | "BE" | "BF" | "BG" | "BH" | "BI" | "BJ" | "BL"
| "BM" | "BN" | "BO" | "BQ" | "BR" | "BS" | "BT" | "BV" | "BW" | "BY" | "BZ"
| "CA" | "CC" | "CD" | "CF" | "CG" | "CH" | "CI" | "CK" | "CL" | "CM"
| "CN" | "CO" | "CR" | "CU" | "CV" | "CW" | "CX" | "CY" | "CZ"
| "DE" | "DJ" | "DK" | "DM" | "DO" | "DZ"
| "EC" | "EE" | "EG" | "EH" | "ER" | "ES" | "ET"
| "FI" | "FJ" | "FK" | "FM" | "FO" | "FR"
| "GA" | "GB" | "GD" | "GE" | "GF" | "GG" | "GH" | "GI" | "GL" | "GM"
| "GN" | "GP" | "GQ" | "GR" | "GS" | "GT" | "GU" | "GW" | "GY"
| "HK" | "HM" | "HN" | "HR" | "HT" | "HU"
| "ID" | "IE" | "IL" | "IM" | "IN" | "IO" | "IQ" | "IR" | "IS" | "IT"
| "JE" | "JM" | "JO" | "JP"
| "KE" | "KG" | "KH" | "KI" | "KM" | "KN" | "KP" | "KR" | "KW" | "KY" | "KZ"
| "LA" | "LB" | "LC" | "LI" | "LK" | "LR" | "LS" | "LT" | "LU" | "LV" | "LY"
| "MA" | "MC" | "MD" | "ME" | "MF" | "MG" | "MH" | "MK" | "ML" | "MM"
| "MN" | "MO" | "MP" | "MQ" | "MR" | "MS" | "MT" | "MU" | "MV" | "MW"
| "MX" | "MY" | "MZ"
| "NA" | "NC" | "NE" | "NF" | "NG" | "NI" | "NL" | "NO" | "NP" | "NR"
| "NU" | "NZ"
| "OM"
| "PA" | "PE" | "PF" | "PG" | "PH" | "PK" | "PL" | "PM" | "PN" | "PR"
| "PS" | "PT" | "PW" | "PY"
| "QA"
| "RE" | "RO" | "RS" | "RU" | "RW"
| "SA" | "SB" | "SC" | "SD" | "SE" | "SG" | "SH" | "SI" | "SJ" | "SK"
| "SL" | "SM" | "SN" | "SO" | "SR" | "SS" | "ST" | "SV" | "SX" | "SY" | "SZ"
| "TC" | "TD" | "TF" | "TG" | "TH" | "TJ" | "TK" | "TL" | "TM" | "TN"
| "TO" | "TR" | "TT" | "TV" | "TW" | "TZ"
| "UA" | "UG" | "UM" | "US" | "UY" | "UZ"
| "VA" | "VC" | "VE" | "VG" | "VI" | "VN" | "VU"
| "WF" | "WS"
| "YE" | "YT"
| "ZA" | "ZM" | "ZW"
| "XK"
| "AA"
| "QM" | "QN" | "QO" | "QP" | "QQ" | "QR" | "QS" | "QT"
| "QU" | "QV" | "QW" | "QX" | "QY" | "QZ"
| "XA" | "XB" | "XC" | "XD" | "XE" | "XF" | "XG" | "XH"
| "XI" | "XJ" | "XL" | "XM" | "XN" | "XO" | "XP" | "XQ"
| "XR" | "XS" | "XT" | "XU" | "XV" | "XW" | "XX" | "XY" | "XZ"
| "ZZ"
)
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Region([u8; 2]);
impl core::fmt::Debug for Region {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_tuple("Region").field(&self.as_str()).finish()
}
}
impl Region {
#[must_use]
pub fn parse(raw: &str) -> Option<Self> {
let upper = raw.trim().to_ascii_uppercase();
if !is_valid_region(&upper) {
return None;
}
let bytes: [u8; 2] = upper.as_bytes().try_into().ok()?;
Some(Self(bytes))
}
#[must_use]
pub fn as_str(&self) -> &str {
core::str::from_utf8(&self.0).unwrap_or("")
}
}
impl AsRef<str> for Region {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl core::fmt::Display for Region {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(self.as_str())
}
}
impl core::str::FromStr for Region {
type Err = InvalidRegion;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::parse(s).ok_or(InvalidRegion)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct InvalidRegion;
impl core::fmt::Display for InvalidRegion {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(
"not an accepted ISO 3166-1 alpha-2 region code \
(assigned, or user-reserved AA/QM-QZ/XA-XZ/ZZ)",
)
}
}
impl core::error::Error for InvalidRegion {}
impl serde::Serialize for Region {
fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
s.serialize_str(self.as_str())
}
}
impl<'de> serde::Deserialize<'de> for Region {
fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
let raw = String::deserialize(d)?;
Self::parse(&raw).ok_or_else(|| serde::de::Error::custom(InvalidRegion))
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests;