Skip to main content

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