Skip to main content

qubit_codec_misc/
misc_codec_error.rs

1// =============================================================================
2//    Copyright (c) 2026 Haixing Hu.
3//
4//    SPDX-License-Identifier: Apache-2.0
5//
6//    Licensed under the Apache License, Version 2.0.
7// =============================================================================
8//! Shared codec error type.
9
10use core::num::NonZeroUsize;
11use std::string::FromUtf8Error;
12
13use ::base64::DecodeError;
14use qubit_codec::DecodeFailure;
15use thiserror::Error;
16
17use crate::base64_error_kind::Base64ErrorKind;
18
19/// Result alias returned by codec operations.
20pub type MiscCodecResult<T> = Result<T, MiscCodecError>;
21
22/// Error returned by codec operations.
23#[derive(Debug, Error)]
24pub enum MiscCodecError {
25    /// Base64 decoding failed in the configured dependency engine.
26    #[error("invalid Base64 input ({kind:?}): {source}")]
27    InvalidBase64 {
28        /// Structured category of the Base64 failure.
29        kind: Base64ErrorKind,
30        /// Input byte index, when the dependency reports one.
31        input_index: Option<usize>,
32        /// Invalid symbol, when the dependency reports one.
33        symbol: Option<u8>,
34        /// Invalid input length, when the dependency reports one.
35        input_length: Option<usize>,
36        /// Underlying Base64 decoding error.
37        #[source]
38        source: DecodeError,
39    },
40
41    /// A configured prefix was required but missing.
42    #[error("missing required prefix '{prefix}'")]
43    MissingPrefix {
44        /// Required prefix.
45        prefix: String,
46    },
47
48    /// Input contained a digit that is invalid for the requested radix.
49    #[error("invalid radix-{radix} digit '{character}' at index {index}")]
50    InvalidDigit {
51        /// Numeric radix expected by the codec.
52        radix: u32,
53        /// Character byte index in the original input.
54        index: usize,
55        /// Invalid character.
56        character: char,
57    },
58
59    /// Input length does not satisfy a codec requirement.
60    #[error("invalid length for {context}: expected {expected}, got {actual}")]
61    InvalidLength {
62        /// Human-readable input part whose length was invalid.
63        context: &'static str,
64        /// Human-readable length requirement.
65        expected: String,
66        /// Actual length observed by the codec.
67        actual: usize,
68    },
69
70    /// Input contained a malformed or unsupported escape sequence.
71    #[error("invalid escape {escape:?} at index {index}: {reason}")]
72    InvalidEscape {
73        /// Byte index of the escape marker in the original input.
74        index: usize,
75        /// Escape sequence fragment that caused the error.
76        escape: String,
77        /// Human-readable reason the escape was rejected.
78        reason: String,
79    },
80
81    /// Input contained a character that cannot appear in that context.
82    #[error("invalid character '{character}' at index {index}: {reason}")]
83    InvalidCharacter {
84        /// Character byte index in the original input.
85        index: usize,
86        /// Invalid character.
87        character: char,
88        /// Human-readable reason the character was rejected.
89        reason: String,
90    },
91
92    /// Input was rejected by a codec-specific validator.
93    #[error("invalid {codec} input: {reason}")]
94    InvalidInput {
95        /// Stable codec name, such as `base64`.
96        codec: &'static str,
97        /// Human-readable reason reported by the codec.
98        reason: String,
99    },
100
101    /// Decoded bytes were not valid UTF-8.
102    #[error("decoded bytes are not valid UTF-8: {source}")]
103    InvalidUtf8 {
104        /// Underlying UTF-8 conversion error.
105        #[from]
106        source: FromUtf8Error,
107    },
108}
109
110impl From<DecodeError> for MiscCodecError {
111    /// Preserves a Base64 dependency error and exposes its structured fields.
112    #[inline]
113    fn from(source: DecodeError) -> Self {
114        Self::InvalidBase64 {
115            kind: Base64ErrorKind::from_decode_error(&source),
116            input_index: Base64ErrorKind::input_index(&source),
117            symbol: Base64ErrorKind::symbol(&source),
118            input_length: Base64ErrorKind::input_length(&source),
119            source,
120        }
121    }
122}
123
124/// Maps a malformed value to a decode failure with a known input width.
125#[inline]
126pub(crate) fn map_misc_decode_failure_with_consumed(
127    error: MiscCodecError,
128    consumed: NonZeroUsize,
129) -> DecodeFailure<MiscCodecError> {
130    DecodeFailure::invalid(error, consumed)
131}