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