Skip to main content

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}