Skip to main content

nam_rs/common/diagnostics/
error_codes.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2026 Fábio Henrique de Lima Silva (fhl.bsb@gmail.com) All rights reserved.
3
4//! Typed error codes for precise failure triage.
5//!
6//! Numeric code convention:
7//! - **E1xxx**: Model loading (I/O, parse, build)
8//! - **E2xxx**: PipeWire / audio (init, stream, resampler, RT)
9//! - **E3xxx**: SPSC / inter-thread communication
10//! - **E4xxx**: Runtime / CLI (parsing, commands)
11//! - **E5xxx**: System / hardware (CPU features, memory)
12
13use std::fmt;
14
15/// Structured error code for precise failure triage.
16///
17/// Each variant maps to exactly one failure scenario. The numeric code
18/// (used in the support block) follows the convention:
19/// - **E1xxx**: Model loading (I/O, parse, build)
20/// - **E2xxx**: PipeWire / audio (init, stream, resampler, RT)
21/// - **E3xxx**: SPSC / inter-thread communication
22/// - **E4xxx**: Runtime / CLI (parsing, commands)
23/// - **E5xxx**: System / hardware (CPU features, memory)
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub enum NamErrorCode {
26    // E1xxx — Model Loading
27    /// Model file not found on the filesystem.
28    FileNotFound,
29    /// I/O read error while accessing the model file.
30    FileReadError,
31    /// Unknown file extension (expected .nam or .namb).
32    UnknownExtension,
33    /// Failed to parse JSON in .nam format.
34    NamJsonParseError,
35    /// JSON `weights` array exceeds float limit (MAX_WEIGHTS).
36    NamJsonWeightsExceedLimit,
37    /// JSON `metadata.training` field exceeds size limit (1 MiB).
38    NamJsonTrainingTooLarge,
39    /// JSON `metadata.training` field exceeds tree depth limit.
40    NamJsonTrainingTooDeep,
41    /// JSON `config.submodels` array exceeds the maximum count (8).
42    NamJsonSubmodelsExceedLimit,
43    /// JSON `config.submodels` exceeds container recursion depth limit.
44    NamJsonSubmodelsTooDeep,
45    /// JSON weight value is non-finite (NaN, +Inf, -Inf).
46    NamJsonWeightNotFinite,
47    /// JSON `sample_rate` field contains an invalid value (non-finite or <= 0).
48    NamJsonInvalidSampleRate,
49    /// JSON topology exceeds OOM safety limits (MAX_LAYERS or MAX_HIDDEN_SIZE).
50    NamJsonUnsupportedTopology,
51    /// JSON version string is not parseable as SemVer.
52    NamJsonInvalidVersionFormat,
53    /// JSON version is outside the supported range (min 0.5.0, max 0.7.x).
54    NamJsonUnsupportedVersion,
55    /// LSTM model has multi-channel I/O (NAM-rs supports mono only).
56    NamJsonUnsupportedMultiChannel,
57    /// NAMB non-finite weight detected in binary weight section.
58    NambNonFiniteWeight,
59    /// NAMB header field contains an invalid value (non-finite, <= 0, etc.).
60    NambInvalidHeaderField,
61    /// .namb CRC32 checksum does not match expected value.
62    NambCrc32Mismatch,
63    /// CRC32 flag missing in .namb v2+ file (mandatory).
64    NambCrc32Missing,
65    /// Invalid .namb magic signature.
66    NambInvalidMagic,
67    /// Unsupported .namb format version.
68    NambUnsupportedVersion,
69    /// Truncated .namb file (smaller than the minimum header).
70    NambTruncated,
71    /// Declared neural architecture is not supported (neither WaveNet nor LSTM).
72    UnsupportedArchitecture,
73    /// WaveNet/LSTM topology not detectable from the configuration.
74    TopologyDetectionFailed,
75    /// Weight count inconsistent with network geometry.
76    WeightCountMismatch,
77    /// Generic failure building the model via the dispatcher.
78    ModelBuildFailed,
79    /// Model file exceeds the maximum allowed size (256 MiB).
80    ModelTooLarge,
81
82    // E2xxx — PipeWire / Audio
83    /// Failed to initialize PipeWire or create context/core.
84    PipewireInitFailed,
85    /// Failed to connect the PipeWire stream.
86    StreamConnectFailed,
87    /// Failed to build the NamResampler (native Polyphase Sinc).
88    ResamplerBuildFailed,
89    /// SPSC resampler channel full (rebuild discarded).
90    ResamplerChannelFull,
91    /// Permission denied for SCHED_FIFO (no RT privileges).
92    SchedFifoDenied,
93    /// Failed to set CPU affinity on the DSP thread.
94    CpuAffinityFailed,
95    /// PipeWire deadline exceeded (DSP processing exceeded budget).
96    DeadlineExceeded,
97
98    // E3xxx — SPSC / Communication
99    /// CLI→DSP parameter SPSC channel full.
100    ParamChannelFull,
101
102    // E4xxx — Runtime / CLI
103    /// Invalid gain value (not a valid f32 number).
104    InvalidGainValue,
105    /// Unknown CLI command.
106    UnknownCommand,
107    /// Failed to configure the Ctrl-C handler.
108    CtrlCHandlerFailed,
109    /// Failed to load a cab-sim impulse response (WAV file).
110    IrLoadFailed,
111    /// Overflow detected in the Garbage Collection (GC) channel.
112    GcOverflow,
113    /// Corrupted GC overflow buffer slot (inconsistent type/pointer).
114    GcCorrupted,
115    /// Memory allocation failed (OOM) — layout overflow or allocator exhaustion.
116    OutOfMemory,
117}
118
119impl NamErrorCode {
120    /// Returns the numeric code in "Exxxx" format.
121    pub fn code(self) -> &'static str {
122        match self {
123            Self::FileNotFound => "E1100",
124            Self::FileReadError => "E1101",
125            Self::UnknownExtension => "E1102",
126            Self::NamJsonParseError => "E1200",
127            Self::NamJsonWeightsExceedLimit => "E1206",
128            Self::NamJsonTrainingTooLarge => "E1207",
129            Self::NamJsonTrainingTooDeep => "E1208",
130            Self::NamJsonSubmodelsExceedLimit => "E1209",
131            Self::NamJsonSubmodelsTooDeep => "E1210",
132            Self::NamJsonWeightNotFinite => "E1211",
133            Self::NamJsonInvalidSampleRate => "E1214",
134            Self::NamJsonUnsupportedTopology => "E1215",
135            Self::NamJsonInvalidVersionFormat => "E1216",
136            Self::NamJsonUnsupportedVersion => "E1217",
137            Self::NamJsonUnsupportedMultiChannel => "E1218",
138            Self::NambNonFiniteWeight => "E1212",
139            Self::NambInvalidHeaderField => "E1213",
140            Self::NambCrc32Mismatch => "E1201",
141            Self::NambCrc32Missing => "E1205",
142            Self::NambInvalidMagic => "E1202",
143            Self::NambUnsupportedVersion => "E1203",
144            Self::NambTruncated => "E1204",
145            Self::UnsupportedArchitecture => "E1300",
146            Self::TopologyDetectionFailed => "E1301",
147            Self::WeightCountMismatch => "E1302",
148            Self::ModelBuildFailed => "E1303",
149            Self::ModelTooLarge => "E1304",
150            Self::PipewireInitFailed => "E2100",
151            Self::StreamConnectFailed => "E2101",
152            Self::ResamplerBuildFailed => "E2200",
153            Self::ResamplerChannelFull => "E2201",
154            Self::SchedFifoDenied => "E2300",
155            Self::CpuAffinityFailed => "E2301",
156            Self::DeadlineExceeded => "E2001",
157            Self::ParamChannelFull => "E3100",
158            Self::GcOverflow => "E3101",
159            Self::GcCorrupted => "E3102",
160            Self::InvalidGainValue => "E4100",
161            Self::UnknownCommand => "E4101",
162            Self::CtrlCHandlerFailed => "E4102",
163            Self::IrLoadFailed => "E4103",
164            Self::OutOfMemory => "E5000",
165        }
166    }
167
168    /// Returns a human-readable message for GUI display.
169    pub fn message(self) -> &'static str {
170        match self {
171            Self::FileNotFound => "File not found",
172            Self::FileReadError => "File read error",
173            Self::UnknownExtension => "Unknown extension",
174            Self::NamJsonParseError => "Invalid JSON format",
175            Self::NamJsonWeightsExceedLimit => "JSON weights exceed limit",
176            Self::NamJsonTrainingTooLarge => "Training metadata too large",
177            Self::NamJsonTrainingTooDeep => "Training metadata too deeply nested",
178            Self::NamJsonSubmodelsExceedLimit => "Submodels count exceeds limit (max 8)",
179            Self::NamJsonSubmodelsTooDeep => "Container nesting too deep",
180            Self::NamJsonWeightNotFinite => "JSON weight is non-finite (NaN/Inf)",
181            Self::NamJsonInvalidSampleRate => {
182                "JSON sample_rate is invalid (must be finite and > 0)"
183            }
184            Self::NamJsonUnsupportedTopology => {
185                "JSON topology exceeds OOM safety limits (MAX_LAYERS or MAX_HIDDEN_SIZE)"
186            }
187            Self::NamJsonInvalidVersionFormat => "JSON version string is not parseable as SemVer",
188            Self::NamJsonUnsupportedVersion => {
189                "JSON version is outside the supported range (min 0.5.0, max 0.7.x)"
190            }
191            Self::NamJsonUnsupportedMultiChannel => {
192                "LSTM model has multi-channel I/O (only mono supported)"
193            }
194            Self::NambNonFiniteWeight => "NAMB weight is non-finite (NaN/Inf)",
195            Self::NambInvalidHeaderField => "NAMB header field is invalid",
196            Self::NambCrc32Mismatch => "CRC32 checksum mismatch",
197            Self::NambCrc32Missing => "CRC32 integrity flag missing (v2+)",
198            Self::NambInvalidMagic => "Invalid signature",
199            Self::NambUnsupportedVersion => "Unsupported version",
200            Self::NambTruncated => "Corrupted/truncated file",
201            Self::UnsupportedArchitecture => "Unsupported architecture",
202            Self::TopologyDetectionFailed => "Topology detection failed",
203            Self::WeightCountMismatch => "Weight count mismatch",
204            Self::ModelBuildFailed => "Model build failed",
205            Self::ModelTooLarge => "Model file too large",
206            Self::PipewireInitFailed => "PipeWire initialization failed",
207            Self::StreamConnectFailed => "Stream connection failed",
208            Self::ResamplerBuildFailed => "Resampler build failed",
209            Self::ResamplerChannelFull => "Resampler channel full",
210            Self::SchedFifoDenied => "SCHED_FIFO denied",
211            Self::CpuAffinityFailed => "CPU affinity setting failed",
212            Self::DeadlineExceeded => "Processing deadline exceeded",
213            Self::ParamChannelFull => "Parameter channel full",
214            Self::GcOverflow => "Garbage collection overflow",
215            Self::GcCorrupted => "Garbage collection corruption",
216            Self::InvalidGainValue => "Invalid gain value",
217            Self::UnknownCommand => "Unknown command",
218            Self::CtrlCHandlerFailed => "Ctrl-C handler setup failed",
219            Self::IrLoadFailed => "Cab-sim IR load failed",
220            Self::OutOfMemory => "Out of memory",
221        }
222    }
223
224    /// Returns the human-readable mnemonic name (SCREAMING_SNAKE_CASE) of the code.
225    pub fn mnemonic(self) -> &'static str {
226        match self {
227            Self::FileNotFound => "FILE_NOT_FOUND",
228            Self::FileReadError => "FILE_READ_ERROR",
229            Self::UnknownExtension => "UNKNOWN_EXTENSION",
230            Self::NamJsonParseError => "NAM_JSON_PARSE_ERROR",
231            Self::NamJsonWeightsExceedLimit => "NAM_JSON_WEIGHTS_EXCEED_LIMIT",
232            Self::NamJsonTrainingTooLarge => "NAM_JSON_TRAINING_TOO_LARGE",
233            Self::NamJsonTrainingTooDeep => "NAM_JSON_TRAINING_TOO_DEEP",
234            Self::NamJsonSubmodelsExceedLimit => "NAM_JSON_SUBMODELS_EXCEED_LIMIT",
235            Self::NamJsonSubmodelsTooDeep => "NAM_JSON_SUBMODELS_TOO_DEEP",
236            Self::NamJsonWeightNotFinite => "NAM_JSON_WEIGHT_NOT_FINITE",
237            Self::NamJsonInvalidSampleRate => "NAM_JSON_INVALID_SAMPLE_RATE",
238            Self::NamJsonUnsupportedTopology => "NAM_JSON_UNSUPPORTED_TOPOLOGY",
239            Self::NamJsonInvalidVersionFormat => "NAM_JSON_INVALID_VERSION_FORMAT",
240            Self::NamJsonUnsupportedVersion => "NAM_JSON_UNSUPPORTED_VERSION",
241            Self::NamJsonUnsupportedMultiChannel => "NAM_JSON_UNSUPPORTED_MULTI_CHANNEL",
242            Self::NambNonFiniteWeight => "NAMB_NON_FINITE_WEIGHT",
243            Self::NambInvalidHeaderField => "NAMB_INVALID_HEADER_FIELD",
244            Self::NambCrc32Mismatch => "NAMB_CRC32_MISMATCH",
245            Self::NambCrc32Missing => "NAMB_CRC32_MISSING",
246            Self::NambInvalidMagic => "NAMB_INVALID_MAGIC",
247            Self::NambUnsupportedVersion => "NAMB_UNSUPPORTED_VERSION",
248            Self::NambTruncated => "NAMB_TRUNCATED",
249            Self::UnsupportedArchitecture => "UNSUPPORTED_ARCHITECTURE",
250            Self::TopologyDetectionFailed => "TOPOLOGY_DETECTION_FAILED",
251            Self::WeightCountMismatch => "WEIGHT_COUNT_MISMATCH",
252            Self::ModelBuildFailed => "MODEL_BUILD_FAILED",
253            Self::ModelTooLarge => "MODEL_TOO_LARGE",
254            Self::PipewireInitFailed => "PIPEWIRE_INIT_FAILED",
255            Self::StreamConnectFailed => "STREAM_CONNECT_FAILED",
256            Self::ResamplerBuildFailed => "RESAMPLER_BUILD_FAILED",
257            Self::ResamplerChannelFull => "RESAMPLER_CHANNEL_FULL",
258            Self::SchedFifoDenied => "SCHED_FIFO_DENIED",
259            Self::CpuAffinityFailed => "CPU_AFFINITY_FAILED",
260            Self::DeadlineExceeded => "DEADLINE_EXCEEDED",
261            Self::ParamChannelFull => "PARAM_CHANNEL_FULL",
262            Self::GcOverflow => "GC_OVERFLOW",
263            Self::GcCorrupted => "GC_CORRUPTED",
264            Self::InvalidGainValue => "INVALID_GAIN_VALUE",
265            Self::UnknownCommand => "UNKNOWN_COMMAND",
266            Self::CtrlCHandlerFailed => "CTRL_C_HANDLER_FAILED",
267            Self::IrLoadFailed => "IR_LOAD_FAILED",
268            Self::OutOfMemory => "OUT_OF_MEMORY",
269        }
270    }
271}
272
273impl std::error::Error for NamErrorCode {}
274
275impl fmt::Display for NamErrorCode {
276    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
277        write!(f, "{} | {}", self.code(), self.mnemonic())
278    }
279}