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