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 mut engine = TranscodeEncodeEngine::new(ByteCodec, StrictHooks);
99/// engine.reset(&mut [], 0).unwrap();
100/// let mut output = [0_u8; 1];
101/// let progress = engine.transcode(&[42], 0, &mut output, 0).unwrap();
102/// assert_eq!(progress.read(), 1);
103/// assert_eq!(progress.written(), 1);
104/// assert_eq!(output, [42]);
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]
143 fn max_transcode_output_len(&self, _codec: &C, input_len: usize) -> Result<usize, CapacityError> {
144 input_len
145 .checked_mul(C::MAX_ENCODE_UNITS_PER_VALUE)
146 .ok_or(CapacityError::OutputLengthOverflow)
147 }
148
149 /// Returns an upper bound for units emitted by finishing hook-owned state.
150 ///
151 /// `finish` never receives more input values. Implementations must report
152 /// the maximum output that could be derived from hook-owned state in any
153 /// reachable transient stream state, not only what happens to remain when
154 /// this method is called.
155 ///
156 /// The default implementation returns `0`. Override it when
157 /// [`finish_hooks`](Self::finish_hooks) can write trailers, checksums,
158 /// delayed replacements, or any other hook-owned final output. Do not
159 /// include [`Codec::MAX_ENCODE_FINISH_UNITS`]; the engine adds the codec
160 /// finish bound separately.
161 ///
162 /// # Parameters
163 ///
164 /// - `codec`: Low-level codec owned by the engine.
165 ///
166 /// # Returns
167 ///
168 /// Returns the finite, transient-state-independent final-output upper
169 /// bound.
170 #[inline]
171 #[must_use]
172 fn max_finish_output_len(&self, _codec: &C) -> usize {
173 0
174 }
175
176 /// Handles one unencodable input value.
177 ///
178 /// # Parameters
179 ///
180 /// - `codec`: Low-level codec owned by the engine.
181 /// - `context`: Encode context for the input value that
182 /// [`Codec::can_encode_value`] rejected.
183 ///
184 /// # Returns
185 ///
186 /// Returns the action selected by this hook policy.
187 ///
188 /// Replacement actions must contain a value that the same codec can encode.
189 /// The engine treats unencodable replacements as hook contract violations
190 /// and panics.
191 ///
192 /// Before invoking this method, the engine requires writable capacity for
193 /// [`Codec::MAX_ENCODE_UNITS_PER_VALUE`]. This conservative bound also
194 /// applies when the selected action is `Skip` or `Reject`; it may exceed
195 /// the replacement's actual encoded width. A capacity shortage does not
196 /// invoke the hook, so hook state is not changed by output backpressure.
197 ///
198 /// # Errors
199 ///
200 /// Returns [`crate::TranscodeEncodeError`] when the policy rejects the
201 /// value with a codec-domain error.
202 fn handle_unencodable_encode(
203 &mut self,
204 codec: &mut C,
205 context: &EncodeContext<'_, C::Value>,
206 ) -> Result<EncodeUnencodableAction<C::Value>, TranscodeEncodeErrorOf<C>>;
207
208 /// Runs hook-owned cleanup as part of stream reset.
209 ///
210 /// Called before [`Codec::encode_reset`] writes
211 /// its own reset output. Stateless hooks may use the default no-op.
212 ///
213 /// # Parameters
214 ///
215 /// - `codec`: Low-level codec owned by the engine.
216 #[inline]
217 fn reset_hooks(&mut self, _codec: &mut C) {}
218
219 /// Finishes hook-owned state and writes any retained output units.
220 ///
221 /// The default implementation is a no-op for stateless encode hooks.
222 /// Stateful hooks may emit final units such as reset sequences, checksums,
223 /// or trailers. The caller must provide at least
224 /// [`TranscodeEncodeHooks::max_finish_output_len`] writable units from
225 /// `output_index`. Implementations must not write beyond that declared
226 /// final-output bound.
227 ///
228 /// Called after [`Codec::encode_finish`] has
229 /// written its own finish output.
230 ///
231 /// # Parameters
232 ///
233 /// - `codec`: Low-level codec owned by the engine.
234 /// - `output`: Output unit slice visible to the hook.
235 /// - `output_index`: Absolute output unit index where writing starts.
236 ///
237 /// # Returns
238 ///
239 /// Returns the number of units written by finalization. This count must not
240 /// exceed [`TranscodeEncodeHooks::max_finish_output_len`].
241 ///
242 /// # Errors
243 ///
244 /// Returns a transcode encode error when hook-owned state cannot be
245 /// finalized.
246 #[inline]
247 fn finish_hooks(
248 &mut self,
249 _codec: &mut C,
250 _output: &mut [C::Unit],
251 _output_index: usize,
252 ) -> Result<usize, TranscodeEncodeErrorOf<C>> {
253 Ok(0)
254 }
255}