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}