qubit_codec/transcode/adapter/codec_transcode_converter.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//! Buffered converter adapter backed by two low-level codecs.
9
10use core::fmt;
11
12use super::{
13 CodecTranscodeDecodeHooks,
14 CodecTranscodeEncodeHooks,
15};
16use crate::{
17 CapacityError,
18 Codec,
19 CodecConvertError,
20 CodecDecodeError,
21 CodecEncodeError,
22 TranscodeConvertEngine,
23 TranscodeConvertEngineError,
24 TranscodeConverter,
25 TranscodeDecodeEngineError,
26 TranscodeEncodeEngineError,
27 TranscodeError,
28 TranscodeProgress,
29 Transcoder,
30};
31
32/// Strict codec-backed converter error type.
33type CodecTranscodeConvertError<D, E> =
34 CodecConvertError<<D as Codec>::DecodeError, <E as Codec>::EncodeError>;
35
36type CodecTranscodeConvertEngineError<D, E> = TranscodeConvertEngineError<
37 TranscodeDecodeEngineError<
38 <D as Codec>::DecodeError,
39 CodecDecodeError<<D as Codec>::DecodeError>,
40 >,
41 TranscodeEncodeEngineError<
42 <E as Codec>::EncodeError,
43 CodecEncodeError<<E as Codec>::EncodeError>,
44 >,
45>;
46
47/// Converts source units to target units through a decoded value by using
48/// codecs.
49///
50/// The converter decodes one source value with the decoder codec, then encodes
51/// that value with the encoder codec. If the current output buffer cannot hold
52/// the encoded value, the already decoded value is retained by the common
53/// converter engine and must be drained before more source input is consumed.
54/// Incomplete source tails are left in the caller-provided input slice; callers
55/// own input-buffer refill and EOF incomplete-tail policy.
56///
57/// Because finalization receives no source input, the source codec should have
58/// locally decidable decode boundaries for the default converter bridge. Source
59/// formats that require EOF-aware maximal-munch parsing or delayed boundary
60/// decisions should implement that source-side policy in a custom transcoder or
61/// facade before conversion.
62///
63/// # Type Parameters
64///
65/// - `D`: Low-level codec used to decode source units.
66/// - `E`: Low-level codec used to encode target units.
67pub struct CodecTranscodeConverter<D, E>
68where
69 D: Codec,
70 E: Codec<Value = D::Value>,
71{
72 /// Common buffered converter engine.
73 engine: TranscodeConvertEngine<
74 D,
75 E,
76 CodecTranscodeDecodeHooks,
77 CodecTranscodeEncodeHooks,
78 >,
79}
80
81impl<D, E> fmt::Debug for CodecTranscodeConverter<D, E>
82where
83 D: Codec,
84 E: Codec<Value = D::Value>,
85 TranscodeConvertEngine<
86 D,
87 E,
88 CodecTranscodeDecodeHooks,
89 CodecTranscodeEncodeHooks,
90 >: fmt::Debug,
91{
92 /// Formats the wrapped converter engine for debugging.
93 ///
94 /// # Parameters
95 ///
96 /// - `f`: Destination formatter.
97 ///
98 /// # Returns
99 ///
100 /// Returns `fmt::Result` from the formatter.
101 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
102 f.debug_struct("CodecTranscodeConverter")
103 .field("engine", &self.engine)
104 .finish()
105 }
106}
107
108impl<D, E> CodecTranscodeConverter<D, E>
109where
110 D: Codec,
111 E: Codec<Value = D::Value>,
112{
113 /// Creates a buffered converter backed by decoder and encoder codecs.
114 ///
115 /// # Parameters
116 ///
117 /// - `decoder`: Low-level codec used to decode source units.
118 /// - `encoder`: Low-level codec used to encode target units.
119 ///
120 /// # Returns
121 ///
122 /// Returns a buffered converter adapter for the supplied codecs.
123 #[inline(always)]
124 #[must_use]
125 pub fn new(decoder: D, encoder: E) -> Self {
126 Self {
127 engine: TranscodeConvertEngine::new(
128 decoder,
129 encoder,
130 CodecTranscodeDecodeHooks,
131 CodecTranscodeEncodeHooks,
132 ),
133 }
134 }
135
136 /// Returns an upper bound for target units produced from `input_len` units.
137 ///
138 /// This concrete adapter method is available even when `D::Value` does not
139 /// implement [`Default`].
140 ///
141 /// # Parameters
142 ///
143 /// - `input_len`: Source units the caller plans to convert.
144 ///
145 /// # Returns
146 ///
147 /// Returns a conservative upper bound for produced target units.
148 #[must_use = "capacity planning can fail on overflow"]
149 #[inline(always)]
150 pub fn max_output_len(
151 &self,
152 input_len: usize,
153 ) -> Result<usize, CapacityError> {
154 self.engine.max_output_len(input_len)
155 }
156
157 /// Returns the maximum target units emitted by finishing internal state.
158 ///
159 /// # Returns
160 ///
161 /// Returns a conservative upper bound for remaining converter-final output.
162 #[must_use = "capacity planning can fail on overflow"]
163 #[inline(always)]
164 pub fn max_finish_output_len(&self) -> Result<usize, CapacityError> {
165 self.engine.max_finish_output_len()
166 }
167
168 /// Returns the maximum target units emitted when resetting stream state.
169 #[must_use = "capacity planning can fail on overflow"]
170 #[inline(always)]
171 pub fn max_reset_output_len(&self) -> Result<usize, CapacityError> {
172 self.engine.max_reset_output_len()
173 }
174
175 /// Clears retained pending output and hook state and emits stream-start
176 /// encode output.
177 ///
178 /// `D::Value: Default` is required so the engine can allocate scratch
179 /// storage for any stream-start values the source decoder emits through
180 /// [`Codec::decode_reset`](crate::Codec::decode_reset) before they are
181 /// piped through the target encoder. Stateless decoders never reach the
182 /// allocating path; the bound is consulted only when
183 /// [`Codec::MAX_DECODE_RESET_VALUES`](crate::Codec::MAX_DECODE_RESET_VALUES)
184 /// is non-zero.
185 #[inline(always)]
186 pub fn reset(
187 &mut self,
188 output: &mut [E::Unit],
189 output_index: usize,
190 ) -> Result<usize, TranscodeError<CodecTranscodeConvertError<D, E>>>
191 where
192 D::Value: Default,
193 {
194 self.engine.reset(output, output_index).map_err(|error| {
195 error.map_domain(flatten_convert_engine_error::<D, E>)
196 })
197 }
198
199 /// Converts source units into target units.
200 ///
201 /// This is the main streaming operation and does not require `D::Value` to
202 /// implement [`Default`].
203 ///
204 /// # Parameters
205 ///
206 /// - `input`: Source unit slice.
207 /// - `input_index`: Absolute source index where conversion starts.
208 /// - `output`: Target unit slice.
209 /// - `output_index`: Absolute target index where writing starts.
210 ///
211 /// # Returns
212 ///
213 /// Returns conversion progress for consumed/produced counters and stop
214 /// reason.
215 ///
216 /// # Errors
217 ///
218 /// Returns converter error when source or target indices are invalid, or
219 /// when decoding/encoding fails under current policy.
220 #[inline(always)]
221 pub fn transcode(
222 &mut self,
223 input: &[D::Unit],
224 input_index: usize,
225 output: &mut [E::Unit],
226 output_index: usize,
227 ) -> Result<
228 TranscodeProgress,
229 TranscodeError<CodecTranscodeConvertError<D, E>>,
230 > {
231 self.engine
232 .transcode(input, input_index, output, output_index)
233 .map_err(|error| {
234 error.map_domain(flatten_convert_engine_error::<D, E>)
235 })
236 }
237
238 /// Finishes internally retained output after EOF.
239 ///
240 /// Finalization delegates to the reusable converter engine. It drains
241 /// retained pending output, encodes source-side decode flush values, and
242 /// then finishes target-side encode hook state.
243 ///
244 /// # Parameters
245 ///
246 /// - `output`: Target unit slice for finalization output.
247 /// - `output_index`: Absolute target output index where writing starts.
248 ///
249 /// # Returns
250 ///
251 /// Returns the number of target units written by finalization.
252 ///
253 /// # Errors
254 ///
255 /// Returns a finish error for pending output that cannot be finalized.
256 #[inline(always)]
257 pub fn finish(
258 &mut self,
259 output: &mut [E::Unit],
260 output_index: usize,
261 ) -> Result<usize, TranscodeError<CodecTranscodeConvertError<D, E>>>
262 where
263 D::Value: Default,
264 {
265 self.engine.finish(output, output_index).map_err(|error| {
266 error.map_domain(flatten_convert_engine_error::<D, E>)
267 })
268 }
269}
270
271impl<D, E> Transcoder<D::Unit, E::Unit> for CodecTranscodeConverter<D, E>
272where
273 D: Codec,
274 E: Codec<Value = D::Value>,
275 D::Value: Default,
276{
277 type Error = CodecConvertError<D::DecodeError, E::EncodeError>;
278
279 /// Returns an upper bound for target units produced from `input_len` units.
280 ///
281 /// # Parameters
282 ///
283 /// - `input_len`: Source units the caller plans to convert.
284 ///
285 /// # Returns
286 ///
287 /// Returns a conservative upper bound for produced target units.
288 #[inline(always)]
289 fn max_output_len(&self, input_len: usize) -> Result<usize, CapacityError> {
290 CodecTranscodeConverter::max_output_len(self, input_len)
291 }
292
293 /// Returns the maximum target units emitted by finishing internal state.
294 ///
295 /// # Returns
296 ///
297 /// Returns a conservative upper bound for remaining converter-final output.
298 #[inline(always)]
299 fn max_finish_output_len(&self) -> Result<usize, CapacityError> {
300 CodecTranscodeConverter::max_finish_output_len(self)
301 }
302
303 /// Returns the maximum target units emitted when resetting stream state.
304 #[inline(always)]
305 fn max_reset_output_len(&self) -> Result<usize, CapacityError> {
306 CodecTranscodeConverter::max_reset_output_len(self)
307 }
308
309 /// Clears retained pending output, resets component state, and emits
310 /// stream-start encode output.
311 #[inline(always)]
312 fn reset(
313 &mut self,
314 output: &mut [E::Unit],
315 output_index: usize,
316 ) -> Result<usize, TranscodeError<Self::Error>> {
317 CodecTranscodeConverter::reset(self, output, output_index)
318 }
319
320 /// Converts source units into target units.
321 ///
322 /// # Parameters
323 ///
324 /// - `input`: Source unit slice.
325 /// - `input_index`: Absolute source index where conversion starts.
326 /// - `output`: Target unit slice.
327 /// - `output_index`: Absolute target index where writing starts.
328 ///
329 /// # Returns
330 ///
331 /// Returns conversion progress for consumed/produced counters and stop
332 /// reason.
333 ///
334 /// # Errors
335 ///
336 /// Returns converter error when source or target indices are invalid, or
337 /// when decoding/encoding fails under current policy.
338 #[inline(always)]
339 fn transcode(
340 &mut self,
341 input: &[D::Unit],
342 input_index: usize,
343 output: &mut [E::Unit],
344 output_index: usize,
345 ) -> Result<TranscodeProgress, TranscodeError<Self::Error>> {
346 CodecTranscodeConverter::transcode(
347 self,
348 input,
349 input_index,
350 output,
351 output_index,
352 )
353 }
354
355 /// Finishes internally retained output after EOF.
356 ///
357 /// # Parameters
358 ///
359 /// - `output`: Target unit slice for finalization output.
360 /// - `output_index`: Absolute target output index where writing starts.
361 ///
362 /// # Returns
363 ///
364 /// Returns the number of target units written by finalization.
365 ///
366 /// # Errors
367 ///
368 /// Returns a finish error for pending output that cannot be finalized.
369 #[inline(always)]
370 fn finish(
371 &mut self,
372 output: &mut [E::Unit],
373 output_index: usize,
374 ) -> Result<usize, TranscodeError<Self::Error>> {
375 CodecTranscodeConverter::finish(self, output, output_index)
376 }
377}
378
379impl<D, E> TranscodeConverter<D::Unit, E::Unit>
380 for CodecTranscodeConverter<D, E>
381where
382 D: Codec,
383 E: Codec<Value = D::Value>,
384 D::Value: Default,
385{
386 // empty
387}
388
389impl<D, E> Default for CodecTranscodeConverter<D, E>
390where
391 D: Codec,
392 E: Codec<Value = D::Value>,
393 TranscodeConvertEngine<
394 D,
395 E,
396 CodecTranscodeDecodeHooks,
397 CodecTranscodeEncodeHooks,
398 >: Default,
399{
400 /// Creates a default codec-backed buffered converter.
401 ///
402 /// # Returns
403 ///
404 /// Returns a converter with default codecs and hooks.
405 #[inline(always)]
406 fn default() -> Self {
407 Self {
408 engine: TranscodeConvertEngine::default(),
409 }
410 }
411}
412
413#[inline(always)]
414fn flatten_convert_engine_error<D, E>(
415 error: CodecTranscodeConvertEngineError<D, E>,
416) -> CodecTranscodeConvertError<D, E>
417where
418 D: Codec,
419 E: Codec<Value = D::Value>,
420{
421 match error {
422 TranscodeConvertEngineError::Decode(error) => match error {
423 TranscodeDecodeEngineError::Codec(error)
424 | TranscodeDecodeEngineError::Hook(error) => {
425 CodecConvertError::decode(error)
426 }
427 },
428 TranscodeConvertEngineError::Encode(error) => match error {
429 TranscodeEncodeEngineError::Codec(error)
430 | TranscodeEncodeEngineError::Hook(error) => {
431 CodecConvertError::encode(error)
432 }
433 },
434 }
435}