1use crate::{Eu4Error, Eu4ErrorKind};
2use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
3use std::{fmt, str::FromStr};
4
5#[derive(Clone, Copy, Hash, Eq, PartialEq, PartialOrd, Ord)]
14pub struct CountryTag([u8; 3]);
15
16impl CountryTag {
17 pub fn create<T: AsRef<[u8]>>(s: T) -> Result<Self, Eu4Error> {
26 if let [a, b, c] = *s.as_ref() {
27 if is_tagc(a) && is_tagc(b) && is_tagc(c) {
28 Ok(CountryTag([a, b, c]))
29 } else {
30 Err(Eu4Error::new(Eu4ErrorKind::CountryTagInvalidCharacters))
31 }
32 } else {
33 Err(Eu4Error::new(Eu4ErrorKind::CountryTagIncorrectSize))
34 }
35 }
36
37 pub fn is<T: AsRef<[u8]>>(&self, s: T) -> bool {
46 self.as_bytes() == s.as_ref()
47 }
48
49 pub fn as_bytes(&self) -> &[u8] {
57 &self.0
58 }
59
60 pub fn as_str(&self) -> &str {
68 debug_assert!(std::str::from_utf8(&self.0).is_ok());
71 unsafe { std::str::from_utf8_unchecked(&self.0) }
72 }
73}
74
75#[inline]
76pub(crate) const fn is_tagc(b: u8) -> bool {
77 b.is_ascii_alphanumeric() || b == b'-'
78}
79
80impl FromStr for CountryTag {
81 type Err = Eu4Error;
82
83 fn from_str(s: &str) -> Result<Self, Self::Err> {
84 CountryTag::create(s)
85 }
86}
87
88impl AsRef<str> for CountryTag {
89 fn as_ref(&self) -> &str {
90 self.as_str()
91 }
92}
93
94impl fmt::Debug for CountryTag {
95 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
96 f.write_str(self.as_ref())
97 }
98}
99
100impl fmt::Display for CountryTag {
101 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
102 f.write_str(self.as_ref())
103 }
104}
105
106impl Serialize for CountryTag {
107 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
108 where
109 S: Serializer,
110 {
111 serializer.serialize_str(self.as_ref())
112 }
113}
114
115impl<'de> Deserialize<'de> for CountryTag {
116 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
117 where
118 D: Deserializer<'de>,
119 {
120 struct CountryTagVisitor;
121
122 impl<'de> de::Visitor<'de> for CountryTagVisitor {
123 type Value = CountryTag;
124
125 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
126 formatter.write_str("struct CountryTag")
127 }
128
129 fn visit_str<A>(self, v: &str) -> Result<Self::Value, A>
130 where
131 A: de::Error,
132 {
133 v.parse().map_err(de::Error::custom)
134 }
135 }
136
137 deserializer.deserialize_str(CountryTagVisitor)
138 }
139}
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144
145 #[test]
146 fn tag_order() {
147 let tag1: CountryTag = "AAA".parse().unwrap();
148 let tag2: CountryTag = "BBB".parse().unwrap();
149 assert!(tag1 < tag2);
150 }
151
152 #[test]
153 fn parse_blank_tag() {
154 let tag1: CountryTag = "---".parse().unwrap();
155 assert_eq!(tag1.to_string(), String::from("---"));
156 }
157
158 #[test]
159 fn tag_debug_representation() {
160 let tag1: CountryTag = "FRA".parse().unwrap();
161 let debug = format!("{:?}", tag1);
162 assert_eq!(debug, String::from("FRA"));
163 }
164}