Skip to main content

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