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