1use std::error::Error as StdError;
2use std::fmt;
3
4#[derive(Debug, Clone, PartialEq)]
6pub enum BlockpediaError {
7 Block(BlockError),
9 Property(PropertyError),
11 State(StateError),
13 Query(QueryError),
15 Fetcher(FetcherError),
17 Validation(ValidationError),
19 Data(DataError),
21}
22
23#[derive(Debug, Clone, PartialEq)]
24pub enum BlockError {
25 NotFound(String),
27 InvalidId(String),
29 CorruptedData(String),
31}
32
33#[derive(Debug, Clone, PartialEq)]
34pub enum PropertyError {
35 NotFound { block_id: String, property: String },
37 InvalidValue {
39 block_id: String,
40 property: String,
41 value: String,
42 valid_values: Vec<String>,
43 },
44 InvalidName(String),
46 NoValues(String),
48}
49
50#[derive(Debug, Clone, PartialEq)]
51pub enum StateError {
52 ParseFailed { input: String, reason: String },
54 ValidationFailed { state: String, errors: Vec<String> },
56 ImmutableState(String),
58 ConflictingProperties {
60 prop1: String,
61 prop2: String,
62 reason: String,
63 },
64}
65
66#[derive(Debug, Clone, PartialEq)]
67pub enum QueryError {
68 InvalidSyntax(String),
70 InvalidParameters(String),
72 ExecutionFailed(String),
74 Timeout(String),
76 NoResults(String),
78}
79
80#[derive(Debug, Clone, PartialEq)]
81pub enum FetcherError {
82 InitializationFailed(String),
84 DataSourceUnavailable(String),
86 InvalidData(String),
88 ConflictingData {
90 fetcher1: String,
91 fetcher2: String,
92 block_id: String,
93 },
94}
95
96#[derive(Debug, Clone, PartialEq)]
97pub enum ValidationError {
98 InvalidFormat {
100 input: String,
101 expected_format: String,
102 },
103 OutOfRange {
105 value: String,
106 min: String,
107 max: String,
108 },
109 MissingRequired(String),
111 InvalidCharacters {
113 input: String,
114 invalid_chars: Vec<char>,
115 },
116 InvalidLength {
118 input: String,
119 min_length: usize,
120 max_length: usize,
121 },
122}
123
124#[derive(Debug, Clone, PartialEq)]
125pub enum DataError {
126 JsonParse(String),
128 NetworkFailed(String),
130 IoFailed(String),
132 UnsupportedFormat(String),
134 IntegrityCheckFailed(String),
136}
137
138pub type Result<T> = std::result::Result<T, BlockpediaError>;
140
141impl fmt::Display for BlockpediaError {
142 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
143 match self {
144 BlockpediaError::Block(e) => write!(f, "Block error: {}", e),
145 BlockpediaError::Property(e) => write!(f, "Property error: {}", e),
146 BlockpediaError::State(e) => write!(f, "State error: {}", e),
147 BlockpediaError::Query(e) => write!(f, "Query error: {}", e),
148 BlockpediaError::Fetcher(e) => write!(f, "Fetcher error: {}", e),
149 BlockpediaError::Validation(e) => write!(f, "Validation error: {}", e),
150 BlockpediaError::Data(e) => write!(f, "Data error: {}", e),
151 }
152 }
153}
154
155impl fmt::Display for BlockError {
156 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
157 match self {
158 BlockError::NotFound(id) => write!(f, "Block '{}' not found", id),
159 BlockError::InvalidId(id) => write!(f, "Invalid block ID format: '{}'", id),
160 BlockError::CorruptedData(msg) => write!(f, "Block data corrupted: {}", msg),
161 }
162 }
163}
164
165impl fmt::Display for PropertyError {
166 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
167 match self {
168 PropertyError::NotFound { block_id, property } => {
169 write!(
170 f,
171 "Property '{}' not found on block '{}'",
172 property, block_id
173 )
174 }
175 PropertyError::InvalidValue {
176 block_id,
177 property,
178 value,
179 valid_values,
180 } => {
181 write!(
182 f,
183 "Invalid value '{}' for property '{}' on block '{}'. Valid values: {:?}",
184 value, property, block_id, valid_values
185 )
186 }
187 PropertyError::InvalidName(name) => {
188 write!(f, "Invalid property name format: '{}'", name)
189 }
190 PropertyError::NoValues(property) => {
191 write!(f, "Property '{}' has no valid values defined", property)
192 }
193 }
194 }
195}
196
197impl fmt::Display for StateError {
198 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
199 match self {
200 StateError::ParseFailed { input, reason } => {
201 write!(f, "Failed to parse BlockState '{}': {}", input, reason)
202 }
203 StateError::ValidationFailed { state, errors } => {
204 write!(
205 f,
206 "BlockState '{}' validation failed: {}",
207 state,
208 errors.join(", ")
209 )
210 }
211 StateError::ImmutableState(msg) => {
212 write!(f, "Cannot modify immutable state: {}", msg)
213 }
214 StateError::ConflictingProperties {
215 prop1,
216 prop2,
217 reason,
218 } => {
219 write!(
220 f,
221 "Conflicting properties '{}' and '{}': {}",
222 prop1, prop2, reason
223 )
224 }
225 }
226 }
227}
228
229impl fmt::Display for QueryError {
230 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
231 match self {
232 QueryError::InvalidSyntax(syntax) => write!(f, "Invalid query syntax: {}", syntax),
233 QueryError::InvalidParameters(params) => {
234 write!(f, "Invalid query parameters: {}", params)
235 }
236 QueryError::ExecutionFailed(reason) => write!(f, "Query execution failed: {}", reason),
237 QueryError::Timeout(query) => write!(f, "Query timed out: {}", query),
238 QueryError::NoResults(query) => write!(f, "No results found for query: {}", query),
239 }
240 }
241}
242
243impl fmt::Display for FetcherError {
244 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
245 match self {
246 FetcherError::InitializationFailed(msg) => {
247 write!(f, "Fetcher initialization failed: {}", msg)
248 }
249 FetcherError::DataSourceUnavailable(source) => {
250 write!(f, "Data source unavailable: {}", source)
251 }
252 FetcherError::InvalidData(msg) => write!(f, "Invalid fetcher data: {}", msg),
253 FetcherError::ConflictingData {
254 fetcher1,
255 fetcher2,
256 block_id,
257 } => {
258 write!(
259 f,
260 "Conflicting data from fetchers '{}' and '{}' for block '{}'",
261 fetcher1, fetcher2, block_id
262 )
263 }
264 }
265 }
266}
267
268impl fmt::Display for ValidationError {
269 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
270 match self {
271 ValidationError::InvalidFormat {
272 input,
273 expected_format,
274 } => {
275 write!(
276 f,
277 "Invalid format for '{}', expected: {}",
278 input, expected_format
279 )
280 }
281 ValidationError::OutOfRange { value, min, max } => {
282 write!(f, "Value '{}' out of range [{}, {}]", value, min, max)
283 }
284 ValidationError::MissingRequired(field) => {
285 write!(f, "Required field missing: {}", field)
286 }
287 ValidationError::InvalidCharacters {
288 input,
289 invalid_chars,
290 } => {
291 write!(f, "Invalid characters in '{}': {:?}", input, invalid_chars)
292 }
293 ValidationError::InvalidLength {
294 input,
295 min_length,
296 max_length,
297 } => {
298 write!(
299 f,
300 "Invalid length for '{}', must be between {} and {} characters",
301 input, min_length, max_length
302 )
303 }
304 }
305 }
306}
307
308impl fmt::Display for DataError {
309 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
310 match self {
311 DataError::JsonParse(msg) => write!(f, "JSON parsing failed: {}", msg),
312 DataError::NetworkFailed(msg) => write!(f, "Network request failed: {}", msg),
313 DataError::IoFailed(msg) => write!(f, "I/O operation failed: {}", msg),
314 DataError::UnsupportedFormat(format) => {
315 write!(f, "Unsupported data format: {}", format)
316 }
317 DataError::IntegrityCheckFailed(msg) => {
318 write!(f, "Data integrity check failed: {}", msg)
319 }
320 }
321 }
322}
323
324impl StdError for BlockpediaError {}
325impl StdError for BlockError {}
326impl StdError for PropertyError {}
327impl StdError for StateError {}
328impl StdError for QueryError {}
329impl StdError for FetcherError {}
330impl StdError for ValidationError {}
331impl StdError for DataError {}
332
333impl BlockpediaError {
335 pub fn block_not_found(id: &str) -> Self {
336 BlockpediaError::Block(BlockError::NotFound(id.to_string()))
337 }
338
339 pub fn invalid_block_id(id: &str) -> Self {
340 BlockpediaError::Block(BlockError::InvalidId(id.to_string()))
341 }
342
343 pub fn property_not_found(block_id: &str, property: &str) -> Self {
344 BlockpediaError::Property(PropertyError::NotFound {
345 block_id: block_id.to_string(),
346 property: property.to_string(),
347 })
348 }
349
350 pub fn invalid_property_value(
351 block_id: &str,
352 property: &str,
353 value: &str,
354 valid_values: Vec<String>,
355 ) -> Self {
356 BlockpediaError::Property(PropertyError::InvalidValue {
357 block_id: block_id.to_string(),
358 property: property.to_string(),
359 value: value.to_string(),
360 valid_values,
361 })
362 }
363
364 pub fn parse_failed(input: &str, reason: &str) -> Self {
365 BlockpediaError::State(StateError::ParseFailed {
366 input: input.to_string(),
367 reason: reason.to_string(),
368 })
369 }
370
371 pub fn invalid_format(input: &str, expected: &str) -> Self {
372 BlockpediaError::Validation(ValidationError::InvalidFormat {
373 input: input.to_string(),
374 expected_format: expected.to_string(),
375 })
376 }
377
378 pub fn custom(message: String) -> Self {
379 BlockpediaError::Data(DataError::JsonParse(message))
380 }
381}
382
383pub mod recovery {
385
386 pub fn suggest_similar_blocks(block_id: &str) -> Vec<String> {
388 let mut suggestions = Vec::new();
391
392 if block_id.starts_with("minecraft:") {
393 if let Some(name) = block_id.strip_prefix("minecraft:") {
395 if !name.is_empty() {
396 suggestions.push(format!("Did you mean '{}'?", name));
397 }
398 }
399 } else {
400 suggestions.push(format!("minecraft:{}", block_id));
402 }
403
404 suggestions
405 }
406
407 pub fn suggest_property_values(
409 _property: &str,
410 invalid_value: &str,
411 valid_values: &[String],
412 ) -> Vec<String> {
413 let mut suggestions = Vec::new();
414
415 for valid in valid_values {
417 if valid.to_lowercase().contains(&invalid_value.to_lowercase())
418 || invalid_value.to_lowercase().contains(&valid.to_lowercase())
419 {
420 suggestions.push(valid.clone());
421 }
422 }
423
424 if suggestions.is_empty() && !valid_values.is_empty() {
426 suggestions.extend(valid_values.iter().take(3).cloned());
427 }
428
429 suggestions
430 }
431
432 pub fn fix_common_parse_errors(input: &str) -> String {
434 let mut fixed = input.to_string();
435
436 if input.contains('=') && !input.contains('[') && !input.contains(']') {
438 if let Some(colon_pos) = input.find(':') {
439 if let Some(equals_pos) = input.find('=') {
440 if equals_pos > colon_pos {
441 let (block_part, _props_part) = input.split_at(equals_pos);
442 if let Some(space_pos) = block_part.rfind(' ') {
444 let (prefix, block_id) = block_part.split_at(space_pos + 1);
445 let properties = &input[equals_pos..];
446 fixed = format!(
447 "{}{}[{}{}]",
448 prefix,
449 block_id,
450 properties.chars().next().unwrap_or('='),
451 &properties[1..]
452 );
453 }
454 }
455 }
456 }
457 }
458
459 fixed = fixed.replace("::", ":");
461
462 fixed = fixed.replace(" = ", "=");
464
465 fixed
466 }
467}
468
469pub mod validation {
471 use super::*;
472
473 pub fn validate_block_id(id: &str) -> Result<()> {
475 if id.is_empty() {
476 return Err(BlockpediaError::invalid_format(id, "non-empty string"));
477 }
478
479 if id.len() > 256 {
480 return Err(BlockpediaError::Validation(
481 ValidationError::InvalidLength {
482 input: id.to_string(),
483 min_length: 1,
484 max_length: 256,
485 },
486 ));
487 }
488
489 if let Some(colon_pos) = id.find(':') {
491 let namespace = &id[..colon_pos];
492 let name = &id[colon_pos + 1..];
493
494 if namespace.is_empty() || name.is_empty() {
495 return Err(BlockpediaError::invalid_format(id, "namespace:name"));
496 }
497
498 if !namespace
500 .chars()
501 .all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-')
502 {
503 return Err(BlockpediaError::Validation(
504 ValidationError::InvalidCharacters {
505 input: namespace.to_string(),
506 invalid_chars: namespace
507 .chars()
508 .filter(|c| !c.is_ascii_alphanumeric() && *c != '_' && *c != '-')
509 .collect(),
510 },
511 ));
512 }
513
514 if !name
516 .chars()
517 .all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-')
518 {
519 return Err(BlockpediaError::Validation(
520 ValidationError::InvalidCharacters {
521 input: name.to_string(),
522 invalid_chars: name
523 .chars()
524 .filter(|c| !c.is_ascii_alphanumeric() && *c != '_' && *c != '-')
525 .collect(),
526 },
527 ));
528 }
529 } else {
530 if !id
532 .chars()
533 .all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-')
534 {
535 return Err(BlockpediaError::Validation(
536 ValidationError::InvalidCharacters {
537 input: id.to_string(),
538 invalid_chars: id
539 .chars()
540 .filter(|c| !c.is_ascii_alphanumeric() && *c != '_' && *c != '-')
541 .collect(),
542 },
543 ));
544 }
545 }
546
547 Ok(())
548 }
549
550 pub fn validate_property_name(name: &str) -> Result<()> {
552 if name.is_empty() {
553 return Err(BlockpediaError::Validation(
554 ValidationError::MissingRequired("property name".to_string()),
555 ));
556 }
557
558 if name.len() > 64 {
559 return Err(BlockpediaError::Validation(
560 ValidationError::InvalidLength {
561 input: name.to_string(),
562 min_length: 1,
563 max_length: 64,
564 },
565 ));
566 }
567
568 if !name.chars().all(|c| c.is_ascii_alphanumeric() || c == '_') {
569 return Err(BlockpediaError::Validation(
570 ValidationError::InvalidCharacters {
571 input: name.to_string(),
572 invalid_chars: name
573 .chars()
574 .filter(|c| !c.is_ascii_alphanumeric() && *c != '_')
575 .collect(),
576 },
577 ));
578 }
579
580 Ok(())
581 }
582
583 pub fn validate_property_value(value: &str) -> Result<()> {
585 if value.is_empty() {
586 return Err(BlockpediaError::Validation(
587 ValidationError::MissingRequired("property value".to_string()),
588 ));
589 }
590
591 if value.len() > 32 {
592 return Err(BlockpediaError::Validation(
593 ValidationError::InvalidLength {
594 input: value.to_string(),
595 min_length: 1,
596 max_length: 32,
597 },
598 ));
599 }
600
601 if !value
603 .chars()
604 .all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-' || c == '.')
605 {
606 return Err(BlockpediaError::Validation(
607 ValidationError::InvalidCharacters {
608 input: value.to_string(),
609 invalid_chars: value
610 .chars()
611 .filter(|c| {
612 !c.is_ascii_alphanumeric() && *c != '_' && *c != '-' && *c != '.'
613 })
614 .collect(),
615 },
616 ));
617 }
618
619 Ok(())
620 }
621}