Skip to main content

bistun_core/
error.rs

1// Bistun Linguistic Metadata Service (LMS)
2// Copyright (C) 2026  Francis Xavier Wazeter IV
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program. If not, see <https://www.gnu.org/licenses/>.
16
17//! # Global Error Definitions
18//! Crate: bistun-core
19//! Ref: [LMS-PROCESS-ERROR]
20//! Location: `crates/bistun-core/src/error.rs`
21//!
22//! **Why**: This module provides a unified, strongly-typed error enumeration using the `thiserror` framework. It ensures all 5-phase pipeline failures are context-aware and bubble up gracefully to the telemetry phase.
23//! **Impact**: If error contexts are lost or poorly formatted, observability sinks will lack actionable data, violating the system's operational SLAs.
24//!
25//! ### Glossary
26//! * **thiserror**: The framework used to derive descriptive, strongly-typed errors.
27//! * **Failure Narrative**: The standard ensuring every error explains What failed, Where it failed, and Why it failed.
28
29use thiserror::Error;
30
31/// The universal error type for the Bistun LMS pipeline.
32///
33/// Time: O(1) (Definition) | Space: O(1)
34///
35/// # Logic Trace (Internal)
36/// 1. Associates standard pipeline failures with explicit context fields.
37/// 2. Derives standard `std::error::Error` and `std::fmt::Display` via `thiserror`.
38///
39/// # Examples
40/// ```rust
41/// use crate::bistun_core::LmsError;
42///
43/// let err = LmsError::MissingTrait {
44///     pipeline_step: "Phase 2: Aggregation".to_string(),
45///     trait_key: "MORPHOLOGY_TYPE".to_string(),
46///     reason: "Golden Set baseline breached".to_string(),
47/// };
48///
49/// assert_eq!(
50///     err.to_string(),
51///     "[Phase 2: Aggregation] Missing Trait (MORPHOLOGY_TYPE): Golden Set baseline breached"
52/// );
53/// ```
54#[derive(Error, Debug, Clone, PartialEq, Eq)]
55pub enum LmsError {
56    /// Raised when a linguistic algorithm fails to process the input string.
57    #[error("[{pipeline_step}] Strategy Execution Failure ({context}): {reason}")]
58    StrategyExecutionFailure {
59        /// Where did it fail? (e.g., "Phase 4: Stemming Strategy")
60        pipeline_step: String,
61        /// What failed? (e.g., the input word or language tag)
62        context: String,
63        /// Why did it fail? (e.g., "Invalid UTF-8 sequence")
64        reason: String,
65    },
66
67    /// Raised when the `CapabilityManifest` lacks a required trait during resolution.
68    #[error("[{pipeline_step}] Missing Trait ({trait_key}): {reason}")]
69    MissingTrait {
70        /// Where did it fail? (e.g., "Phase 2: Aggregation")
71        pipeline_step: String,
72        /// What failed? (e.g., "`PRIMARY_DIRECTION`")
73        trait_key: String,
74        /// Why did it fail? (e.g., "Script Definition lacks fallback")
75        reason: String,
76    },
77
78    /// Raised when a BCP 47 tag is mathematically invalid or unparseable.
79    #[error("[{pipeline_step}] Invalid Tag ({tag}): {reason}")]
80    InvalidTag {
81        /// Where did it fail? (e.g., "Phase 1: Taxonomic Resolution")
82        pipeline_step: String,
83        /// What failed? (The raw tag, e.g., "en-US-u-foo-bar")
84        tag: String,
85        /// Why did it fail? (e.g., "Malformed Unicode extension subtag")
86        reason: String,
87    },
88
89    /// Raised when the Taxonomic Engine fails to resolve a locale, exhausting the fallback chain.
90    #[error("[{pipeline_step}] Resolution Failed ({tag}): {reason}")]
91    ResolutionFailed {
92        /// Where did it fail? (e.g., "Phase 1: Taxonomic Resolution")
93        pipeline_step: String,
94        /// What failed? (e.g., the missing canonical tag)
95        tag: String,
96        /// Why did it fail? (e.g., "Exhausted fallback chain without finding a registry entry")
97        reason: String,
98    },
99
100    /// Raised when a capability manifest fails structural or typological integrity checks.
101    #[error("[{pipeline_step}] Integrity Violation ({context}): {reason}")]
102    IntegrityViolation {
103        /// Where did it fail? (e.g., "Phase 4: Integrity Check")
104        pipeline_step: String,
105        /// What failed? (e.g., the canonical locale ID being verified)
106        context: String,
107        /// Why did it fail? (e.g., "Bidirectional text inherently requires complex shaping logic")
108        reason: String,
109    },
110
111    /// Raised when registry signature verification or WORM state fails.
112    #[error("[{pipeline_step}] Integrity Failure ({context}): {reason}")]
113    SecurityFault {
114        /// Where did it fail? (e.g., "Phase 0: WORM Hydration")
115        pipeline_step: String,
116        /// What failed? (e.g., the registry snapshot payload)
117        context: String,
118        /// Why did it fail? (e.g., "Ed25519 signature mismatch")
119        reason: String,
120    },
121
122    /// Raised when the WORM snapshot cannot be read or contains a structural persistence breach.
123    #[error("[{pipeline_step}] Persistence Fault ({context}): {reason}")]
124    PersistenceFault {
125        /// Where did it fail? (e.g., "Phase 0: WORM Hydration")
126        pipeline_step: String,
127        /// What failed? (e.g., the file path or URI)
128        context: String,
129        /// Why did it fail? (e.g., "Snapshot checksum mismatch")
130        reason: String,
131    },
132
133    /// Raised when Phase 2 (Aggregation) fails to synthesize algorithmic rules due to unresolvable conflicts.
134    #[error("[{pipeline_step}] Rule Synthesis Failure ({context}): {reason}")]
135    RuleSynthesisFailure {
136        /// Where did it fail? (e.g., "Phase 2: Typology Aggregation")
137        pipeline_step: String,
138        /// What failed? (e.g., "`PLURAL_LOGIC`")
139        context: String,
140        /// Why did it fail? (e.g., "Conflicting plural categories without a clear High-Water Mark")
141        reason: String,
142    },
143
144    /// Raised when Phase 2.5 (Resource Bridge) cannot map an abstract Resource ID to a physical URI.
145    #[error("[{pipeline_step}] Resource Resolution Failure ({context}): {reason}")]
146    ResourceResolutionFailure {
147        /// Where did it fail? (e.g., "Phase 2.5: Resource Bridge")
148        pipeline_step: String,
149        /// What failed? (e.g., "`icu_arab`")
150        context: String,
151        /// Why did it fail? (e.g., "Resource ID not found in deployment map")
152        reason: String,
153    },
154
155    /// Raised when Phase 3 (Override) encounters a malformed or unsupported BCP 47 extension subtag.
156    #[error("[{pipeline_step}] Extension Mapping Failure ({context}): {reason}")]
157    ExtensionMappingFailure {
158        /// Where did it fail? (e.g., "Phase 3: Extension Override")
159        pipeline_step: String,
160        /// What failed? (e.g., "-u-nu-invalid")
161        context: String,
162        /// Why did it fail? (e.g., "Unsupported numbering system requested")
163        reason: String,
164    },
165}
166
167#[cfg(test)]
168mod tests {
169    use super::*;
170
171    #[test]
172    fn test_error_narrative_formatting() {
173        // [Logic Trace Mapping]
174        // [STEP 1]: Setup: Instantiate a StrategyExecutionFailure error variant.
175        // [STEP 2]: Execute: Format the error via the derived Display trait.
176        // [STEP 3]: Assert: Verify the string output matches the LMS-PROCESS-ERROR "Failure Narrative" (What, Where, Why).
177
178        let error = LmsError::StrategyExecutionFailure {
179            pipeline_step: "Phase 4: Strategy Execution".to_string(),
180            context: "input_word".to_string(),
181            reason: "Agglutinative stemmer encountered invalid suffix".to_string(),
182        };
183
184        let output = error.to_string();
185
186        assert_eq!(
187            output,
188            "[Phase 4: Strategy Execution] Strategy Execution Failure (input_word): Agglutinative stemmer encountered invalid suffix"
189        );
190    }
191}