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::encode_context::EncodeContext;
11use crate::{
12    CapacityError,
13    Codec,
14    EncodeOutcome,
15};
16
17/// Policy hooks for [`crate::TranscodeEncodeEngine`].
18///
19/// Hooks own policy state, such as replacement or ignore behavior, but not the
20/// codec or engine cursor state. The engine passes the codec into hook methods
21/// when policy code needs codec metadata or one-value encode operations.
22///
23/// Implement this trait when a buffered encoder needs policy decisions around
24/// individual values while reusing the common engine loop. Examples include
25/// rejecting unsupported values with adapter-level context, consuming values
26/// without writing output, writing replacement units, resetting hook-owned
27/// state in [`reset_hooks`](Self::reset_hooks), or emitting final state in
28/// [`finish_hooks`](Self::finish_hooks).
29///
30/// The engine calls [`encode_value`](Self::encode_value) for the current input
31/// value. The hook either consumes that value and reports written output units,
32/// or returns [`EncodeOutcome::NeedOutput`] without consuming it. This lets
33/// the engine stop with [`crate::TranscodeStatus::NeedOutput`] and resume later
34/// at the same input value.
35///
36/// # Example
37///
38/// This hook writes each value with the wrapped codec and reports
39/// `NeedOutput` when the current output slice cannot fit the value.
40///
41/// ```rust
42/// use core::{
43///     convert::Infallible,
44///     num::NonZeroUsize,
45/// };
46/// use qubit_codec::{
47///     TranscodeEncodeHooks,
48///     Codec,
49///     DecodeFailure,
50///     CodecEncodeError,
51///     EncodeContext,
52///     EncodeOutcome,
53/// };
54///
55/// #[derive(Clone, Copy)]
56/// struct ByteCodec;
57///
58/// impl Codec for ByteCodec {
59///     type Value = u8;
60///     type Unit = u8;
61///     type DecodeError = Infallible;
62///     type EncodeError = Infallible;
63///
64///     const MIN_UNITS_PER_VALUE: NonZeroUsize = NonZeroUsize::MIN;
65///     const MAX_UNITS_PER_VALUE: NonZeroUsize = NonZeroUsize::MIN;
66///
67///     unsafe fn decode(
68///         &mut self,
69///         input: &[u8],
70///         index: usize,
71///     ) -> Result<(u8, NonZeroUsize), DecodeFailure<Self::DecodeError>> {
72///         Ok((input[index], NonZeroUsize::MIN))
73///     }
74///
75///     unsafe fn encode(
76///         &mut self,
77///         value: &u8,
78///         output: &mut [u8],
79///         index: usize,
80///     ) -> Result<NonZeroUsize, Self::EncodeError> {
81///         output[index] = *value;
82///         Ok(NonZeroUsize::MIN)
83///     }
84/// }
85///
86/// struct StrictHooks;
87///
88/// impl<C> TranscodeEncodeHooks<C> for StrictHooks
89/// where
90///     C: Codec,
91/// {
92///     type Error = CodecEncodeError<C::EncodeError>;
93///
94///     fn encode_value(
95///         &mut self,
96///         codec: &mut C,
97///         context: EncodeContext<'_, C::Value, C::Unit>,
98///     ) -> Result<EncodeOutcome, Self::Error> {
99///         let required = C::MAX_UNITS_PER_VALUE;
100///         if context.available_output() < required.get() {
101///             return Ok(EncodeOutcome::need_output(required));
102///         }
103///         let (value, input_index, output, output_index) = context.into_parts();
104///         let written = unsafe { codec.encode(value, output, output_index) }
105///             .map(NonZeroUsize::get)
106///             .map_err(|error| CodecEncodeError::encode(error, input_index))?;
107///         Ok(EncodeOutcome::consumed(written))
108///     }
109/// }
110/// ```
111///
112/// # Type Parameters
113///
114/// - `C`: Low-level codec owned by the engine.
115pub trait TranscodeEncodeHooks<C>
116where
117    C: Codec,
118{
119    /// Domain error type returned by the buffered encoder policy.
120    ///
121    /// Engine methods wrap this type in
122    /// [`crate::TranscodeEncodeEngineError::Hook`]. Codec lifecycle failures
123    /// are reported separately through
124    /// [`crate::TranscodeEncodeEngineError::Codec`].
125    type Error;
126
127    /// Returns the maximum output units needed for `input_len` values.
128    ///
129    /// # Parameters
130    ///
131    /// - `codec`: Low-level codec owned by the engine.
132    /// - `input_len`: Number of input values the caller plans to encode.
133    ///
134    /// # Returns
135    ///
136    /// Returns a conservative upper bound derived from the codec's
137    /// [`Codec::MAX_UNITS_PER_VALUE`].
138    #[inline]
139    #[must_use = "capacity planning can fail on overflow"]
140    fn max_output_len(
141        &self,
142        _codec: &C,
143        input_len: usize,
144    ) -> Result<usize, CapacityError> {
145        input_len
146            .checked_mul(C::MAX_UNITS_PER_VALUE.get())
147            .ok_or(CapacityError::OutputLengthOverflow)
148    }
149
150    /// Returns an upper bound for units emitted by finishing hook-owned state.
151    ///
152    /// `finish` never receives more input values. Implementations must only
153    /// report output derived from hook-owned state that remains after the
154    /// caller has supplied all input.
155    ///
156    /// # Parameters
157    ///
158    /// - `codec`: Low-level codec owned by the engine.
159    ///
160    /// # Returns
161    ///
162    /// Returns the finite final-output upper bound.
163    #[inline(always)]
164    #[must_use]
165    fn max_finish_output_len(&self, _codec: &C) -> usize {
166        0
167    }
168
169    /// Processes one input value at the current output cursor.
170    ///
171    /// # Parameters
172    ///
173    /// - `codec`: Low-level codec owned by the engine.
174    /// - `context`: Encode context containing the input value, input index,
175    ///   output slice, and output cursor.
176    ///
177    /// # Returns
178    ///
179    /// Returns whether the current input value was consumed or needs more
180    /// output capacity.
181    ///
182    /// # Errors
183    ///
184    /// Returns `Self::Error` when the policy rejects the value or the wrapped
185    /// codec fails while writing it.
186    fn encode_value(
187        &mut self,
188        codec: &mut C,
189        context: EncodeContext<'_, C::Value, C::Unit>,
190    ) -> Result<EncodeOutcome, Self::Error>;
191
192    /// Runs hook-owned cleanup as part of stream reset.
193    ///
194    /// Called before [`Codec::encode_reset`](crate::Codec::encode_reset) writes
195    /// its own reset output. Stateless hooks may use the default no-op.
196    ///
197    /// # Parameters
198    ///
199    /// - `codec`: Low-level codec owned by the engine.
200    #[inline(always)]
201    fn reset_hooks(&mut self, _codec: &mut C) {}
202
203    /// Finishes hook-owned state and writes any retained output units.
204    ///
205    /// The default implementation is a no-op for stateless encode hooks.
206    /// Stateful hooks may emit final units such as reset sequences, checksums,
207    /// or trailers. The caller must provide at least
208    /// [`TranscodeEncodeHooks::max_finish_output_len`] writable units from
209    /// `output_index`. Implementations must not write beyond that declared
210    /// final-output bound.
211    ///
212    /// Called after [`Codec::encode_flush`](crate::Codec::encode_flush) has
213    /// written its own flush output.
214    ///
215    /// # Parameters
216    ///
217    /// - `codec`: Low-level codec owned by the engine.
218    /// - `output`: Output unit slice visible to the hook.
219    /// - `output_index`: Absolute output unit index where writing starts.
220    ///
221    /// # Returns
222    ///
223    /// Returns the number of units written by finalization. This count must not
224    /// exceed [`TranscodeEncodeHooks::max_finish_output_len`].
225    ///
226    /// # Errors
227    ///
228    /// Returns `Self::Error` when hook-owned state cannot be finalized.
229    #[inline(always)]
230    fn finish_hooks(
231        &mut self,
232        _codec: &mut C,
233        _output: &mut [C::Unit],
234        _output_index: usize,
235    ) -> Result<usize, Self::Error> {
236        Ok(0)
237    }
238}