Skip to main content

qubit_codec/transcode/engine/
transcode_decode_hooks.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//! Policy hooks used by buffered decoder engines.
9
10use super::super::{
11    decode_action::DecodeAction,
12    decode_context::DecodeContext,
13};
14use crate::{
15    CapacityError,
16    Codec,
17};
18
19/// Policy hooks for [`crate::TranscodeDecodeEngine`].
20///
21/// Hooks own policy state, such as malformed-input replacement behavior. The
22/// engine passes the codec into hook methods when policy code needs codec
23/// metadata.
24///
25/// Implement this trait when a buffered decoder needs policy decisions after
26/// the low-level codec reports an error. The engine handles input/output cursor
27/// bookkeeping, output-capacity checks, and successful one-value decodes; hooks
28/// decide whether a decode error means "need more input", "skip these units",
29/// "emit a replacement value", or "return an error".
30///
31/// The hook receives a [`DecodeContext`] with absolute input/output cursors, so
32/// errors can include useful positions without duplicating engine arithmetic.
33/// Stateful hooks may also use [`finish`](Self::finish) to emit final values
34/// after the caller has supplied all input and handled any incomplete tail.
35///
36/// # Example
37///
38/// This hook maps incomplete codec errors to `NeedInput`, replaces malformed
39/// units with `b'?'`, and otherwise lets the engine keep decoding.
40///
41/// ```rust
42/// use core::num::NonZeroUsize;
43/// use qubit_codec::{
44///     TranscodeDecodeHooks,
45///     Codec,
46///     CodecDecodeError,
47///     DecodeAction,
48///     DecodeContext,
49/// };
50///
51/// #[derive(Clone, Copy)]
52/// struct MyCodec;
53///
54/// #[derive(Clone, Copy, Debug, Eq, PartialEq)]
55/// enum MyDecodeError {
56///     Incomplete { required_total: usize },
57///     Malformed { consumed: NonZeroUsize },
58/// }
59///
60/// unsafe impl Codec for MyCodec {
61///     type Value = u8;
62///     type Unit = u8;
63///     type DecodeError = MyDecodeError;
64///     type EncodeError = core::convert::Infallible;
65///
66///     fn min_units_per_value(&self) -> NonZeroUsize {
67///         NonZeroUsize::MIN
68///     }
69///
70///     fn max_units_per_value(&self) -> NonZeroUsize {
71///         NonZeroUsize::MIN
72///     }
73///
74///     unsafe fn decode(
75///         &mut self,
76///         input: &[u8],
77///         index: usize,
78///     ) -> Result<(u8, NonZeroUsize), Self::DecodeError> {
79///         match input[index] {
80///             0xff => Err(MyDecodeError::Malformed {
81///                 consumed: NonZeroUsize::MIN,
82///             }),
83///             value => Ok((value, NonZeroUsize::MIN)),
84///         }
85///     }
86///
87///     unsafe fn encode(
88///         &mut self,
89///         value: &u8,
90///         output: &mut [u8],
91///         index: usize,
92///     ) -> Result<NonZeroUsize, Self::EncodeError> {
93///         output[index] = *value;
94///         Ok(NonZeroUsize::MIN)
95///     }
96/// }
97///
98/// struct ReplacementHooks;
99///
100/// impl TranscodeDecodeHooks<MyCodec> for ReplacementHooks {
101///     type Error = CodecDecodeError<MyDecodeError>;
102///
103///     fn handle_decode_error(
104///         &mut self,
105///         _codec: &mut MyCodec,
106///         error: MyDecodeError,
107///         _context: DecodeContext,
108///     ) -> Result<DecodeAction<u8>, Self::Error> {
109///         match error {
110///             MyDecodeError::Incomplete { required_total } => {
111///                 Ok(DecodeAction::NeedInput { required_total })
112///             }
113///             MyDecodeError::Malformed { consumed } => {
114///                 Ok(DecodeAction::Emit { value: b'?', consumed })
115///             }
116///         }
117///     }
118/// }
119/// ```
120///
121/// # Type Parameters
122///
123/// - `C`: Low-level codec owned by the engine.
124pub trait TranscodeDecodeHooks<C>
125where
126    C: Codec,
127{
128    /// Domain error type returned by the buffered decoder policy.
129    type Error;
130
131    /// Returns an upper bound for decoded values produced from `input_len`
132    /// units.
133    ///
134    /// # Parameters
135    ///
136    /// - `codec`: Low-level codec owned by the engine.
137    /// - `input_len`: Number of source units the caller plans to decode.
138    ///
139    /// # Returns
140    ///
141    /// Returns a conservative upper bound derived from
142    /// [`Codec::min_units_per_value`].
143    #[must_use = "capacity planning can fail on overflow"]
144    #[inline]
145    fn max_output_len(
146        &self,
147        codec: &C,
148        input_len: usize,
149    ) -> Result<usize, CapacityError> {
150        Ok(input_len / codec.min_units_per_value().get())
151    }
152
153    /// Returns an upper bound for values emitted by finishing hook-owned state.
154    ///
155    /// `finish` never receives more input. Implementations must only report
156    /// output derived from hook-owned state that remains after the caller has
157    /// handled any incomplete input tail.
158    ///
159    /// # Parameters
160    ///
161    /// - `codec`: Low-level codec owned by the engine.
162    ///
163    /// # Returns
164    ///
165    /// Returns the finite final-output upper bound.
166    #[must_use]
167    #[inline(always)]
168    fn max_finish_output_len(&self, _codec: &C) -> usize {
169        0
170    }
171
172    /// Handles a codec decode error during `transcode`.
173    ///
174    /// # Parameters
175    ///
176    /// - `codec`: Low-level codec owned by the engine.
177    /// - `error`: Error returned by the codec.
178    /// - `context`: Decode attempt context.
179    ///
180    /// # Returns
181    ///
182    /// Returns the action selected by this hook policy.
183    ///
184    /// Returned actions must be consistent with `context.available()`:
185    /// - `NeedInput.required_total` must be greater than `context.available()`;
186    /// - `Skip.consumed` and `Emit.consumed` must not exceed
187    ///   `context.available()`.
188    ///
189    /// The engine treats violations as hook bugs and panics.
190    ///
191    /// # Errors
192    ///
193    /// Returns `Self::Error` when the policy rejects the input.
194    fn handle_decode_error(
195        &mut self,
196        codec: &mut C,
197        error: C::DecodeError,
198        context: DecodeContext,
199    ) -> Result<DecodeAction<C::Value>, Self::Error>;
200
201    /// Maps a codec-level flush error into this hook's public error type.
202    ///
203    /// # Parameters
204    ///
205    /// - `codec`: Low-level codec owned by the engine.
206    /// - `error`: Error returned by [`Codec::decode_flush`].
207    ///
208    /// # Returns
209    ///
210    /// Returns the hook-specific error.
211    ///
212    /// # Required Overrides
213    ///
214    /// The default implementation panics. Override this method whenever
215    /// [`Codec::decode_flush`] can return an error for `C`. Leaving the default
216    /// is appropriate only when flush is infallible or unreachable for the
217    /// codec and hook pairing.
218    #[inline]
219    fn map_decode_flush_error(
220        &mut self,
221        _codec: &mut C,
222        _error: C::DecodeError,
223    ) -> Self::Error {
224        panic!(
225            "TranscodeDecodeHooks::map_decode_flush_error must be implemented for fallible flush codecs"
226        )
227    }
228
229    /// Finishes hook-owned state and writes any retained output.
230    ///
231    /// The default implementation is a no-op for stateless decode hooks.
232    /// Stateful hooks may emit final values such as checksums, reset markers,
233    /// or other trailer data. The caller must provide at least
234    /// [`TranscodeDecodeHooks::max_finish_output_len`] writable slots from
235    /// `output_index`. Implementations must not write beyond that declared
236    /// final-output bound.
237    ///
238    /// # Parameters
239    ///
240    /// - `codec`: Low-level codec owned by the engine.
241    /// - `output`: Output value slice visible to the hook.
242    /// - `output_index`: Absolute output value index where writing starts.
243    ///
244    /// # Returns
245    ///
246    /// Returns the number of values written by finalization. This count must
247    /// not exceed [`TranscodeDecodeHooks::max_finish_output_len`].
248    ///
249    /// # Errors
250    ///
251    /// Returns `Self::Error` when hook-owned state cannot be finalized.
252    #[inline]
253    fn finish(
254        &mut self,
255        _codec: &mut C,
256        _output: &mut [C::Value],
257        _output_index: usize,
258    ) -> Result<usize, Self::Error> {
259        Ok(0)
260    }
261
262    /// Resets hook-owned policy state.
263    ///
264    /// # Parameters
265    ///
266    /// - `codec`: Low-level codec owned by the engine.
267    ///
268    /// # Errors
269    ///
270    /// Returns `Self::Error` when hook-owned state cannot be reset.
271    #[inline]
272    fn reset(&mut self, _codec: &mut C) -> Result<(), Self::Error> {
273        Ok(())
274    }
275}