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`](crate::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`](crate::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}