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}