Skip to main content

qubit_codec/transcode/
decode_invalid_action.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//! Invalid-decode actions returned by buffered decoder policy hooks.
9
10use core::num::NonZeroUsize;
11
12use super::internal::decode_step::DecodeStep;
13
14/// Action selected after a codec reports invalid encoded input.
15///
16/// Incomplete input is not a policy action. Codecs report it with
17/// [`crate::DecodeFailure::Incomplete`], and the decode engine converts it
18/// directly into [`crate::TranscodeStatus::NeedInput`].
19///
20/// # Type Parameters
21///
22/// - `Value`: Decoded output value type.
23#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
24pub enum DecodeInvalidAction<Value> {
25    /// Consume invalid input without producing output.
26    ///
27    /// `consumed` must not exceed the hook context's available input count.
28    /// Over-consuming is a hook contract violation and panics in the engine.
29    Skip {
30        /// Source units to consume.
31        consumed: NonZeroUsize,
32    },
33
34    /// Produce one replacement value and consume source units.
35    ///
36    /// `consumed` must not exceed the hook context's available input count.
37    /// Over-consuming is a hook contract violation and panics in the engine.
38    Emit {
39        /// Value to write to the output buffer.
40        value: Value,
41        /// Source units to consume.
42        consumed: NonZeroUsize,
43    },
44}
45
46impl<Value> DecodeInvalidAction<Value> {
47    /// Converts this policy action into the normalized internal decode attempt.
48    ///
49    /// # Parameters
50    ///
51    /// - `input_index`: Absolute source index where the failed decode started.
52    /// - `available`: Source units visible from `input_index`.
53    ///
54    /// # Returns
55    ///
56    /// Returns the internal decode attempt consumed by buffered decode loops.
57    ///
58    /// # Panics
59    ///
60    /// Panics when a consuming action exceeds `available`.
61    #[inline]
62    #[must_use]
63    pub(super) fn into_step(
64        self,
65        input_index: usize,
66        available: usize,
67    ) -> DecodeStep<Value> {
68        match self {
69            Self::Skip { consumed } => {
70                DecodeStep::skipped(Self::bound_consumed(consumed, available))
71            }
72            Self::Emit { value, consumed } => DecodeStep::decoded(
73                value,
74                Self::bound_consumed(consumed, available),
75                input_index,
76            ),
77        }
78    }
79
80    /// Validates a policy-reported consumed source-unit count against available
81    /// input.
82    ///
83    /// # Parameters
84    ///
85    /// - `consumed`: Source units requested by the concrete policy.
86    /// - `available`: Source units visible from the current decode cursor.
87    ///
88    /// # Returns
89    ///
90    /// Returns the validated non-zero consumed count.
91    ///
92    /// # Panics
93    ///
94    /// Panics when `available == 0` or when `consumed > available`.
95    #[inline(always)]
96    #[must_use]
97    fn bound_consumed(
98        consumed: NonZeroUsize,
99        available: usize,
100    ) -> NonZeroUsize {
101        assert!(
102            available > 0,
103            "DecodeInvalidAction cannot consume empty input",
104        );
105        assert!(
106            consumed.get() <= available,
107            "DecodeInvalidAction consumed units must not exceed available input",
108        );
109        consumed
110    }
111}