steel_utils/types/
identifier.rs1use std::{
2 borrow::Cow,
3 fmt::{self, Debug, Display, Formatter},
4 mem::MaybeUninit,
5 str::FromStr,
6};
7
8use serde::{Deserialize, Serialize, de::Error as _};
9use simdnbt::owned::NbtTag;
10use wincode::{SchemaRead, SchemaWrite, config::Config, io::Reader, io::Writer};
11
12use crate::hash::{ComponentHasher, HashComponent};
13
14#[derive(Clone, PartialEq, Eq, Hash, Default)]
16pub struct Identifier {
17 pub namespace: Cow<'static, str>,
19 pub path: Cow<'static, str>,
21}
22
23impl Debug for Identifier {
24 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
25 f.write_str(&format!("{}:{}", self.namespace, self.path))
26 }
27}
28
29impl Identifier {
30 pub const VANILLA_NAMESPACE: &'static str = "minecraft";
32 pub const STEEL_NAMESPACE: &'static str = "steel";
34
35 #[must_use]
37 pub fn new(
38 namespace: impl Into<Cow<'static, str>>,
39 path: impl Into<Cow<'static, str>>,
40 ) -> Self {
41 Identifier {
42 namespace: namespace.into(),
43 path: path.into(),
44 }
45 }
46 #[must_use]
47 pub const fn new_static(namespace: &'static str, path: &'static str) -> Self {
48 Identifier {
49 namespace: Cow::Borrowed(namespace),
50 path: Cow::Borrowed(path),
51 }
52 }
53
54 #[must_use]
56 pub fn from_steel(path: impl Into<Cow<'static, str>>) -> Self {
57 Self::new(Self::STEEL_NAMESPACE, path)
58 }
59
60 #[must_use]
62 pub const fn vanilla(path: String) -> Self {
63 Identifier {
64 namespace: Cow::Borrowed(Self::VANILLA_NAMESPACE),
65 path: Cow::Owned(path),
66 }
67 }
68
69 #[must_use]
71 pub const fn vanilla_static(path: &'static str) -> Self {
72 Identifier {
73 namespace: Cow::Borrowed(Self::VANILLA_NAMESPACE),
74 path: Cow::Borrowed(path),
75 }
76 }
77
78 #[must_use]
80 pub const fn valid_namespace_char(char: char) -> bool {
81 char == '_'
82 || char == '-'
83 || char.is_ascii_lowercase()
84 || char.is_ascii_digit()
85 || char == '.'
86 }
87
88 #[must_use]
90 pub const fn valid_char(char: char) -> bool {
91 Self::valid_namespace_char(char) || char == '/'
92 }
93
94 pub fn validate_namespace(namespace: &str) -> bool {
96 namespace != ".." && namespace.chars().all(Self::valid_namespace_char)
97 }
98
99 pub fn validate_path(path: &str) -> bool {
101 path.chars().all(Self::valid_char)
102 }
103
104 #[must_use]
106 pub fn validate(namespace: &str, path: &str) -> bool {
107 Self::validate_namespace(namespace) && Self::validate_path(path)
108 }
109}
110
111impl Display for Identifier {
112 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
113 write!(f, "{}:{}", self.namespace, self.path)
114 }
115}
116
117impl FromStr for Identifier {
118 type Err = &'static str;
119
120 fn from_str(s: &str) -> Result<Self, Self::Err> {
121 let (namespace, path) = match s.split_once(':') {
122 Some(("", path)) => (Self::VANILLA_NAMESPACE, path),
123 Some((namespace, path)) => (namespace, path),
124 None => (Self::VANILLA_NAMESPACE, s),
125 };
126
127 if !Identifier::validate_namespace(namespace) {
128 return Err("Invalid namespace");
129 }
130
131 if !Identifier::validate_path(path) {
132 return Err("Invalid path");
133 }
134
135 Ok(Identifier {
136 namespace: Cow::Owned(namespace.to_owned()),
137 path: Cow::Owned(path.to_owned()),
138 })
139 }
140}
141impl Serialize for Identifier {
142 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
143 where
144 S: serde::Serializer,
145 {
146 serializer.serialize_str(&self.to_string())
147 }
148}
149
150impl<'de> Deserialize<'de> for Identifier {
151 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
152 where
153 D: serde::Deserializer<'de>,
154 {
155 let s = String::deserialize(deserializer)?;
156 Identifier::from_str(&s).map_err(D::Error::custom)
157 }
158}
159
160unsafe impl<C: Config> SchemaWrite<C> for Identifier {
165 type Src = Identifier;
166
167 fn size_of(src: &Self::Src) -> wincode::WriteResult<usize> {
168 <str as SchemaWrite<C>>::size_of(&src.to_string())
169 }
170
171 fn write(writer: impl Writer, src: &Self::Src) -> wincode::WriteResult<()> {
172 <str as SchemaWrite<C>>::write(writer, &src.to_string())
173 }
174}
175
176unsafe impl<'de, C: Config> SchemaRead<'de, C> for Identifier {
180 type Dst = Identifier;
181
182 fn read(reader: impl Reader<'de>, dst: &mut MaybeUninit<Self::Dst>) -> wincode::ReadResult<()> {
183 let mut s = MaybeUninit::<String>::uninit();
184 <String as SchemaRead<'de, C>>::read(reader, &mut s)?;
185
186 let s = unsafe { s.assume_init() };
188
189 dst.write(Identifier::from_str(&s).map_err(wincode::ReadError::Custom)?);
190 Ok(())
191 }
192}
193
194impl HashComponent for Identifier {
195 fn hash_component(&self, hasher: &mut ComponentHasher) {
196 hasher.put_string(&self.to_string());
198 }
199}
200
201impl simdnbt::ToNbtTag for Identifier {
202 fn to_nbt_tag(self) -> NbtTag {
203 NbtTag::String(self.to_string().into())
204 }
205}
206
207impl simdnbt::FromNbtTag for Identifier {
208 fn from_nbt_tag(tag: simdnbt::borrow::NbtTag) -> Option<Self> {
209 let s = tag.string()?.to_str();
210 s.parse().ok()
211 }
212}