country_code/iso3166_2/
mod.rs

1//! [ISO 3166-2 - Wikipedia](https://en.wikipedia.org/wiki/ISO_3166-2)
2
3//
4#[macro_export]
5macro_rules! country_subdivision_code {
6    (
7        $country_code_ty:ty, $country_code_val:expr;
8
9        $( #[$meta:meta] )*
10        $pub:vis enum $name:ident {
11            $(
12                $( #[$variant_meta:meta] )*
13                $variant:ident,
14            )+
15        }
16    ) => {
17        $(#[$meta])*
18        $pub enum $name {
19            $(
20                $( #[$variant_meta] )*
21                $variant,
22            )+
23            Other($crate::alloc::boxed::Box<str>),
24        }
25
26        //
27        impl $name {
28            pub const COUNTRY_CODE: $country_code_ty = $country_code_val;
29
30            pub const VARS: &'static [$name] = &[
31                $(
32                    $name::$variant,
33                )+
34            ];
35        }
36
37        //
38        impl ::core::str::FromStr for $name {
39            type Err = $crate::error::CountrySubdivisionCodeParseError;
40
41            fn from_str(s: &str) -> Result<Self, Self::Err> {
42                let country_code_s = s.chars().take_while(|x| x != &'-' && x != &'_')
43                                                .collect::<$crate::alloc::string::String>();
44                let country_code = country_code_s.parse::<$country_code_ty>()
45                                                    .map_err(|_| $crate::error::CountrySubdivisionCodeParseError::CountryCodeInvalid(country_code_s.as_str().into()))?;
46
47                if country_code != Self::COUNTRY_CODE {
48                    return Err($crate::error::CountrySubdivisionCodeParseError::CountryCodeMismatch(country_code_s.into()));
49                }
50
51                let subdivision_code_s = if s.len() > country_code_s.len() + 1 {
52                    &s[country_code_s.len() + 1..]
53                } else {
54                    return Err($crate::error::CountrySubdivisionCodeParseError::SubdivisionCodeMissing);
55                };
56
57                match subdivision_code_s {
58                    $(
59                        ::core::stringify!($variant) => Ok(Self::$variant),
60                    )+
61                    s if s.len() == 2 => Ok(Self::Other(s.into())),
62                    s => Err($crate::error::CountrySubdivisionCodeParseError::SubdivisionCodeInvalid(s.into()))
63                }
64            }
65        }
66
67        //
68        impl ::core::fmt::Display for $name {
69            fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
70                match self {
71                    $(
72                        Self::$variant => ::core::write!(f, "{}-{}", $name::COUNTRY_CODE, ::core::stringify!($variant)),
73                    )+
74                    Self::Other(s) => ::core::write!(f, "{}-{}", $name::COUNTRY_CODE, s)
75                }
76            }
77        }
78
79        //
80        impl ::core::default::Default for $name {
81            fn default() -> Self {
82                Self::Other(Default::default())
83            }
84        }
85
86        //
87        impl ::core::cmp::PartialEq for $name {
88            fn eq(&self, other: &Self) -> bool {
89                $crate::alloc::format!("{}", self) == $crate::alloc::format!("{}", other)
90            }
91        }
92
93        impl ::core::cmp::Eq for $name {
94        }
95
96        //
97        $crate::impl_partial_eq_str_for_display! { str, $name }
98        $crate::impl_partial_eq_str_for_display! { &'a str, $name }
99        $crate::impl_partial_eq_str_for_display! { $crate::alloc::borrow::Cow<'a, str>, $name }
100        $crate::impl_partial_eq_str_for_display! { $crate::alloc::string::String, $name }
101
102        //
103        #[cfg(feature = "std")]
104        impl ::std::hash::Hash for $name {
105            fn hash<H: ::std::hash::Hasher>(&self, state: &mut H) {
106                $crate::alloc::format!("{}", self).hash(state);
107            }
108        }
109
110        //
111        #[cfg(feature = "serde")]
112        impl<'de> ::serde::Deserialize<'de> for $name {
113            fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
114            where
115                D: ::serde::Deserializer<'de>,
116            {
117                use ::core::str::FromStr as _;
118
119                let s = $crate::alloc::boxed::Box::<str>::deserialize(deserializer)?;
120                Self::from_str(&s).map_err(::serde::de::Error::custom)
121            }
122        }
123
124        //
125        #[cfg(feature = "serde")]
126        impl ::serde::Serialize for $name {
127            fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
128            where
129                S: ::serde::Serializer,
130            {
131                use $crate::alloc::string::ToString as _;
132
133                self.to_string().serialize(serializer)
134            }
135        }
136    };
137}
138
139//
140pub mod cn;
141pub mod us;
142
143pub use cn::CountrySubdivisionCode as CNSubdivisionCode;
144pub use us::CountrySubdivisionCode as USSubdivisionCode;
145
146//
147//
148//
149use crate::iso3166_1::alpha_2::CountryCode;
150
151#[derive(Debug, Clone, PartialEq, Eq)]
152#[cfg_attr(feature = "std", derive(Hash))]
153pub enum SubdivisionCode {
154    CN(CNSubdivisionCode),
155    US(USSubdivisionCode),
156    Other(CountryCode, Option<::alloc::boxed::Box<str>>),
157}
158
159//
160impl ::core::str::FromStr for SubdivisionCode {
161    type Err = crate::error::CountrySubdivisionCodeParseError;
162
163    fn from_str(s: &str) -> Result<Self, Self::Err> {
164        let country_code_s = s
165            .chars()
166            .take_while(|x| x != &'-' && x != &'_')
167            .collect::<::alloc::string::String>();
168        let country_code = country_code_s.parse::<CountryCode>().map_err(|_| {
169            crate::error::CountrySubdivisionCodeParseError::CountryCodeInvalid(
170                country_code_s.as_str().into(),
171            )
172        })?;
173
174        match country_code {
175            CountryCode::CN => {
176                let subdivision = s.parse::<CNSubdivisionCode>()?;
177                Ok(Self::CN(subdivision))
178            }
179            CountryCode::US => {
180                let subdivision = s.parse::<USSubdivisionCode>()?;
181                Ok(Self::US(subdivision))
182            }
183            country => {
184                let subdivision_code_s = if s.len() > country_code_s.len() + 1 {
185                    let subdivision_code_s = &s[country_code_s.len() + 1..];
186                    Some(subdivision_code_s.into())
187                } else {
188                    None
189                };
190
191                Ok(Self::Other(country, subdivision_code_s))
192            }
193        }
194    }
195}
196
197//
198impl ::core::fmt::Display for SubdivisionCode {
199    fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
200        match self {
201            Self::CN(subdivision) => ::core::write!(f, "{subdivision}"),
202            Self::US(subdivision) => ::core::write!(f, "{subdivision}"),
203            Self::Other(country, Some(s)) => ::core::write!(f, "{country}-{s}"),
204            Self::Other(country, None) => ::core::write!(f, "{country}-"),
205        }
206    }
207}
208
209//
210crate::impl_partial_eq_str_for_display! { str, SubdivisionCode }
211crate::impl_partial_eq_str_for_display! { &'a str, SubdivisionCode }
212crate::impl_partial_eq_str_for_display! { ::alloc::borrow::Cow<'a, str>, SubdivisionCode }
213crate::impl_partial_eq_str_for_display! { ::alloc::string::String, SubdivisionCode }
214
215//
216#[cfg(feature = "serde")]
217impl<'de> ::serde::Deserialize<'de> for SubdivisionCode {
218    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
219    where
220        D: ::serde::Deserializer<'de>,
221    {
222        use ::core::str::FromStr as _;
223
224        let s = ::alloc::boxed::Box::<str>::deserialize(deserializer)?;
225        Self::from_str(&s).map_err(::serde::de::Error::custom)
226    }
227}
228
229//
230#[cfg(feature = "serde")]
231impl ::serde::Serialize for SubdivisionCode {
232    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
233    where
234        S: ::serde::Serializer,
235    {
236        use ::alloc::string::ToString as _;
237
238        self.to_string().serialize(serializer)
239    }
240}
241
242#[cfg(test)]
243mod tests {
244    use super::*;
245
246    use alloc::string::ToString as _;
247
248    #[test]
249    fn test_subdivision_code() {
250        //
251        assert_eq!(
252            SubdivisionCode::US(us::CountrySubdivisionCode::NY).to_string(),
253            "US-NY"
254        );
255        assert_eq!(
256            "US-NY".parse::<SubdivisionCode>().unwrap(),
257            SubdivisionCode::US(us::CountrySubdivisionCode::NY)
258        );
259
260        //
261        assert_eq!(
262            SubdivisionCode::Other(CountryCode::ZW, Some("BU".into())).to_string(),
263            "ZW-BU"
264        );
265        assert_eq!(
266            "ZW-BU".parse::<SubdivisionCode>().unwrap(),
267            SubdivisionCode::Other(CountryCode::ZW, Some("BU".into()))
268        );
269
270        //
271        assert_eq!(
272            SubdivisionCode::Other(CountryCode::AI, None).to_string(),
273            "AI-"
274        );
275        assert_eq!(
276            "AI-".parse::<SubdivisionCode>().unwrap(),
277            SubdivisionCode::Other(CountryCode::AI, None)
278        );
279    }
280}