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