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}