qubit-codec-misc 0.3.0

Miscellaneous byte and text format codecs for Rust applications
Documentation
// =============================================================================
//    Copyright (c) 2026 Haixing Hu.
//
//    SPDX-License-Identifier: Apache-2.0
//
//    Licensed under the Apache License, Version 2.0.
// =============================================================================
//! Shared codec error type.

use core::num::NonZeroUsize;
use std::string::FromUtf8Error;

use crate::base64_error_kind::Base64ErrorKind;
use ::base64::DecodeError;
use qubit_codec::DecodeFailure;
use thiserror::Error;

/// Result alias returned by codec operations.
pub type MiscCodecResult<T> = Result<T, MiscCodecError>;

/// Error returned by codec operations.
#[derive(Debug, Error)]
pub enum MiscCodecError {
    /// Base64 decoding failed in the configured dependency engine.
    #[error("invalid Base64 input ({kind:?}): {source}")]
    InvalidBase64 {
        /// Structured category of the Base64 failure.
        kind: Base64ErrorKind,
        /// Input byte index, when the dependency reports one.
        input_index: Option<usize>,
        /// Invalid symbol, when the dependency reports one.
        symbol: Option<u8>,
        /// Invalid input length, when the dependency reports one.
        input_length: Option<usize>,
        /// Underlying Base64 decoding error.
        #[source]
        source: DecodeError,
    },

    /// A configured prefix was required but missing.
    #[error("missing required prefix '{prefix}'")]
    MissingPrefix {
        /// Required prefix.
        prefix: String,
    },

    /// Input contained a digit that is invalid for the requested radix.
    #[error("invalid radix-{radix} digit '{character}' at index {index}")]
    InvalidDigit {
        /// Numeric radix expected by the codec.
        radix: u32,
        /// Character byte index in the original input.
        index: usize,
        /// Invalid character.
        character: char,
    },

    /// Input length does not satisfy a codec requirement.
    #[error("invalid length for {context}: expected {expected}, got {actual}")]
    InvalidLength {
        /// Human-readable input part whose length was invalid.
        context: &'static str,
        /// Human-readable length requirement.
        expected: String,
        /// Actual length observed by the codec.
        actual: usize,
    },

    /// Input contained a malformed or unsupported escape sequence.
    #[error("invalid escape {escape:?} at index {index}: {reason}")]
    InvalidEscape {
        /// Byte index of the escape marker in the original input.
        index: usize,
        /// Escape sequence fragment that caused the error.
        escape: String,
        /// Human-readable reason the escape was rejected.
        reason: String,
    },

    /// Input contained a character that cannot appear in that context.
    #[error("invalid character '{character}' at index {index}: {reason}")]
    InvalidCharacter {
        /// Character byte index in the original input.
        index: usize,
        /// Invalid character.
        character: char,
        /// Human-readable reason the character was rejected.
        reason: String,
    },

    /// Input was rejected by a codec-specific validator.
    #[error("invalid {codec} input: {reason}")]
    InvalidInput {
        /// Stable codec name, such as `base64`.
        codec: &'static str,
        /// Human-readable reason reported by the codec.
        reason: String,
    },

    /// Decoded bytes were not valid UTF-8.
    #[error("decoded bytes are not valid UTF-8: {source}")]
    InvalidUtf8 {
        /// Underlying UTF-8 conversion error.
        #[from]
        source: FromUtf8Error,
    },
}

impl From<DecodeError> for MiscCodecError {
    /// Preserves a Base64 dependency error and exposes its structured fields.
    #[inline]
    fn from(source: DecodeError) -> Self {
        Self::InvalidBase64 {
            kind: Base64ErrorKind::from_decode_error(&source),
            input_index: Base64ErrorKind::input_index(&source),
            symbol: Base64ErrorKind::symbol(&source),
            input_length: Base64ErrorKind::input_length(&source),
            source,
        }
    }
}

/// Maps a malformed value to a decode failure with a known input width.
#[inline]
pub(crate) fn map_misc_decode_failure_with_consumed(
    error: MiscCodecError,
    consumed: NonZeroUsize,
) -> DecodeFailure<MiscCodecError> {
    DecodeFailure::invalid(error, consumed)
}