1use crate::error::CoreError;
13use serde_with::{DeserializeFromStr, SerializeDisplay};
14use std::{env::VarError, fmt::Display, str::FromStr};
15
16#[derive(Clone, Debug, PartialEq, Eq, DeserializeFromStr, SerializeDisplay)]
25pub struct CountryCode(String);
26
27pub const ENVVAR_COUNTRY_CODE: &str = "RFHAM_COUNTRY";
28
29pub type CountryCodeNumeric = u16;
30
31pub fn country_code_us() -> CountryCode {
36 CountryCode::new_unchecked("US")
37}
38
39pub fn country_code_uk() -> CountryCode {
40 CountryCode::new_unchecked("UK")
41}
42
43impl Display for CountryCode {
56 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
57 write!(f, "{}", self.0)
58 }
59}
60
61impl FromStr for CountryCode {
62 type Err = CoreError;
63
64 fn from_str(s: &str) -> Result<Self, Self::Err> {
65 if Self::is_valid(s) {
66 Ok(Self(s.to_string()))
67 } else {
68 Err(CoreError::InvalidValueFromStr(s.to_string(), "CountryCode"))
69 }
70 }
71}
72
73impl From<CountryCode> for String {
74 fn from(value: CountryCode) -> Self {
75 value.0
76 }
77}
78
79impl AsRef<str> for CountryCode {
80 fn as_ref(&self) -> &str {
81 self.0.as_ref()
82 }
83}
84
85impl CountryCode {
86 pub(crate) fn new_unchecked(s: &str) -> Self {
87 Self(s.to_string())
88 }
89
90 pub fn from_env() -> Result<Option<Self>, CoreError> {
91 Self::from_env_named(ENVVAR_COUNTRY_CODE)
92 }
93
94 pub fn from_env_named(envvar_name: &str) -> Result<Option<Self>, CoreError> {
95 match std::env::var(envvar_name) {
96 Ok(value) => Ok(Some(Self::from_str(&value)?)),
97 Err(VarError::NotPresent) => Ok(None),
98 Err(VarError::NotUnicode(value)) => Err(CoreError::InvalidValueFromStr(
99 format!("{:#?}", value),
100 "CountryCode",
101 )),
102 }
103 }
104
105 pub fn as_str(&self) -> &str {
106 self.0.as_str()
107 }
108
109 pub fn to_numeric(&self) -> CountryCodeNumeric {
110 country_code_coded(self.0.as_str())
111 }
112
113 pub fn is_valid(s: &str) -> bool {
114 s.len() == 2 && s.chars().all(|c| c.is_ascii_uppercase())
116 }
117}
118
119impl From<CountryCode> for CountryCodeNumeric {
122 fn from(country_code: CountryCode) -> Self {
123 country_code_coded(country_code.as_str())
124 }
125}
126
127impl TryFrom<CountryCodeNumeric> for CountryCode {
128 type Error = CoreError;
129
130 fn try_from(value: CountryCodeNumeric) -> Result<Self, Self::Error> {
131 let country_code = country_code_decoded(value)?;
132 CountryCode::from_str(&country_code)
133 }
134}
135
136const CC_CODE_BASIS: u32 = 'A' as u32;
141
142pub(crate) fn country_code_coded(s: &str) -> CountryCodeNumeric {
144 let pair: Vec<u16> = s
145 .chars()
146 .map(|c| (c as u32 - CC_CODE_BASIS) as u16)
147 .collect();
148 (pair[0] << 8) + pair[1]
149}
150
151fn country_code_decoded(country_code: CountryCodeNumeric) -> Result<String, CoreError> {
153 Ok(vec![
154 char::from_u32((country_code >> 8) as u32 + CC_CODE_BASIS).ok_or(
155 CoreError::InvalidValue(country_code.to_string(), "CountryCode"),
156 )?,
157 char::from_u32((country_code & 0b11111111) as u32 + CC_CODE_BASIS).ok_or(
158 CoreError::InvalidValue(country_code.to_string(), "CountryCode"),
159 )?,
160 ]
161 .into_iter()
162 .collect::<String>())
163}
164
165#[cfg(test)]
174mod test {
175 use super::{CountryCodeNumeric, country_code_coded, country_code_decoded};
176 use pretty_assertions::assert_eq;
177
178 const VALID_MAPPINGS: &[(&str, CountryCodeNumeric)] =
179 &[("US", 5138_u16), ("GB", 1537), ("CN", 525)];
180
181 #[test]
182 fn country_code_to_number() {
183 for (string, numeric) in VALID_MAPPINGS {
184 assert_eq!(*numeric, country_code_coded(string));
185 }
186 }
187
188 #[test]
189 fn country_code_to_string() {
190 for (string, numeric) in VALID_MAPPINGS {
191 assert_eq!(string, &country_code_decoded(*numeric).unwrap().as_str());
192 }
193 }
194}