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::{
11 encode_context::EncodeContext,
12 encode_plan::EncodePlan,
13};
14use crate::{
15 CapacityError,
16 Codec,
17};
18
19/// Policy hooks for [`crate::TranscodeEncodeEngine`].
20///
21/// Hooks own policy state, such as replacement or ignore behavior, but not the
22/// codec or engine cursor state. The engine passes the codec into hook methods
23/// when policy code needs codec metadata or one-value encode operations.
24///
25/// Implement this trait when a buffered encoder needs policy decisions around
26/// individual values while reusing the common engine loop. Examples include
27/// rejecting unsupported values with adapter-level context, consuming values
28/// without writing output, writing replacement units, or emitting final state
29/// in [`finish`](Self::finish).
30///
31/// The engine calls [`prepare_encode`](Self::prepare_encode) before each value
32/// is consumed. The returned [`EncodePlan`] states the required output capacity
33/// and may carry an action computed by the hook. Only after that capacity is
34/// available does the engine call [`write_encode`](Self::write_encode) with the
35/// same cursor context and the prepared plan. This split lets the engine stop
36/// with [`crate::TranscodeStatus::NeedOutput`]
37/// without consuming the next input value.
38///
39/// # Example
40///
41/// This hook writes each value with the wrapped codec and uses the codec's
42/// maximum width as the capacity plan.
43///
44/// ```rust
45/// use core::{
46/// convert::Infallible,
47/// num::NonZeroUsize,
48/// };
49/// use qubit_codec::{
50/// TranscodeEncodeHooks,
51/// Codec,
52/// CodecEncodeError,
53/// EncodeContext,
54/// EncodePlan,
55/// };
56///
57/// #[derive(Clone, Copy)]
58/// struct ByteCodec;
59///
60/// unsafe impl Codec for ByteCodec {
61/// type Value = u8;
62/// type Unit = u8;
63/// type DecodeError = Infallible;
64/// type EncodeError = Infallible;
65///
66/// fn min_units_per_value(&self) -> NonZeroUsize {
67/// NonZeroUsize::MIN
68/// }
69///
70/// fn max_units_per_value(&self) -> NonZeroUsize {
71/// NonZeroUsize::MIN
72/// }
73///
74/// unsafe fn decode(
75/// &mut self,
76/// input: &[u8],
77/// index: usize,
78/// ) -> Result<(u8, NonZeroUsize), Self::DecodeError> {
79/// Ok((input[index], NonZeroUsize::MIN))
80/// }
81///
82/// unsafe fn encode(
83/// &mut self,
84/// value: &u8,
85/// output: &mut [u8],
86/// index: usize,
87/// ) -> Result<NonZeroUsize, Self::EncodeError> {
88/// output[index] = *value;
89/// Ok(NonZeroUsize::MIN)
90/// }
91/// }
92///
93/// struct StrictHooks;
94///
95/// impl<C> TranscodeEncodeHooks<C> for StrictHooks
96/// where
97/// C: Codec,
98/// {
99/// type Error = CodecEncodeError<C::EncodeError>;
100/// type PlanAction = ();
101///
102/// fn prepare_encode(
103/// &mut self,
104/// codec: &mut C,
105/// _value: &C::Value,
106/// _input_index: usize,
107/// ) -> Result<EncodePlan<()>, Self::Error> {
108/// Ok(EncodePlan::new(codec.max_units_per_value().get(), ()))
109/// }
110///
111/// unsafe fn write_encode(
112/// &mut self,
113/// codec: &mut C,
114/// context: EncodeContext<'_, C::Value, C::Unit>,
115/// _plan: EncodePlan<()>,
116/// ) -> Result<usize, Self::Error> {
117/// unsafe {
118/// codec.encode(context.input_value, context.output, context.output_index)
119/// }
120/// .map(NonZeroUsize::get)
121/// .map_err(|error| CodecEncodeError::encode(error, context.input_index))
122/// }
123/// }
124/// ```
125///
126/// # Type Parameters
127///
128/// - `C`: Low-level codec owned by the engine.
129pub trait TranscodeEncodeHooks<C>
130where
131 C: Codec,
132{
133 /// Domain error type returned by the buffered encoder policy.
134 type Error;
135
136 /// Concrete action stored in [`EncodePlan::action`].
137 type PlanAction;
138
139 /// Returns the maximum output units needed for `input_len` values.
140 ///
141 /// # Parameters
142 ///
143 /// - `codec`: Low-level codec owned by the engine.
144 /// - `input_len`: Number of input values the caller plans to encode.
145 ///
146 /// # Returns
147 ///
148 /// Returns a conservative upper bound derived from the codec's
149 /// [`Codec::max_units_per_value`].
150 #[must_use = "capacity planning can fail on overflow"]
151 #[inline]
152 fn max_output_len(
153 &self,
154 codec: &C,
155 input_len: usize,
156 ) -> Result<usize, CapacityError> {
157 input_len
158 .checked_mul(codec.max_units_per_value().get())
159 .ok_or(CapacityError::OutputLengthOverflow)
160 }
161
162 /// Returns an upper bound for units emitted by finishing hook-owned state.
163 ///
164 /// `finish` never receives more input values. Implementations must only
165 /// report output derived from hook-owned state that remains after the
166 /// caller has supplied all input.
167 ///
168 /// # Parameters
169 ///
170 /// - `codec`: Low-level codec owned by the engine.
171 ///
172 /// # Returns
173 ///
174 /// Returns the finite final-output upper bound.
175 #[must_use]
176 #[inline(always)]
177 fn max_finish_output_len(&self, _codec: &C) -> usize {
178 0
179 }
180
181 /// Prepares an encoding plan for one input value.
182 ///
183 /// This method must not write output. It decides the output capacity bound
184 /// needed before [`write_encode`](Self::write_encode) may be called and
185 /// returns an implementation-specific plan action.
186 ///
187 /// # Parameters
188 ///
189 /// - `codec`: Low-level codec owned by the engine.
190 /// - `input_value`: Input value being encoded.
191 /// - `input_index`: Absolute input index of `value`.
192 ///
193 /// # Returns
194 ///
195 /// Returns the write plan for `value`.
196 ///
197 /// # Errors
198 ///
199 /// Returns `Self::Error` when this value cannot be encoded under the hook
200 /// policy.
201 fn prepare_encode(
202 &mut self,
203 codec: &mut C,
204 input_value: &C::Value,
205 input_index: usize,
206 ) -> Result<EncodePlan<Self::PlanAction>, Self::Error>;
207
208 /// Writes one input value according to a previously prepared plan.
209 ///
210 /// This method is called only after the engine has verified that
211 /// [`EncodePlan::max_output_units`] units from `plan` are writable from
212 /// [`EncodeContext::output_index`]. Implementations may rely on that
213 /// capacity guarantee and do not need to report output starvation here. If
214 /// a value needs more output than the plan declared, fix
215 /// [`prepare_encode`](Self::prepare_encode) to return a larger bound.
216 ///
217 /// # Parameters
218 ///
219 /// - `codec`: Low-level codec owned by the engine.
220 /// - `context`: Encode-write context containing the input value, input
221 /// index, output slice, and output cursor.
222 /// - `plan`: Prepared plan returned by
223 /// [`prepare_encode`](Self::prepare_encode).
224 ///
225 /// # Returns
226 ///
227 /// Returns the number of output units written.
228 ///
229 /// # Errors
230 ///
231 /// Returns `Self::Error` when writing fails under the hook policy. Output
232 /// capacity exhaustion is handled before this method is called and should
233 /// not be reported as a write error.
234 ///
235 /// # Safety
236 ///
237 /// The caller must guarantee that at least the corresponding
238 /// [`EncodePlan::max_output_units`] units are writable from
239 /// [`EncodeContext::output_index`] in [`EncodeContext::output`].
240 unsafe fn write_encode(
241 &mut self,
242 codec: &mut C,
243 context: EncodeContext<'_, C::Value, C::Unit>,
244 plan: EncodePlan<Self::PlanAction>,
245 ) -> Result<usize, Self::Error>;
246
247 /// Maps a codec-level reset error into this hook's public error type.
248 ///
249 /// # Parameters
250 ///
251 /// - `codec`: Low-level codec owned by the engine.
252 /// - `error`: Error returned by [`Codec::encode_reset`].
253 ///
254 /// # Returns
255 ///
256 /// Returns the hook-specific error.
257 ///
258 /// # Required Overrides
259 ///
260 /// The default implementation panics. Override this method whenever
261 /// [`Codec::encode_reset`] can return an error for `C`. Leaving the default
262 /// is appropriate only when reset is infallible or unreachable for the
263 /// codec and hook pairing.
264 #[inline]
265 fn map_encode_reset_error(
266 &mut self,
267 _codec: &mut C,
268 _error: C::EncodeError,
269 ) -> Self::Error {
270 panic!(
271 "TranscodeEncodeHooks::map_encode_reset_error must be implemented for fallible reset codecs"
272 )
273 }
274
275 /// Writes encoder reset output through the wrapped codec.
276 ///
277 /// The default implementation delegates to [`Codec::encode_reset`] and
278 /// maps errors through
279 /// [`map_encode_reset_error`](Self::map_encode_reset_error).
280 ///
281 /// # Parameters
282 ///
283 /// - `codec`: Low-level codec owned by the engine.
284 /// - `output`: Destination unit buffer.
285 /// - `output_index`: Absolute output index where reset output starts.
286 ///
287 /// # Returns
288 ///
289 /// Returns the number of reset units written.
290 ///
291 /// # Errors
292 ///
293 /// Returns hook-specific reset errors.
294 ///
295 /// # Safety
296 ///
297 /// The caller must guarantee that at least
298 /// [`Codec::max_encode_reset_units`] units are writable from
299 /// `output_index`.
300 #[inline]
301 unsafe fn write_encode_reset(
302 &mut self,
303 codec: &mut C,
304 output: &mut [C::Unit],
305 output_index: usize,
306 ) -> Result<usize, Self::Error> {
307 // SAFETY: Forwarded from this method's safety contract.
308 unsafe { codec.encode_reset(output, output_index) }
309 .map_err(|error| self.map_encode_reset_error(codec, error))
310 }
311
312 /// Finishes hook-owned state and writes any retained output units.
313 ///
314 /// The default implementation is a no-op for stateless encode hooks.
315 /// Stateful hooks may emit final units such as reset sequences, checksums,
316 /// or trailers. The caller must provide at least
317 /// [`TranscodeEncodeHooks::max_finish_output_len`] writable units from
318 /// `output_index`. Implementations must not write beyond that declared
319 /// final-output bound.
320 ///
321 /// # Parameters
322 ///
323 /// - `codec`: Low-level codec owned by the engine.
324 /// - `output`: Output unit slice visible to the hook.
325 /// - `output_index`: Absolute output unit index where writing starts.
326 ///
327 /// # Returns
328 ///
329 /// Returns the number of units written by finalization. This count must not
330 /// exceed [`TranscodeEncodeHooks::max_finish_output_len`].
331 ///
332 /// # Errors
333 ///
334 /// Returns `Self::Error` when hook-owned state cannot be finalized.
335 #[inline]
336 fn finish(
337 &mut self,
338 _codec: &mut C,
339 _output: &mut [C::Unit],
340 _output_index: usize,
341 ) -> Result<usize, Self::Error> {
342 Ok(0)
343 }
344
345 /// Resets hook-owned policy state.
346 ///
347 /// # Parameters
348 ///
349 /// - `codec`: Low-level codec owned by the engine.
350 #[inline(always)]
351 fn reset(&mut self, _codec: &mut C) {}
352}