1use core::str;
11use std::{
12 error::Error,
13 ffi::{CStr, CString},
14 fmt::Display,
15 str::FromStr,
16};
17
18use num_traits::{FromPrimitive, ToPrimitive};
19
20use crate::{biome2str, mc2str, str2mc, struct2str};
21
22#[derive(Debug, Clone, PartialEq, Eq)]
23pub enum ParseError {
24 NonAsciiStr,
25 NotMCVersion,
26 NotDimension,
27 InvalidCString(std::ffi::NulError),
28}
29
30impl Display for ParseError {
31 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
32 match self {
33 ParseError::NonAsciiStr => write!(f, "Value is not an ascii sequence"),
34 ParseError::NotMCVersion => write!(f, "Value does not represent a minecraft version"),
35 ParseError::NotDimension => {
36 write!(f, "Value does not represent a valid minecraft dimension")
37 }
38 ParseError::InvalidCString(_) => {
39 write!(f, "Could not format self as a c string")
40 }
41 }
42 }
43}
44
45impl Error for ParseError {
46 fn source(&self) -> Option<&(dyn Error + 'static)> {
47 if let Self::InvalidCString(e) = self {
48 Some(e)
49 } else {
50 None
51 }
52 }
53
54 fn cause(&self) -> Option<&dyn Error> {
55 self.source()
56 }
57}
58
59impl From<std::ffi::NulError> for ParseError {
60 fn from(value: std::ffi::NulError) -> Self {
61 Self::InvalidCString(value)
62 }
63}
64
65include!(concat!(env!("OUT_DIR"), "/biome_enums.rs"));
66
67impl BiomeID {
68 pub fn to_mc_biome_str(&self, version: MCVersion) -> &'static str {
74 let chars = unsafe { biome2str(version as i32, self.to_i32().unwrap()) };
75
76 assert!(!chars.is_null());
79
80 let slice = unsafe { CStr::from_ptr(chars) };
81
82 match slice.to_str() {
83 Ok(str) => str,
84 Err(_) => panic!(),
85 }
86 }
87}
88
89impl Display for StructureType {
91 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
92 let chars = unsafe { struct2str(*self as i32) };
93
94 assert!(!chars.is_null());
95 let slice = unsafe { CStr::from_ptr(chars) };
96
97 write!(f, "{}", slice.to_str().unwrap())
98 }
99}
100
101impl Display for MCVersion {
102 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
103 let chars = unsafe { mc2str(*self as i32) };
104
105 assert!(!chars.is_null());
106 let slice = unsafe { CStr::from_ptr(chars) };
107
108 write!(f, "{}", slice.to_str().unwrap())
109 }
110}
111
112impl FromStr for MCVersion {
113 type Err = ParseError;
114
115 fn from_str(s: &str) -> Result<Self, Self::Err> {
116 if !s.is_ascii() {
117 return Err(ParseError::NonAsciiStr);
118 }
119
120 let s: CString = CString::from_str(s)?;
121
122 let version = unsafe { str2mc(s.as_ptr()) };
123
124 if version == 0 {
125 Err(ParseError::NotMCVersion)
126 } else {
127 Ok(MCVersion::from_i32(version).unwrap())
128 }
129 }
130}
131
132impl Display for Dimension {
133 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
134 match self {
135 Dimension::DIM_NETHER => write!(f, "the_nether"),
136 Dimension::DIM_OVERWORLD => write!(f, "overworld"),
137 Dimension::DIM_END => write!(f, "the_end"),
138 Dimension::DIM_UNDEF => write!(f, "undefined"),
140 }
141 }
142}
143
144impl FromStr for Dimension {
145 type Err = ParseError;
146
147 fn from_str(s: &str) -> Result<Self, Self::Err> {
148 match s {
149 "the_nether" => Ok(Self::DIM_NETHER),
150 "overworld" => Ok(Self::DIM_OVERWORLD),
151 "the_end" => Ok(Self::DIM_END),
152 "undefined" => Ok(Self::DIM_UNDEF),
153 _ => Err(ParseError::NotDimension),
154 }
155 }
156}
157
158#[cfg(test)]
159mod test {
160 use crate::enums::{BiomeID, Dimension, MCVersion, StructureType};
161
162 #[test]
163 fn test_biome_conversion() {
164 assert_eq!(
165 BiomeID::badlands.to_mc_biome_str(crate::enums::MCVersion::MC_1_6_4),
166 "badlands"
167 );
168
169 assert_eq!(
170 BiomeID::stony_shore.to_mc_biome_str(crate::enums::MCVersion::MC_1_18),
171 "stony_shore"
172 );
173
174 assert_eq!(
175 BiomeID::stone_shore.to_mc_biome_str(crate::enums::MCVersion::MC_1_6_4),
176 "stone_shore"
177 );
178 }
179
180 #[test]
181 fn test_structure_conversion() {
182 assert_eq!(StructureType::Bastion.to_string(), "bastion_remnant");
183 assert_eq!(StructureType::Shipwreck.to_string(), "shipwreck");
184 assert_eq!(StructureType::End_City.to_string(), "end_city");
185 }
186
187 #[test]
188 fn test_mc_version_conversion() {
189 assert_eq!(MCVersion::MC_1_15.to_string(), "1.15");
190 }
191
192 #[test]
193 fn test_mc_version_parsing() {
194 assert_eq!(
195 MCVersion::MC_B1_7,
196 "Beta 1.7".parse().expect("parsing erro")
197 );
198 assert_eq!(
199 MCVersion::MC_1_15_2,
200 "1.15.2".parse().expect("parsing erro")
201 );
202 assert_eq!(
203 MCVersion::MC_1_10_2,
204 "1.10.2".parse().expect("parsing erro")
205 );
206 }
207
208 #[test]
209 fn test_dimension_parsing() {
210 assert_eq!(
211 Dimension::DIM_END,
212 Dimension::DIM_END
213 .to_string()
214 .parse()
215 .expect("Failed to parse"),
216 "the_end"
217 );
218 assert_eq!(
219 Dimension::DIM_OVERWORLD,
220 Dimension::DIM_OVERWORLD
221 .to_string()
222 .parse()
223 .expect("Failed to parse"),
224 "overworld"
225 );
226
227 assert_eq!(
228 Dimension::DIM_NETHER,
229 Dimension::DIM_NETHER
230 .to_string()
231 .parse()
232 .expect("Failed to parse"),
233 "the_nether"
234 );
235 assert_eq!(
236 Dimension::DIM_UNDEF,
237 Dimension::DIM_UNDEF
238 .to_string()
239 .parse()
240 .expect("Failed to parse"),
241 "undefined"
242 );
243 }
244}