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}