qubit-codec 0.17.4

Core codec traits and buffer conversion primitives for Rust
Documentation
// =============================================================================
//    Copyright (c) 2026 Haixing Hu.
//
//    SPDX-License-Identifier: Apache-2.0
//
//    Licensed under the Apache License, Version 2.0.
// =============================================================================
//! Domain-specific transcode errors with codec phase context.

use core::num::NonZeroUsize;

use thiserror::Error;

/// Domain-specific codec, charset, or policy error with transcode context.
///
/// Reset and finish failures are lifecycle failures and therefore cannot carry
/// an input index. Main-phase failures carry an input index whose coordinate
/// meaning is defined by the concrete adapter. For codec-backed converters,
/// ordinary decoded values retain their index in the input slice supplied to
/// the decode call that produced them. A pending value keeps that index across
/// calls; it is not rebased to a later input slice and is not a whole-stream
/// offset. The retained index may be outside the bounds of a later input
/// slice. Lifecycle-generated values encoded during reset or finish use
/// placeholder index 0 because they have no source-unit position. Their encode
/// error still has the main phase because it describes the underlying encode
/// operation.
///
/// # Type Parameters
///
/// - `E`: Domain error reported by the concrete codec, hook, or facade.
///
/// # Examples
///
/// ```
/// use qubit_codec::TranscodeDomainError;
///
/// let error = TranscodeDomainError::main("invalid byte", 4);
/// assert_eq!(error.input_index(), Some(4));
/// assert_eq!(error.to_string(), "codec main error at input index 4: invalid byte");
/// ```
#[derive(Clone, Copy, Debug, Eq, Error, Hash, PartialEq)]
#[must_use]
pub enum TranscodeDomainError<E> {
    /// Domain error reported while resetting codec or hook state.
    #[error("codec reset error: {source}")]
    Reset {
        /// Domain error returned by the codec or policy facade.
        #[source]
        source: E,
    },

    /// Domain error reported while processing input.
    #[error("codec main error at input index {input_index}: {source}")]
    Main {
        /// Domain error returned by the codec or policy facade.
        #[source]
        source: E,
        /// Input index where the error occurred, in the coordinate system
        /// defined by the concrete adapter.
        input_index: usize,
        /// Invalid input units consumed by the codec-domain error, when known.
        input_consumed: Option<NonZeroUsize>,
    },

    /// Domain error reported while finishing codec or hook state.
    #[error("codec finish error: {source}")]
    Finish {
        /// Domain error returned by the codec or policy facade.
        #[source]
        source: E,
    },
}

impl<E> TranscodeDomainError<E> {
    /// Creates a reset-phase domain error.
    ///
    /// # Parameters
    ///
    /// - `source`: Domain error returned by reset handling.
    ///
    /// # Returns
    ///
    /// Returns a reset-phase domain error.
    #[inline]
    pub const fn reset(source: E) -> Self {
        Self::Reset { source }
    }

    /// Creates a main-phase domain error.
    ///
    /// # Parameters
    ///
    /// - `source`: Domain error returned by value processing.
    /// - `input_index`: Input index where the error occurred, in the coordinate
    ///   system defined by the concrete adapter.
    ///
    /// # Returns
    ///
    /// Returns a main-phase domain error.
    #[inline]
    pub const fn main(source: E, input_index: usize) -> Self {
        Self::Main {
            source,
            input_index,
            input_consumed: None,
        }
    }

    /// Creates a main-phase domain error with decode consumption context.
    ///
    /// # Parameters
    ///
    /// - `source`: Domain error returned by value processing.
    /// - `input_index`: Input index where the error occurred, in the coordinate
    ///   system defined by the concrete adapter.
    /// - `input_consumed`: Invalid input units consumed, when known.
    ///
    /// # Returns
    ///
    /// Returns a main-phase domain error with consumption context.
    #[inline]
    pub const fn main_with_consumed(source: E, input_index: usize, input_consumed: Option<NonZeroUsize>) -> Self {
        Self::Main {
            source,
            input_index,
            input_consumed,
        }
    }

    /// Creates a finish-phase domain error.
    ///
    /// # Parameters
    ///
    /// - `source`: Domain error returned by finish handling.
    ///
    /// # Returns
    ///
    /// Returns a finish-phase domain error.
    #[inline]
    pub const fn finish(source: E) -> Self {
        Self::Finish { source }
    }

    /// Returns the wrapped source error.
    ///
    /// # Returns
    ///
    /// Returns a shared reference to the domain error.
    #[inline]
    #[must_use]
    pub const fn source(&self) -> &E {
        match self {
            Self::Reset { source } | Self::Main { source, .. } | Self::Finish { source } => source,
        }
    }

    /// Consumes this error and returns the wrapped source error.
    ///
    /// # Returns
    ///
    /// Returns the domain error.
    #[inline]
    #[must_use]
    pub fn into_source(self) -> E {
        match self {
            Self::Reset { source } | Self::Main { source, .. } | Self::Finish { source } => source,
        }
    }

    /// Returns the input index associated with the error, in the coordinate
    /// system defined by the concrete adapter. The value may be a placeholder
    /// when the adapter reports an error for a value without a source-unit
    /// position.
    ///
    /// # Returns
    ///
    /// Returns `Some(index)` for main-phase errors and `None` for reset or
    /// finish errors.
    #[inline]
    pub const fn input_index(&self) -> Option<usize> {
        match self {
            Self::Main { input_index, .. } => Some(*input_index),
            Self::Reset { .. } | Self::Finish { .. } => None,
        }
    }

    /// Returns invalid input consumption associated with the error.
    ///
    /// # Returns
    ///
    /// Returns `Some(consumed)` when a main-phase decode error reports the
    /// invalid input width. Returns `None` otherwise.
    #[inline]
    pub const fn input_consumed(&self) -> Option<NonZeroUsize> {
        match self {
            Self::Main { input_consumed, .. } => *input_consumed,
            Self::Reset { .. } | Self::Finish { .. } => None,
        }
    }

    /// Maps the wrapped source error while preserving phase context.
    ///
    /// # Type Parameters
    ///
    /// - `T`: Target source error type.
    /// - `F`: Mapping function type.
    ///
    /// # Parameters
    ///
    /// - `f`: Function applied to the wrapped source error.
    ///
    /// # Returns
    ///
    /// Returns the mapped domain error.
    #[inline]
    pub fn map_source<T, F>(self, f: F) -> TranscodeDomainError<T>
    where
        F: FnOnce(E) -> T,
    {
        match self {
            Self::Reset { source } => TranscodeDomainError::Reset { source: f(source) },
            Self::Main {
                source,
                input_index,
                input_consumed,
            } => TranscodeDomainError::Main {
                source: f(source),
                input_index,
                input_consumed,
            },
            Self::Finish { source } => TranscodeDomainError::Finish { source: f(source) },
        }
    }
}