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