#[cfg(test)]
pub(crate) mod test;
mod data;
use std::fmt;
#[doc(inline)]
pub use data::Code;
use crate::{
json,
warning::{self, GatherWarnings as _, IntoCaveat as _},
Verdict,
};
const RESERVED_PREFIX: u8 = b'x';
const ALPHA_2_LEN: usize = 2;
const ALPHA_3_LEN: usize = 3;
#[derive(Debug, Eq, PartialEq, Ord, PartialOrd)]
pub enum Warning {
ContainsEscapeCodes,
Decode(json::decode::Warning),
PreferUpperCase,
InvalidCode,
InvalidType { type_found: json::ValueKind },
InvalidLength,
InvalidReserved,
}
impl Warning {
fn invalid_type(elem: &json::Element<'_>) -> Self {
Self::InvalidType {
type_found: elem.value().kind(),
}
}
}
impl fmt::Display for Warning {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::ContainsEscapeCodes => f.write_str("The value contains escape codes but it does not need them"),
Self::Decode(warning) => fmt::Display::fmt(warning, f),
Self::PreferUpperCase => f.write_str("The country-code follows the ISO 3166-1 standard which states: the chars should be uppercase."),
Self::InvalidCode => f.write_str("The country-code is not a valid ISO 3166-1 code."),
Self::InvalidType { type_found } => {
write!(f, "The value should be a string but is `{type_found}`")
}
Self::InvalidLength => f.write_str("The country-code follows the ISO 3166-1 which states that the code should be 2 or 3 chars in length."),
Self::InvalidReserved => f.write_str("The country-code follows the ISO 3166-1 standard which states: all codes beginning with 'X' are reserved."),
}
}
}
impl crate::Warning for Warning {
fn id(&self) -> warning::Id {
match self {
Self::ContainsEscapeCodes => warning::Id::from_static("contains_escape_codes"),
Self::Decode(kind) => kind.id(),
Self::PreferUpperCase => warning::Id::from_static("prefer_upper_case"),
Self::InvalidCode => warning::Id::from_static("invalid_code"),
Self::InvalidType { type_found } => {
warning::Id::from_string(format!("invalid_type({type_found})"))
}
Self::InvalidLength => warning::Id::from_static("invalid_length"),
Self::InvalidReserved => warning::Id::from_static("invalid_reserved"),
}
}
}
#[derive(Debug)]
pub(crate) enum CodeSet {
Alpha2(Code),
Alpha3(Code),
}
impl From<json::decode::Warning> for Warning {
fn from(warn_kind: json::decode::Warning) -> Self {
Self::Decode(warn_kind)
}
}
impl json::FromJson<'_> for CodeSet {
type Warning = Warning;
fn from_json(elem: &json::Element<'_>) -> Verdict<CodeSet, Self::Warning> {
let mut warnings = warning::Set::new();
let value = elem.as_value();
let Some(s) = value.to_raw_str() else {
return warnings.bail(elem, Warning::invalid_type(elem));
};
let pending_str = s.has_escapes(elem).gather_warnings_into(&mut warnings);
let s = match pending_str {
json::PendingStr::NoEscapes(s) => s,
json::PendingStr::HasEscapes(_) => {
return warnings.bail(elem, Warning::ContainsEscapeCodes);
}
};
let bytes = s.as_bytes();
if let [a, b, c] = bytes {
let triplet: [u8; ALPHA_3_LEN] = [
a.to_ascii_uppercase(),
b.to_ascii_uppercase(),
c.to_ascii_uppercase(),
];
if triplet != bytes {
warnings.insert(elem, Warning::PreferUpperCase);
}
if a.eq_ignore_ascii_case(&RESERVED_PREFIX) {
warnings.insert(elem, Warning::InvalidReserved);
}
let Some(code) = Code::from_alpha_3(triplet) else {
return warnings.bail(elem, Warning::InvalidCode);
};
Ok(CodeSet::Alpha3(code).into_caveat(warnings))
} else if let [a, b] = bytes {
let pair: [u8; ALPHA_2_LEN] = [a.to_ascii_uppercase(), b.to_ascii_uppercase()];
if pair != bytes {
warnings.insert(elem, Warning::PreferUpperCase);
}
if a.eq_ignore_ascii_case(&RESERVED_PREFIX) {
warnings.insert(elem, Warning::InvalidReserved);
}
let Some(code) = Code::from_alpha_2(pair) else {
return warnings.bail(elem, Warning::InvalidCode);
};
Ok(CodeSet::Alpha2(code).into_caveat(warnings))
} else {
warnings.bail(elem, Warning::InvalidLength)
}
}
}
impl fmt::Display for Code {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.into_alpha_2_str())
}
}
macro_rules! country_codes {
[$(($name:ident, $alpha2:literal, $alpha3:literal)),*] => {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum Code {
$($name),*
}
impl Code {
pub(super) const fn from_alpha_2(code: [u8; 2]) -> Option<Self> {
match &code {
$($alpha2 => Some(Self::$name),)*
_ => None
}
}
pub(super) const fn from_alpha_3(code: [u8; 3]) -> Option<Self> {
match &code {
$($alpha3 => Some(Self::$name),)*
_ => None
}
}
pub fn into_alpha_2_str(self) -> &'static str {
let bytes = match self {
$(Self::$name => $alpha2),*
};
std::str::from_utf8(bytes).expect("The country code bytes are known to be valid UTF8 as they are embedded into the binary")
}
pub fn into_alpha_3_str(self) -> &'static str {
let bytes = match self {
$(Self::$name => $alpha3),*
};
std::str::from_utf8(bytes).expect("The country code bytes are known to be valid UTF8 as they are embedded into the binary")
}
}
};
}
pub(crate) use country_codes;