bird_chat/
identifier.rs

1use std::borrow::Cow;
2use std::fmt::{Display, Formatter};
3use std::str::pattern::{Pattern, Searcher};
4
5#[derive(Clone, Debug)]
6pub enum IdentifierInner<'a> {
7    Fulled(Cow<'a, str>),
8    Partial(Cow<'a, str>, Cow<'a, str>),
9}
10
11#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
12#[serde(try_from = "String", into = "String")]
13#[repr(transparent)]
14pub struct Identifier<'a>(IdentifierInner<'a>);
15
16#[derive(Debug, Clone, Copy, Eq, PartialEq, thiserror::Error)]
17pub enum IdentifierError {
18    #[error("Value contains double dot")]
19    ValueContainsDoubleDot,
20    #[error("Key contains double dot")]
21    KeyContainsDoubleDot,
22    #[error("Fulled contains more than one double dot")]
23    FulledContainsMoreThanOneDoubleDot,
24    #[error("Fulled contains no double dots")]
25    FulledContainsNoDoubleDot,
26}
27
28impl<'a> Identifier<'a> {
29    const fn new(inner: IdentifierInner<'a>) -> Self {
30        Self(inner)
31    }
32
33    const fn get_inner(&self) -> &IdentifierInner<'a> {
34        &self.0
35    }
36
37    pub fn into_inner(self) -> IdentifierInner<'a> {
38        self.0
39    }
40
41    pub const unsafe fn from_inner_unchecked(inner: IdentifierInner<'a>) -> Self {
42        Self::new(inner)
43    }
44
45    pub fn from_inner(inner: IdentifierInner<'a>) -> Result<Self, IdentifierError> {
46        match inner {
47            IdentifierInner::Fulled(fulled) => Self::new_fulled(fulled),
48            IdentifierInner::Partial(key, value) => Self::new_partial(key, value),
49        }
50    }
51
52    /// If value is full identifier then new_fulled function with value argument will be used.
53    /// If value is not full identifier then new_partial with default_key and value arguments will be used
54    pub fn new_with_default(value: impl Into<Cow<'a, str>>, default_key: impl Into<Cow<'a, str>>) -> Result<Self, IdentifierError> {
55        let value = value.into();
56        let default_key = default_key.into();
57        let mut searcher = ':'.into_searcher(&value);
58        match searcher.next_match() {
59            Some(_) => match searcher.next_match() {
60                Some(_) => Err(IdentifierError::FulledContainsMoreThanOneDoubleDot),
61                None => Ok(Self::new(IdentifierInner::Fulled(value))),
62            },
63            None => match default_key.contains(':') {
64                true => Err(IdentifierError::KeyContainsDoubleDot),
65                false => Ok(Self::new(IdentifierInner::Partial(default_key, value)))
66            }
67        }
68    }
69
70    pub fn new_fulled(full: impl Into<Cow<'a, str>>) -> Result<Self, IdentifierError> {
71        let full = full.into();
72        let mut searcher = ':'.into_searcher(&full);
73        match searcher.next_match() {
74            Some(_) => match searcher.next_match() {
75                Some(_) => Err(IdentifierError::FulledContainsMoreThanOneDoubleDot),
76                None => Ok(Self::new(IdentifierInner::Fulled(full))),
77            },
78            None => Err(IdentifierError::FulledContainsNoDoubleDot),
79        }
80    }
81
82    pub fn new_partial(key: impl Into<Cow<'a, str>>, value: impl Into<Cow<'a, str>>) -> Result<Self, IdentifierError> {
83        let key = key.into();
84        let value = value.into();
85        match key.contains(':') {
86            true => Err(IdentifierError::KeyContainsDoubleDot),
87            false => match value.contains(':') {
88                true => Err(IdentifierError::ValueContainsDoubleDot),
89                false => Ok(Self::new(IdentifierInner::Partial(key, value)))
90            }
91        }
92    }
93
94    pub fn get_fulled(&'a self) -> Cow<'a, str> {
95        match self.get_inner() {
96            IdentifierInner::Fulled(fulled) => Cow::Borrowed(&fulled),
97            IdentifierInner::Partial(key, value) =>
98                Cow::Owned(format!("{}:{}", key, value))
99        }
100    }
101
102    pub fn get_partial(&'a self) -> (&'a str, &'a str) {
103        match self.get_inner() {
104            IdentifierInner::Fulled(fulled) => {
105                // Safety. guarantied by constructors
106                let index = unsafe { fulled.find(':').unwrap_unchecked() };
107                (&fulled[0..index], &fulled[index + 1..fulled.len()])
108            }
109            IdentifierInner::Partial(key, value) => (&key, &value)
110        }
111    }
112
113    pub fn into_fulled(self) -> Cow<'a, str> {
114        match self.into_inner() {
115            IdentifierInner::Fulled(fulled) => fulled,
116            IdentifierInner::Partial(key, value) =>
117                Cow::Owned(format!("{}:{}", key, value))
118        }
119    }
120
121    pub fn into_partial(self) -> (Cow<'a, str>, Cow<'a, str>) {
122        match self.into_inner() {
123            IdentifierInner::Fulled(fulled) => {
124                // Safety. guarantied by constructors
125                let index = unsafe { fulled.find(':').unwrap_unchecked() };
126                (
127                    Cow::Owned(fulled[0..index].to_owned()),
128                    Cow::Owned(fulled[index + 1..fulled.len()].to_owned())
129                )
130            }
131            IdentifierInner::Partial(key, value) => (key, value)
132        }
133    }
134
135    pub const fn is_fulled(&self) -> bool {
136        match self.get_inner() {
137            IdentifierInner::Fulled(_) => true,
138            IdentifierInner::Partial(..) => false,
139        }
140    }
141
142    pub const fn is_partial(&self) -> bool {
143        !self.is_fulled()
144    }
145}
146
147impl Display for IdentifierInner<'_> {
148    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
149        match self {
150            Self::Fulled(fulled) => write!(f, "{}", fulled),
151            Self::Partial(key, value) => write!(f, "{}:{}", key, value)
152        }
153    }
154}
155
156impl Display for Identifier<'_> {
157    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
158        write!(f, "{}", self.get_inner())
159    }
160}
161
162impl PartialEq for Identifier<'_> {
163    fn eq(&self, other: &Self) -> bool {
164        self.get_partial() == other.get_partial()
165    }
166
167    fn ne(&self, other: &Self) -> bool {
168        !self.eq(other)
169    }
170}
171
172impl TryFrom<String> for Identifier<'_> {
173    type Error = IdentifierError;
174
175    fn try_from(value: String) -> Result<Self, Self::Error> {
176        Self::new_fulled(value)
177    }
178}
179
180impl From<Identifier<'_>> for String {
181    fn from(identifier: Identifier<'_>) -> Self {
182        identifier.to_string()
183    }
184}
185
186#[cfg(test)]
187mod tests {
188    use super::*;
189
190    #[test]
191    pub fn identifier_constructor() {
192        {
193            let good_identifier = Identifier::new_fulled("minecraft:grass_block").unwrap();
194            assert_eq!(good_identifier.to_string(), "minecraft:grass_block");
195            assert_eq!(good_identifier.get_partial(), ("minecraft", "grass_block"));
196            assert_eq!(good_identifier.get_fulled(), Cow::Borrowed("minecraft:grass_block"));
197            assert_eq!(
198                good_identifier,
199                Identifier::from_inner(IdentifierInner::Fulled(Cow::Borrowed("minecraft:grass_block"))).unwrap()
200            );
201        }
202        {
203            assert_eq!(
204                Identifier::new_partial("minecraft", "grass_block"),
205                Identifier::new_fulled("minecraft:grass_block")
206            );
207        }
208        {
209            assert_eq!(
210                Identifier::new_fulled("minecraft:grass_block:"),
211                Err(IdentifierError::FulledContainsMoreThanOneDoubleDot)
212            );
213            assert_eq!(
214                Identifier::new_fulled("minecraft_grass_block"),
215                Err(IdentifierError::FulledContainsNoDoubleDot)
216            );
217            assert_eq!(
218                Identifier::new_partial("minecraft", "grass_block:"),
219                Err(IdentifierError::ValueContainsDoubleDot)
220            );
221            assert_eq!(
222                Identifier::new_partial("minecraft:", "grass_block"),
223                Err(IdentifierError::KeyContainsDoubleDot)
224            );
225        }
226        {
227            assert_eq!(
228                Identifier::new_with_default("grass_block", "minecraft"),
229                Identifier::new_fulled("minecraft:grass_block")
230            );
231            assert_eq!(
232                Identifier::new_with_default("other:grass_block", "minecraft"),
233                Identifier::new_fulled("other:grass_block")
234            );
235        }
236    }
237
238    #[test]
239    fn into() {
240        {
241            let identifier = Identifier::new_fulled("minecraft:grass_block").unwrap();
242            assert_eq!(identifier.get_fulled(), Cow::Borrowed("minecraft:grass_block"));
243            assert_eq!(identifier.get_partial(), ("minecraft", "grass_block"));
244        }
245        {
246            let identifier = Identifier::new_partial("minecraft", "grass_block").unwrap();
247            assert_eq!(identifier.get_fulled(), Cow::Owned::<str>("minecraft:grass_block".to_string()));
248            assert_eq!(identifier.get_partial(), ("minecraft", "grass_block"));
249        }
250    }
251}