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 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 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 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}