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 ///
125 /// # Parameters
126 ///
127 /// - `source`: The Base64 decoding error to convert.
128 #[inline]
129 fn from(source: DecodeError) -> Self {
130 Self::InvalidBase64 {
131 kind: Base64ErrorKind::from_decode_error(&source),
132 input_index: Base64ErrorKind::input_index(&source),
133 symbol: Base64ErrorKind::symbol(&source),
134 input_length: Base64ErrorKind::input_length(&source),
135 source,
136 }
137 }
138}