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