qubit_codec/buffered/buffered_encode_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 encoder engines.
11
12use super::{
13 encode_context::EncodeContext,
14 encode_plan::EncodePlan,
15};
16use crate::{
17 CapacityError,
18 Codec,
19};
20
21/// Policy hooks for [`crate::BufferedEncodeEngine`].
22///
23/// Hooks own policy state, such as replacement or ignore behavior, but not the
24/// codec or engine cursor state. The engine passes the codec into hook methods
25/// when policy code needs codec metadata or one-value encode operations.
26///
27/// Implement this trait when a buffered encoder needs policy decisions around
28/// individual values while reusing the common engine loop. Examples include
29/// rejecting unsupported values with adapter-level context, consuming values
30/// without writing output, writing replacement units, or emitting final state in
31/// [`finish`](Self::finish).
32///
33/// The engine calls [`prepare_encode`](Self::prepare_encode) before each value
34/// is consumed. The returned [`EncodePlan`] states the required output capacity
35/// and may carry an action computed by the hook. Only after that capacity is
36/// available does the engine call [`write_encode`](Self::write_encode) with the
37/// same cursor context and the prepared plan. This split lets the engine stop
38/// with [`crate::TranscodeStatus::NeedOutput`]
39/// without consuming the next input value.
40///
41/// # Example
42///
43/// This hook writes each value with the wrapped codec and uses the codec's
44/// maximum width as the capacity plan.
45///
46/// ```rust
47/// use core::{
48/// convert::Infallible,
49/// num::NonZeroUsize,
50/// };
51/// use qubit_codec::{
52/// BufferedEncodeHooks,
53/// Codec,
54/// CodecEncodeError,
55/// EncodeContext,
56/// EncodePlan,
57/// };
58///
59/// #[derive(Clone, Copy)]
60/// struct ByteCodec;
61///
62/// unsafe impl Codec for ByteCodec {
63/// type Value = u8;
64/// type Unit = u8;
65/// type DecodeError = Infallible;
66/// type EncodeError = 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/// Ok((input[index], NonZeroUsize::MIN))
82/// }
83///
84/// unsafe fn encode_unchecked(
85/// &self,
86/// value: &u8,
87/// output: &mut [u8],
88/// index: usize,
89/// ) -> Result<usize, Self::EncodeError> {
90/// output[index] = *value;
91/// Ok(1)
92/// }
93/// }
94///
95/// struct StrictHooks;
96///
97/// impl<C> BufferedEncodeHooks<C> for StrictHooks
98/// where
99/// C: Codec,
100/// {
101/// type Error = CodecEncodeError<C::EncodeError>;
102/// type PlanAction = ();
103///
104/// fn prepare_encode(
105/// &mut self,
106/// codec: &C,
107/// _value: &C::Value,
108/// _input_index: usize,
109/// ) -> Result<EncodePlan<()>, Self::Error> {
110/// Ok(EncodePlan::new(codec.max_units_per_value().get(), ()))
111/// }
112///
113/// unsafe fn write_encode(
114/// &mut self,
115/// codec: &C,
116/// context: EncodeContext<'_, C::Value, C::Unit>,
117/// _plan: EncodePlan<()>,
118/// ) -> Result<usize, Self::Error> {
119/// unsafe {
120/// codec.encode_unchecked(context.input_value, context.output, context.output_index)
121/// }
122/// .map_err(|error| CodecEncodeError::encode(error, context.input_index))
123/// }
124///
125/// fn invalid_input_index(
126/// &mut self,
127/// _codec: &C,
128/// index: usize,
129/// input_len: usize,
130/// ) -> Self::Error {
131/// CodecEncodeError::invalid_input_index(index, input_len)
132/// }
133///
134/// fn invalid_output_index(
135/// &mut self,
136/// _codec: &C,
137/// index: usize,
138/// output_len: usize,
139/// ) -> Self::Error {
140/// CodecEncodeError::invalid_output_index(index, output_len)
141/// }
142/// }
143/// ```
144///
145/// # Type Parameters
146///
147/// - `C`: Low-level codec owned by the engine.
148pub trait BufferedEncodeHooks<C>
149where
150 C: Codec,
151{
152 /// Error type returned by the buffered encoder.
153 type Error;
154
155 /// Concrete action stored in [`EncodePlan::action`].
156 type PlanAction;
157
158 /// Returns the maximum output units needed for `input_len` values.
159 ///
160 /// # Parameters
161 ///
162 /// - `codec`: Low-level codec owned by the engine.
163 /// - `input_len`: Number of input values the caller plans to encode.
164 ///
165 /// # Returns
166 ///
167 /// Returns a conservative upper bound derived from the codec's
168 /// [`Codec::max_units_per_value`].
169 #[must_use = "capacity planning can fail on overflow"]
170 #[inline]
171 fn max_output_len(&self, codec: &C, input_len: usize) -> Result<usize, CapacityError> {
172 input_len
173 .checked_mul(codec.max_units_per_value().get())
174 .ok_or(CapacityError::OutputLengthOverflow)
175 }
176
177 /// Returns an upper bound for units emitted by finishing hook-owned state.
178 ///
179 /// `finish` never receives more input values. Implementations must only
180 /// report output derived from hook-owned state that remains after the caller
181 /// has supplied all input.
182 ///
183 /// # Parameters
184 ///
185 /// - `codec`: Low-level codec owned by the engine.
186 ///
187 /// # Returns
188 ///
189 /// Returns the finite final-output upper bound.
190 #[must_use]
191 #[inline(always)]
192 fn max_finish_output_len(&self, _codec: &C) -> usize {
193 0
194 }
195
196 /// Prepares an encoding plan for one input value.
197 ///
198 /// This method must not write output. It decides the output capacity bound
199 /// needed before [`write_encode`](Self::write_encode) may be called and
200 /// returns an implementation-specific plan action.
201 ///
202 /// # Parameters
203 ///
204 /// - `codec`: Low-level codec owned by the engine.
205 /// - `input_value`: Input value being encoded.
206 /// - `input_index`: Absolute input index of `value`.
207 ///
208 /// # Returns
209 ///
210 /// Returns the write plan for `value`.
211 ///
212 /// # Errors
213 ///
214 /// Returns `Self::Error` when this value cannot be encoded under the hook
215 /// policy.
216 fn prepare_encode(
217 &mut self,
218 codec: &C,
219 input_value: &C::Value,
220 input_index: usize,
221 ) -> Result<EncodePlan<Self::PlanAction>, Self::Error>;
222
223 /// Writes one input value according to a previously prepared plan.
224 ///
225 /// This method is called only after the engine has verified that
226 /// [`EncodePlan::max_output_units`] units from `plan` are writable from
227 /// [`EncodeContext::output_index`]. Implementations may rely on that
228 /// capacity guarantee and do not need to report output starvation here. If
229 /// a value needs more output than the plan declared, fix
230 /// [`prepare_encode`](Self::prepare_encode) to return a larger bound.
231 ///
232 /// # Parameters
233 ///
234 /// - `codec`: Low-level codec owned by the engine.
235 /// - `context`: Encode-write context containing the input value, input
236 /// index, output slice, and output cursor.
237 /// - `plan`: Prepared plan returned by [`prepare_encode`](Self::prepare_encode).
238 ///
239 /// # Returns
240 ///
241 /// Returns the number of output units written.
242 ///
243 /// # Errors
244 ///
245 /// Returns `Self::Error` when writing fails under the hook policy. Output
246 /// capacity exhaustion is handled before this method is called and should
247 /// not be reported as a write error.
248 ///
249 /// # Safety
250 ///
251 /// The caller must guarantee that at least the corresponding
252 /// [`EncodePlan::max_output_units`] units are writable from
253 /// [`EncodeContext::output_index`] in [`EncodeContext::output`].
254 unsafe fn write_encode(
255 &mut self,
256 codec: &C,
257 context: EncodeContext<'_, C::Value, C::Unit>,
258 plan: EncodePlan<Self::PlanAction>,
259 ) -> Result<usize, Self::Error>;
260
261 /// Builds an error for a caller-supplied input index outside the input slice.
262 ///
263 /// The engine calls this hook before it reads input. Keeping this
264 /// construction in the hook lets codec-backed adapters preserve their own
265 /// concrete error type without a separate public factory trait.
266 ///
267 /// # Parameters
268 ///
269 /// - `codec`: Low-level codec owned by the engine.
270 /// - `index`: Invalid absolute input index supplied by the caller.
271 /// - `input_len`: Length of the input slice.
272 ///
273 /// # Returns
274 ///
275 /// Returns the hook-specific invalid-input-index error.
276 fn invalid_input_index(&mut self, codec: &C, index: usize, input_len: usize) -> Self::Error;
277
278 /// Builds an error for a caller-supplied output index outside the output slice.
279 ///
280 /// The engine calls this hook before it writes output. Keeping this
281 /// construction in the hook lets codec-backed adapters preserve their own
282 /// concrete error type without a separate public factory trait.
283 ///
284 /// # Parameters
285 ///
286 /// - `codec`: Low-level codec owned by the engine.
287 /// - `index`: Invalid absolute output index supplied by the caller.
288 /// - `output_len`: Length of the output slice.
289 ///
290 /// # Returns
291 ///
292 /// Returns the hook-specific invalid-output-index error.
293 fn invalid_output_index(&mut self, codec: &C, index: usize, output_len: usize) -> Self::Error;
294
295 /// Finishes hook-owned state and writes any retained output units.
296 ///
297 /// The default implementation is a no-op for stateless encode hooks.
298 /// Stateful hooks may emit final units such as reset sequences, checksums, or
299 /// trailers. The caller must provide at least
300 /// [`BufferedEncodeHooks::max_finish_output_len`] writable units from
301 /// `output_index`. Engines may pass an output slice whose upper bound is
302 /// capped at `output_index + max_finish_output_len`, so implementations must
303 /// not write beyond that declared final-output bound.
304 ///
305 /// # Parameters
306 ///
307 /// - `codec`: Low-level codec owned by the engine.
308 /// - `output`: Output unit slice visible to the hook.
309 /// - `output_index`: Absolute output unit index where writing starts.
310 ///
311 /// # Returns
312 ///
313 /// Returns the number of units written by finalization. This count must not
314 /// exceed [`BufferedEncodeHooks::max_finish_output_len`].
315 ///
316 /// # Errors
317 ///
318 /// Returns `Self::Error` when hook-owned state cannot be finalized.
319 #[inline]
320 fn finish(&mut self, _codec: &C, _output: &mut [C::Unit], _output_index: usize) -> Result<usize, Self::Error> {
321 Ok(0)
322 }
323
324 /// Resets hook-owned policy state.
325 ///
326 /// # Parameters
327 ///
328 /// - `codec`: Low-level codec owned by the engine.
329 #[inline(always)]
330 fn reset(&mut self, _codec: &C) {}
331}