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::{
11    encode_context::EncodeContext,
12    encode_unencodable_action::EncodeUnencodableAction,
13};
14use crate::{
15    CapacityError,
16    Codec,
17    TranscodeEncodeErrorOf,
18};
19
20/// Policy hooks for [`crate::engine::TranscodeEncodeEngine`].
21///
22/// Hooks own policy state, such as replacement or ignore behavior, but not the
23/// codec or engine cursor state. The engine owns the normal one-value encode
24/// operation and calls hooks only when policy decisions are needed.
25///
26/// Implement this trait when a buffered encoder needs policy decisions around
27/// individual values while reusing the common engine loop. Examples include
28/// rejecting unsupported values with adapter-level context, skipping
29/// unsupported values, replacing them with encodable values, resetting
30/// hook-owned state in [`reset_hooks`](Self::reset_hooks), or emitting final
31/// state in [`finish_hooks`](Self::finish_hooks).
32///
33/// The engine calls
34/// [`handle_unencodable_encode`](Self::handle_unencodable_encode) only after
35/// [`Codec::can_encode_value`] returns `false` for the current input
36/// value. Encodable values are encoded directly by the engine with
37/// [`Codec::encode_len`] and [`Codec::encode`].
38///
39/// # Example
40///
41/// This hook reports unsupported values as adapter-level errors.
42///
43/// ```rust
44/// use core::{
45///     convert::Infallible,
46///     num::NonZeroUsize,
47/// };
48/// use qubit_codec::{
49///     Codec,
50///     TranscodeEncodeErrorOf,
51///     DecodeFailure,
52/// };
53/// use qubit_codec::engine::{
54///     EncodeContext,
55///     EncodeUnencodableAction,
56///     TranscodeEncodeHooks,
57/// };
58///
59/// #[derive(Clone, Copy)]
60/// struct ByteCodec;
61///
62/// impl Codec for ByteCodec {
63///     type Value = u8;
64///     type Unit = u8;
65///     type DecodeError = Infallible;
66///     type EncodeError = Infallible;
67///
68///     const MIN_UNITS_PER_VALUE: usize = 1;
69///     const MAX_ENCODE_UNITS_PER_VALUE: usize = 1;
70///     const MAX_DECODE_UNITS_PER_VALUE: usize = 1;
71///
72///     unsafe fn decode(
73///         &mut self,
74///         input: &[u8],
75///         index: usize,
76///     ) -> Result<(u8, NonZeroUsize), DecodeFailure<Self::DecodeError>> {
77///         Ok((input[index], NonZeroUsize::MIN))
78///     }
79///
80///     unsafe fn encode(
81///         &mut self,
82///         value: &u8,
83///         output: &mut [u8],
84///         index: usize,
85///     ) -> Result<usize, Self::EncodeError> {
86///         output[index] = *value;
87///         Ok(1)
88///     }
89/// }
90///
91/// struct StrictHooks;
92///
93/// impl<C> TranscodeEncodeHooks<C> for StrictHooks
94/// where
95///     C: Codec,
96/// {
97///     fn handle_unencodable_encode(
98///         &mut self,
99///         _codec: &mut C,
100///         _context: &EncodeContext<'_, C::Value>,
101///     ) -> Result<EncodeUnencodableAction<C::Value>, TranscodeEncodeErrorOf<C>> {
102///         Ok(EncodeUnencodableAction::Reject)
103///     }
104/// }
105/// ```
106///
107/// # Type Parameters
108///
109/// - `C`: Low-level codec owned by the engine.
110pub trait TranscodeEncodeHooks<C>
111where
112    C: Codec,
113{
114    /// Returns the maximum output units needed for `input_len` values.
115    ///
116    /// This bound covers only the streaming encode phase driven by
117    /// [`crate::engine::TranscodeEncodeEngine::transcode`]. It must not include
118    /// codec reset output, codec finish output, or hook finish output.
119    /// It may depend on immutable hook and codec configuration, but it must
120    /// cover every reachable transient hook and codec state, including the
121    /// maximum hook-owned pending output that may be drained during transcode.
122    ///
123    /// The default implementation multiplies `input_len` by
124    /// [`Codec::MAX_ENCODE_UNITS_PER_VALUE`]. This bound is valid for direct
125    /// encoding, skipped unencodable values, and single-value replacements.
126    /// Override it only when hook-owned pending output can be drained
127    /// during `transcode`.
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 for streaming output.
137    ///
138    /// # Errors
139    ///
140    /// Returns [`CapacityError::OutputLengthOverflow`] when the bound cannot
141    /// be represented as `usize`.
142    #[inline(always)]
143    #[must_use = "capacity planning can fail on overflow"]
144    fn max_transcode_output_len(
145        &self,
146        _codec: &C,
147        input_len: usize,
148    ) -> Result<usize, CapacityError> {
149        input_len
150            .checked_mul(C::MAX_ENCODE_UNITS_PER_VALUE)
151            .ok_or(CapacityError::OutputLengthOverflow)
152    }
153
154    /// Returns an upper bound for units emitted by finishing hook-owned state.
155    ///
156    /// `finish` never receives more input values. Implementations must report
157    /// the maximum output that could be derived from hook-owned state in any
158    /// reachable transient stream state, not only what happens to remain when
159    /// this method is called.
160    ///
161    /// The default implementation returns `0`. Override it when
162    /// [`finish_hooks`](Self::finish_hooks) can write trailers, checksums,
163    /// delayed replacements, or any other hook-owned final output. Do not
164    /// include [`Codec::MAX_ENCODE_FINISH_UNITS`]; the engine adds the codec
165    /// finish bound separately.
166    ///
167    /// # Parameters
168    ///
169    /// - `codec`: Low-level codec owned by the engine.
170    ///
171    /// # Returns
172    ///
173    /// Returns the finite, transient-state-independent final-output upper
174    /// bound.
175    #[inline(always)]
176    #[must_use]
177    fn max_finish_output_len(&self, _codec: &C) -> usize {
178        0
179    }
180
181    /// Handles one unencodable input value.
182    ///
183    /// # Parameters
184    ///
185    /// - `codec`: Low-level codec owned by the engine.
186    /// - `context`: Encode context for the input value that
187    ///   [`Codec::can_encode_value`] rejected.
188    ///
189    /// # Returns
190    ///
191    /// Returns the action selected by this hook policy.
192    ///
193    /// Replacement actions must contain a value that the same codec can encode.
194    /// The engine treats unencodable replacements as hook contract violations
195    /// and panics.
196    ///
197    /// # Errors
198    ///
199    /// Returns [`crate::TranscodeEncodeError`] when the policy rejects the
200    /// value with a codec-domain error.
201    fn handle_unencodable_encode(
202        &mut self,
203        codec: &mut C,
204        context: &EncodeContext<'_, C::Value>,
205    ) -> Result<EncodeUnencodableAction<C::Value>, TranscodeEncodeErrorOf<C>>;
206
207    /// Runs hook-owned cleanup as part of stream reset.
208    ///
209    /// Called before [`Codec::encode_reset`] writes
210    /// its own reset output. Stateless hooks may use the default no-op.
211    ///
212    /// # Parameters
213    ///
214    /// - `codec`: Low-level codec owned by the engine.
215    #[inline(always)]
216    fn reset_hooks(&mut self, _codec: &mut C) {}
217
218    /// Finishes hook-owned state and writes any retained output units.
219    ///
220    /// The default implementation is a no-op for stateless encode hooks.
221    /// Stateful hooks may emit final units such as reset sequences, checksums,
222    /// or trailers. The caller must provide at least
223    /// [`TranscodeEncodeHooks::max_finish_output_len`] writable units from
224    /// `output_index`. Implementations must not write beyond that declared
225    /// final-output bound.
226    ///
227    /// Called after [`Codec::encode_finish`] has
228    /// written its own finish output.
229    ///
230    /// # Parameters
231    ///
232    /// - `codec`: Low-level codec owned by the engine.
233    /// - `output`: Output unit slice visible to the hook.
234    /// - `output_index`: Absolute output unit index where writing starts.
235    ///
236    /// # Returns
237    ///
238    /// Returns the number of units written by finalization. This count must not
239    /// exceed [`TranscodeEncodeHooks::max_finish_output_len`].
240    ///
241    /// # Errors
242    ///
243    /// Returns a transcode encode error when hook-owned state cannot be
244    /// finalized.
245    #[inline(always)]
246    fn finish_hooks(
247        &mut self,
248        _codec: &mut C,
249        _output: &mut [C::Unit],
250        _output_index: usize,
251    ) -> Result<usize, TranscodeEncodeErrorOf<C>> {
252        Ok(0)
253    }
254}