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