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}