Skip to main content

qubit_codec/transcode/engine/
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
12/// Action selected after a codec reports invalid encoded input.
13///
14/// While the stream is open, incomplete input is not an invalid-input policy
15/// action. Codecs report it with [`crate::DecodeFailure::Incomplete`], and the
16/// decode engine converts it into [`crate::TranscodeStatus::NeedInput`]. After
17/// upstream EOF, [`crate::Transcoder::transcode_eof`] asks the codec to resolve
18/// a trailing incomplete sequence through [`crate::Codec::decode_eof`]; if it
19/// remains incomplete, the engine calls
20/// [`crate::engine::DecodeIncompleteAction`] through
21/// [`crate::engine::TranscodeDecodeHooks::handle_incomplete_decode`].
22///
23/// # Type Parameters
24///
25/// - `Value`: Decoded output value type.
26///
27/// # Examples
28///
29/// ```
30/// use core::num::NonZeroUsize;
31///
32/// use qubit_codec::engine::DecodeInvalidAction;
33///
34/// let action = DecodeInvalidAction::<u8>::Skip {
35///     consumed: NonZeroUsize::new(1).unwrap(),
36/// };
37/// assert!(matches!(action, DecodeInvalidAction::Skip { .. }));
38/// ```
39#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
40#[non_exhaustive]
41#[must_use]
42pub enum DecodeInvalidAction<Value> {
43    /// Reject the current invalid input.
44    ///
45    /// The decode engine reports the original codec decode error as
46    /// [`crate::TranscodeDecodeError::Domain`] at the current input index.
47    Reject,
48
49    /// Consume invalid input without producing output.
50    ///
51    /// `consumed` must not exceed the hook context's available input count.
52    /// Over-consuming is a hook contract violation and panics in the engine.
53    Skip {
54        /// Source units to consume.
55        consumed: NonZeroUsize,
56    },
57
58    /// Produce one replacement value and consume source units.
59    ///
60    /// `consumed` must not exceed the hook context's available input count.
61    /// Over-consuming is a hook contract violation and panics in the engine.
62    Emit {
63        /// Value to write to the output buffer.
64        value: Value,
65        /// Source units to consume.
66        consumed: NonZeroUsize,
67    },
68}
69
70impl<Value> DecodeInvalidAction<Value> {
71    /// Validates a policy-reported consumed source-unit count against available
72    /// input.
73    ///
74    /// # Parameters
75    ///
76    /// - `consumed`: Source units requested by the concrete policy.
77    /// - `available`: Source units visible from the current decode cursor.
78    ///
79    /// # Returns
80    ///
81    /// Returns the validated non-zero consumed count.
82    ///
83    /// # Panics
84    ///
85    /// Panics when `available == 0` or when `consumed > available`.
86    #[inline]
87    #[must_use]
88    pub(super) fn bound_consumed(consumed: NonZeroUsize, available: usize) -> NonZeroUsize {
89        assert!(available > 0, "DecodeInvalidAction cannot consume empty input",);
90        assert!(
91            consumed.get() <= available,
92            "DecodeInvalidAction consumed units must not exceed available input",
93        );
94        consumed
95    }
96}