Skip to main content

blockpedia/
errors.rs

1use std::error::Error as StdError;
2use std::fmt;
3
4/// Main error type for all Blockpedia operations
5#[derive(Debug, Clone, PartialEq)]
6pub enum BlockpediaError {
7    /// Block-related errors
8    Block(BlockError),
9    /// Property-related errors
10    Property(PropertyError),
11    /// BlockState parsing and validation errors
12    State(StateError),
13    /// Query execution errors
14    Query(QueryError),
15    /// Fetcher-related errors
16    Fetcher(FetcherError),
17    /// Data validation errors
18    Validation(ValidationError),
19    /// I/O and data loading errors
20    Data(DataError),
21}
22
23#[derive(Debug, Clone, PartialEq)]
24pub enum BlockError {
25    /// Block ID not found in the database
26    NotFound(String),
27    /// Block ID format is invalid
28    InvalidId(String),
29    /// Block data is corrupted or missing required fields
30    CorruptedData(String),
31}
32
33#[derive(Debug, Clone, PartialEq)]
34pub enum PropertyError {
35    /// Property doesn't exist for the specified block
36    NotFound { block_id: String, property: String },
37    /// Property value is not valid for this property
38    InvalidValue {
39        block_id: String,
40        property: String,
41        value: String,
42        valid_values: Vec<String>,
43    },
44    /// Property name format is invalid
45    InvalidName(String),
46    /// Property has no valid values defined
47    NoValues(String),
48}
49
50#[derive(Debug, Clone, PartialEq)]
51pub enum StateError {
52    /// BlockState string parsing failed
53    ParseFailed { input: String, reason: String },
54    /// BlockState validation failed
55    ValidationFailed { state: String, errors: Vec<String> },
56    /// Attempting to modify immutable state
57    ImmutableState(String),
58    /// State contains conflicting properties
59    ConflictingProperties {
60        prop1: String,
61        prop2: String,
62        reason: String,
63    },
64}
65
66#[derive(Debug, Clone, PartialEq)]
67pub enum QueryError {
68    /// Query syntax is invalid
69    InvalidSyntax(String),
70    /// Query parameters are out of range or invalid
71    InvalidParameters(String),
72    /// Query execution failed due to data issues
73    ExecutionFailed(String),
74    /// Query timed out (for future async queries)
75    Timeout(String),
76    /// No results found for query
77    NoResults(String),
78}
79
80#[derive(Debug, Clone, PartialEq)]
81pub enum FetcherError {
82    /// Fetcher initialization failed
83    InitializationFailed(String),
84    /// Fetcher data source is unavailable
85    DataSourceUnavailable(String),
86    /// Fetcher returned invalid data
87    InvalidData(String),
88    /// Multiple fetchers provide conflicting data
89    ConflictingData {
90        fetcher1: String,
91        fetcher2: String,
92        block_id: String,
93    },
94}
95
96#[derive(Debug, Clone, PartialEq)]
97pub enum ValidationError {
98    /// Input fails format validation
99    InvalidFormat {
100        input: String,
101        expected_format: String,
102    },
103    /// Input is out of acceptable range
104    OutOfRange {
105        value: String,
106        min: String,
107        max: String,
108    },
109    /// Required field is missing
110    MissingRequired(String),
111    /// Input contains invalid characters
112    InvalidCharacters {
113        input: String,
114        invalid_chars: Vec<char>,
115    },
116    /// Input is too long or too short
117    InvalidLength {
118        input: String,
119        min_length: usize,
120        max_length: usize,
121    },
122}
123
124#[derive(Debug, Clone, PartialEq)]
125pub enum DataError {
126    /// JSON parsing failed
127    JsonParse(String),
128    /// Network request failed
129    NetworkFailed(String),
130    /// File I/O failed
131    IoFailed(String),
132    /// Data format is not supported
133    UnsupportedFormat(String),
134    /// Data integrity check failed
135    IntegrityCheckFailed(String),
136}
137
138/// Convenience type alias for Results with BlockpediaError
139pub 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
333// Convenience constructors for common error patterns
334impl 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
383/// Error recovery utilities
384pub mod recovery {
385
386    /// Attempt to recover from a block not found error by suggesting similar blocks
387    pub fn suggest_similar_blocks(block_id: &str) -> Vec<String> {
388        // In a real implementation, this would use fuzzy matching
389        // For now, return some basic suggestions
390        let mut suggestions = Vec::new();
391
392        if block_id.starts_with("minecraft:") {
393            // Already namespaced, suggest removing prefix for common blocks
394            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            // Not namespaced, suggest adding minecraft prefix
401            suggestions.push(format!("minecraft:{}", block_id));
402        }
403
404        suggestions
405    }
406
407    /// Attempt to recover from property value errors by suggesting valid values
408    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        // Find values that are similar to the invalid one
416        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 no similar values found, suggest a few common ones
425        if suggestions.is_empty() && !valid_values.is_empty() {
426            suggestions.extend(valid_values.iter().take(3).cloned());
427        }
428
429        suggestions
430    }
431
432    /// Attempt to fix common parsing errors
433    pub fn fix_common_parse_errors(input: &str) -> String {
434        let mut fixed = input.to_string();
435
436        // Fix missing brackets
437        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                        // Find the last valid block ID character
443                        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        // Fix double colons
460        fixed = fixed.replace("::", ":");
461
462        // Fix spaces around equals
463        fixed = fixed.replace(" = ", "=");
464
465        fixed
466    }
467}
468
469/// Validation utilities
470pub mod validation {
471    use super::*;
472
473    /// Validate block ID format
474    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        // Check for valid namespace format
490        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            // Validate namespace characters
499            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            // Validate name characters
515            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            // No namespace, just validate the name
531            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    /// Validate property name format
551    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    /// Validate property value format
584    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        // Property values can contain more characters than names
602        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}