azalea_registry/
identifier.rs1use std::{
4 fmt::{self, Debug, Display},
5 hash::{Hash, Hasher},
6 io::{self, Cursor, Write},
7 num::NonZeroUsize,
8 str::FromStr,
9};
10
11use azalea_buf::{AzBuf, BufReadError};
12#[cfg(feature = "serde")]
13use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
14use simdnbt::{FromNbtTag, ToNbtTag, owned::NbtTag};
15
16#[doc(alias = "ResourceLocation")]
22#[derive(Clone, Default, Eq)]
23pub struct Identifier {
24 colon_index: Option<NonZeroUsize>,
26 inner: Box<str>,
27}
28
29const _: () = assert!(size_of::<Identifier>() == 24);
30
31static DEFAULT_NAMESPACE: &str = "minecraft";
32impl Identifier {
35 pub fn new(resource_string: impl Into<String>) -> Identifier {
36 let mut resource_string = resource_string.into();
37
38 let colon_index = resource_string.find(':');
39 let colon_index = if let Some(colon_index) = colon_index {
40 if colon_index == 0 {
41 resource_string = resource_string.split_off(1);
42 }
43 NonZeroUsize::new(colon_index)
44 } else {
45 None
46 };
47
48 Self {
49 colon_index,
50 inner: resource_string.into(),
51 }
52 }
53
54 pub fn namespace(&self) -> &str {
55 if let Some(colon_index) = self.colon_index {
56 &self.inner[0..colon_index.get()]
57 } else {
58 DEFAULT_NAMESPACE
59 }
60 }
61 pub fn path(&self) -> &str {
62 if let Some(colon_index) = self.colon_index {
63 &self.inner[(colon_index.get() + 1)..]
64 } else {
65 &self.inner
66 }
67 }
68}
69impl PartialEq for Identifier {
70 fn eq(&self, other: &Self) -> bool {
71 self.namespace() == other.namespace() && self.path() == other.path()
72 }
73}
74impl Hash for Identifier {
75 fn hash<H: Hasher>(&self, state: &mut H) {
76 let namespace = self.namespace();
77 if namespace != DEFAULT_NAMESPACE {
78 namespace.hash(state);
79 }
80 self.path().hash(state);
81 }
82}
83
84impl Display for Identifier {
85 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
86 if self.colon_index.is_some() {
87 write!(f, "{}", self.inner)
88 } else {
89 write!(f, "{DEFAULT_NAMESPACE}:{}", self.inner)
90 }
91 }
92}
93impl Debug for Identifier {
94 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
95 write!(f, "{self}")
96 }
97}
98impl FromStr for Identifier {
99 type Err = &'static str;
100
101 fn from_str(s: &str) -> Result<Self, Self::Err> {
102 Ok(Identifier::new(s))
103 }
104}
105impl From<&str> for Identifier {
106 fn from(s: &str) -> Self {
107 Identifier::new(s)
108 }
109}
110
111impl AzBuf for Identifier {
112 fn azalea_read(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> {
113 let location_string = String::azalea_read(buf)?;
114 Ok(Identifier::new(&location_string))
115 }
116 fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
117 self.to_string().azalea_write(buf)
118 }
119}
120
121#[cfg(feature = "serde")]
122impl Serialize for Identifier {
123 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
124 where
125 S: Serializer,
126 {
127 serializer.serialize_str(&self.to_string())
128 }
129}
130
131#[cfg(feature = "serde")]
132impl<'de> Deserialize<'de> for Identifier {
133 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
134 where
135 D: Deserializer<'de>,
136 {
137 let s = String::deserialize(deserializer)?;
138 if s.contains(':') {
139 Ok(Identifier::new(&s))
140 } else {
141 Err(de::Error::invalid_value(
142 de::Unexpected::Str(&s),
143 &"a valid Identifier",
144 ))
145 }
146 }
147}
148
149impl FromNbtTag for Identifier {
150 fn from_nbt_tag(tag: simdnbt::borrow::NbtTag) -> Option<Self> {
151 tag.string().and_then(|s| s.to_str().parse().ok())
152 }
153}
154
155impl ToNbtTag for Identifier {
156 fn to_nbt_tag(self) -> NbtTag {
157 NbtTag::String(self.to_string().into())
158 }
159}
160
161#[cfg(test)]
162mod tests {
163 use super::*;
164
165 #[test]
166 fn basic_identifier() {
167 let r = Identifier::new("abcdef:ghijkl");
168 assert_eq!(r.namespace(), "abcdef");
169 assert_eq!(r.path(), "ghijkl");
170 }
171 #[test]
172 fn no_namespace() {
173 let r = Identifier::new("azalea");
174 assert_eq!(r.namespace(), "minecraft");
175 assert_eq!(r.path(), "azalea");
176 }
177 #[test]
178 fn colon_start() {
179 let r = Identifier::new(":azalea");
180 assert_eq!(r.namespace(), "minecraft");
181 assert_eq!(r.path(), "azalea");
182 }
183 #[test]
184 fn colon_end() {
185 let r = Identifier::new("azalea:");
186 assert_eq!(r.namespace(), "azalea");
187 assert_eq!(r.path(), "");
188 }
189
190 #[test]
191 fn azbuf_identifier() {
192 let mut buf = Vec::new();
193 Identifier::new("minecraft:dirt")
194 .azalea_write(&mut buf)
195 .unwrap();
196
197 let mut buf = Cursor::new(&buf[..]);
198
199 assert_eq!(
200 Identifier::azalea_read(&mut buf).unwrap(),
201 Identifier::new("minecraft:dirt")
202 );
203 }
204}