Skip to main content

qubit_codec/buffered/
decode_action.rs

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