bistun_core/error.rs
1// Copyright (C) 2026 Francis Xavier Wazeter IV
2// SPDX-License-Identifier: GPL-3.0-or-later
3//
4// This file is part of the Bistun Linguistic Metadata Service (LMS).
5// See the LICENSE file in the workspace root for full license information.
6
7//! # Global Error Definitions
8//! **Crate**: `bistun-core`
9//! **Ref**: `[LMS-PROCESS-ERROR]`
10//! **Domain**: `Operations`
11//! **Location**: `crates/bistun-core/src/error.rs`
12//!
13//! **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.
14//! **Impact**: If error contexts are lost or poorly formatted, observability sinks will lack actionable data, violating the system's operational `SLI` targets.
15//!
16//! ### Architectural Topology
17//! * **Tier**: `0`
18//! * **Dependencies**: `thiserror`
19//! * **Consumers**: All `LMS` modules and the `Resolution Pipeline`
20//! * **State Model**: `Stateless`
21//! * **Design Patterns**: `Failure Narrative`
22//!
23//! ### Local Definitions
24//! * **`Failure Narrative`**: The standard ensuring every error explains What failed, Where it failed, and Why it failed.
25//! * **`thiserror`**: The framework used to derive descriptive, strongly-typed errors.
26
27use thiserror::Error;
28
29/// The universal error type for the Bistun `LMS` pipeline.
30///
31/// ### `OpenAPI` Schema
32/// ```yaml
33/// schema: LmsError
34/// description: The unified, strongly-typed error enumeration representing all pipeline and resolution failures.
35/// ```
36#[derive(Error, Debug, Clone, PartialEq, Eq)]
37pub enum LmsError {
38 /// Raised when a linguistic algorithm fails to process the input string.
39 #[error("[{pipeline_step}] Strategy Execution Failure ({context}): {reason}")]
40 StrategyExecutionFailure {
41 /// Where did it fail? (e.g., "Phase 4: Stemming Strategy")
42 pipeline_step: String,
43 /// What failed? (e.g., the input word or language tag)
44 context: String,
45 /// Why did it fail? (e.g., "Invalid `UTF-8` sequence")
46 reason: String,
47 },
48
49 /// Raised when the `CapabilityManifest` lacks a required trait during resolution.
50 #[error("[{pipeline_step}] Missing Trait ({trait_key}): {reason}")]
51 MissingTrait {
52 /// Where did it fail? (e.g., "Phase 2: Aggregation")
53 pipeline_step: String,
54 /// What failed? (e.g., "`PRIMARY_DIRECTION`")
55 trait_key: String,
56 /// Why did it fail? (e.g., "Script Definition lacks fallback")
57 reason: String,
58 },
59
60 /// Raised when a `BCP 47` tag is mathematically invalid or unparseable.
61 #[error("[{pipeline_step}] Invalid Tag ({tag}): {reason}")]
62 InvalidTag {
63 /// Where did it fail? (e.g., "Phase 1: Taxonomic Resolution")
64 pipeline_step: String,
65 /// What failed? (The raw tag, e.g., "en-US-u-foo-bar")
66 tag: String,
67 /// Why did it fail? (e.g., "Malformed Unicode extension subtag")
68 reason: String,
69 },
70
71 /// Raised when the Taxonomic Engine fails to resolve a locale, exhausting the fallback chain.
72 #[error("[{pipeline_step}] Resolution Failed ({tag}): {reason}")]
73 ResolutionFailed {
74 /// Where did it fail? (e.g., "Phase 1: Taxonomic Resolution")
75 pipeline_step: String,
76 /// What failed? (e.g., the missing canonical tag)
77 tag: String,
78 /// Why did it fail? (e.g., "Exhausted fallback chain without finding a registry entry")
79 reason: String,
80 },
81
82 /// Raised when a capability manifest fails structural or typological integrity checks.
83 #[error("[{pipeline_step}] Integrity Violation ({context}): {reason}")]
84 IntegrityViolation {
85 /// Where did it fail? (e.g., "Phase 4: Integrity Check")
86 pipeline_step: String,
87 /// What failed? (e.g., the canonical locale `ID` being verified)
88 context: String,
89 /// Why did it fail? (e.g., "Bidirectional text inherently requires complex shaping logic")
90 reason: String,
91 },
92
93 /// Raised when registry signature verification or `WORM` state fails.
94 #[error("[{pipeline_step}] Integrity Failure ({context}): {reason}")]
95 SecurityFault {
96 /// Where did it fail? (e.g., "Phase 0: `WORM` Hydration")
97 pipeline_step: String,
98 /// What failed? (e.g., the registry snapshot payload)
99 context: String,
100 /// Why did it fail? (e.g., "`Ed25519` signature mismatch")
101 reason: String,
102 },
103
104 /// Raised when the `WORM` snapshot cannot be read or contains a structural persistence breach.
105 #[error("[{pipeline_step}] Persistence Fault ({context}): {reason}")]
106 PersistenceFault {
107 /// Where did it fail? (e.g., "Phase 0: `WORM` Hydration")
108 pipeline_step: String,
109 /// What failed? (e.g., the file path or `URI`)
110 context: String,
111 /// Why did it fail? (e.g., "Snapshot checksum mismatch")
112 reason: String,
113 },
114
115 /// Raised when Phase 2 (Aggregation) fails to synthesize algorithmic rules due to unresolvable conflicts.
116 #[error("[{pipeline_step}] Rule Synthesis Failure ({context}): {reason}")]
117 RuleSynthesisFailure {
118 /// Where did it fail? (e.g., "Phase 2: Typology Aggregation")
119 pipeline_step: String,
120 /// What failed? (e.g., "`PLURAL_LOGIC`")
121 context: String,
122 /// Why did it fail? (e.g., "Conflicting plural categories without a clear High-Water Mark")
123 reason: String,
124 },
125
126 /// Raised when Phase 2.5 (Resource Bridge) cannot map an abstract Resource `ID` to a physical `URI`.
127 #[error("[{pipeline_step}] Resource Resolution Failure ({context}): {reason}")]
128 ResourceResolutionFailure {
129 /// Where did it fail? (e.g., "Phase 2.5: Resource Bridge")
130 pipeline_step: String,
131 /// What failed? (e.g., "`icu_arab`")
132 context: String,
133 /// Why did it fail? (e.g., "Resource `ID` not found in deployment map")
134 reason: String,
135 },
136
137 /// Raised when Phase 3 (Override) encounters a malformed or unsupported `BCP 47` extension subtag.
138 #[error("[{pipeline_step}] Extension Mapping Failure ({context}): {reason}")]
139 ExtensionMappingFailure {
140 /// Where did it fail? (e.g., "Phase 3: Extension Override")
141 pipeline_step: String,
142 /// What failed? (e.g., "-u-nu-invalid")
143 context: String,
144 /// Why did it fail? (e.g., "Unsupported numbering system requested")
145 reason: String,
146 },
147}
148
149#[cfg(test)]
150mod tests {
151 use super::*;
152
153 /// **What is being proven**: Verifies that the `Failure Narrative` formatting correctly structures the `LmsError` context for observability sinks.
154 #[test]
155 fn test_error_narrative_formatting() {
156 // [1] Set up Mock
157 let error = LmsError::StrategyExecutionFailure {
158 pipeline_step: "Phase 4: Strategy Execution".to_string(),
159 context: "input_word".to_string(),
160 reason: "Agglutinative stemmer encountered invalid suffix".to_string(),
161 };
162
163 // [2] Execute
164 let output = error.to_string();
165
166 // [3] Assert Equation
167 assert_eq!(
168 output,
169 "[Phase 4: Strategy Execution] Strategy Execution Failure (input_word): Agglutinative stemmer encountered invalid suffix"
170 );
171 }
172}