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