Skip to main content

qubit_codec/transcode/
decode_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//! 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 decode attempt fails during `transcode`.
15///
16/// # Type Parameters
17///
18/// - `Value`: Decoded output value type.
19#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
20pub enum DecodeAction<Value> {
21    /// More source units are needed before a value can be produced.
22    ///
23    /// When returned by a decode hook, `required_total` must be greater than
24    /// the hook context's available input count. Returning a value that is
25    /// already satisfied is a hook contract violation and panics in the engine.
26    NeedInput {
27        /// Total units required from the current value start.
28        required_total: usize,
29    },
30
31    /// Consume invalid input without producing output.
32    ///
33    /// When returned by a decode hook, `consumed` must not exceed the hook
34    /// context's available input count. Over-consuming is a hook contract
35    /// violation and panics in the engine.
36    Skip {
37        /// Source units to consume.
38        consumed: NonZeroUsize,
39    },
40
41    /// Produce one value and consume source units.
42    ///
43    /// When returned by a decode hook, `consumed` must not exceed the hook
44    /// context's available input count. Over-consuming is a hook contract
45    /// violation and panics in the engine.
46    Emit {
47        /// Value to write to the output buffer.
48        value: Value,
49        /// Source units to consume.
50        consumed: NonZeroUsize,
51    },
52}
53
54impl<Value> DecodeAction<Value> {
55    /// Converts this policy action into the normalized internal decode attempt.
56    ///
57    /// # Parameters
58    ///
59    /// - `input_index`: Absolute source index where the failed decode started.
60    /// - `available`: Source units visible from `input_index`.
61    ///
62    /// # Returns
63    ///
64    /// Returns the internal decode attempt consumed by buffered decode loops.
65    ///
66    /// # Panics
67    ///
68    /// Panics when a hook returns `NeedInput` with `required_total <=
69    /// available` or a consuming action whose consumed count exceeds
70    /// `available`.
71    #[must_use]
72    #[inline]
73    pub(super) fn into_step(
74        self,
75        input_index: usize,
76        available: usize,
77    ) -> DecodeStep<Value> {
78        match self {
79            Self::NeedInput { required_total } => DecodeStep::need_input(
80                Self::missing_input(required_total, available),
81                available,
82            ),
83            Self::Skip { consumed } => {
84                DecodeStep::skipped(Self::bound_consumed(consumed, available))
85            }
86            Self::Emit { value, consumed } => DecodeStep::decoded(
87                value,
88                Self::bound_consumed(consumed, available),
89                input_index,
90            ),
91        }
92    }
93
94    /// Returns the additional source units required by a need-input action.
95    ///
96    /// # Parameters
97    ///
98    /// - `required_total`: Total source units required from the current value
99    ///   start.
100    /// - `available`: Source units already visible at the current value start.
101    ///
102    /// # Returns
103    ///
104    /// Returns a non-zero additional source-unit count.
105    ///
106    /// # Panics
107    ///
108    /// Panics when `required_total <= available`.
109    #[must_use]
110    #[inline(always)]
111    fn missing_input(required_total: usize, available: usize) -> NonZeroUsize {
112        assert!(
113            required_total > available,
114            "DecodeAction::NeedInput required_total must exceed available input",
115        );
116        let additional = required_total - available;
117        qubit_io::nz(additional)
118    }
119
120    /// Validates a policy-reported consumed source-unit count against available
121    /// input.
122    ///
123    /// # Parameters
124    ///
125    /// - `consumed`: Source units requested by the concrete policy.
126    /// - `available`: Source units visible from the current decode cursor.
127    ///
128    /// # Returns
129    ///
130    /// Returns the validated non-zero consumed count.
131    ///
132    /// # Panics
133    ///
134    /// Panics when `available == 0` or when `consumed > available`.
135    #[must_use]
136    #[inline(always)]
137    fn bound_consumed(
138        consumed: NonZeroUsize,
139        available: usize,
140    ) -> NonZeroUsize {
141        assert!(available > 0, "DecodeAction cannot consume empty input");
142        assert!(
143            consumed.get() <= available,
144            "DecodeAction consumed units must not exceed available input",
145        );
146        consumed
147    }
148}