use core::str::FromStr;
use crate::parser::ParseError;
use crate::subtags::{Region, Subtag};
impl_tinystr_subtag!(
SubdivisionSuffix,
extensions::unicode,
subdivision_suffix,
extensions_unicode_subdivision_suffix,
1..=4,
s,
s.is_ascii_alphanumeric(),
s.to_ascii_lowercase(),
s.is_ascii_alphanumeric() && s.is_ascii_lowercase(),
InvalidExtension,
["sct"],
["toolooong"],
);
impl SubdivisionSuffix {
pub(crate) const UNKNOWN: Self = subdivision_suffix!("zzzz");
pub(crate) fn is_unknown(self) -> bool {
self == Self::UNKNOWN
}
}
#[derive(Debug, PartialEq, Eq, Clone, Hash, PartialOrd, Ord, Copy)]
#[non_exhaustive]
pub struct SubdivisionId {
pub region: Region,
pub suffix: SubdivisionSuffix,
}
impl SubdivisionId {
pub const fn new(region: Region, suffix: SubdivisionSuffix) -> Self {
Self { region, suffix }
}
#[inline]
pub fn try_from_str(s: &str) -> Result<Self, ParseError> {
Self::try_from_utf8(s.as_bytes())
}
pub fn try_from_utf8(code_units: &[u8]) -> Result<Self, ParseError> {
let is_alpha = code_units
.first()
.and_then(|b| {
b.is_ascii_alphabetic()
.then_some(true)
.or_else(|| b.is_ascii_digit().then_some(false))
})
.ok_or(ParseError::InvalidExtension)?;
let region_len = if is_alpha { 2 } else { 3 };
let (region_code_units, suffix_code_units) = code_units
.split_at_checked(region_len)
.ok_or(ParseError::InvalidExtension)?;
let region =
Region::try_from_utf8(region_code_units).map_err(|_| ParseError::InvalidExtension)?;
let suffix = SubdivisionSuffix::try_from_utf8(suffix_code_units)?;
Ok(Self { region, suffix })
}
pub const fn into_subtag(self) -> Subtag {
let result = self
.region
.to_tinystr()
.to_ascii_lowercase()
.concat(self.suffix.to_tinystr());
Subtag::from_tinystr_unvalidated(result)
}
}
impl writeable::Writeable for SubdivisionId {
#[inline]
fn write_to<W: core::fmt::Write + ?Sized>(&self, sink: &mut W) -> core::fmt::Result {
sink.write_str(self.region.to_tinystr().to_ascii_lowercase().as_str())?;
sink.write_str(self.suffix.as_str())
}
#[inline]
fn writeable_length_hint(&self) -> writeable::LengthHint {
self.region.writeable_length_hint() + self.suffix.writeable_length_hint()
}
}
writeable::impl_display_with_writeable!(SubdivisionId, #[cfg(feature = "alloc")]);
impl FromStr for SubdivisionId {
type Err = ParseError;
#[inline]
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::try_from_str(s)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_subdivisionid_fromstr() {
let si: SubdivisionId = "gbzzzz".parse().expect("Failed to parse SubdivisionId");
assert_eq!(si.region.to_string(), "GB");
assert_eq!(si.suffix.to_string(), "zzzz");
assert_eq!(si.to_string(), "gbzzzz");
for sample in ["", "gb", "o"] {
let oe: Result<SubdivisionId, _> = sample.parse();
assert!(oe.is_err(), "Should fail: {sample}");
}
}
}