azalea_core/
identifier.rs

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