Skip to main content

qubit_codec/transcode/
transcoder.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// =============================================================================
8use super::{
9    capacity_error::CapacityError,
10    transcode_error::TranscodeError,
11    transcode_progress::TranscodeProgress,
12};
13
14/// Converts one logical stream of input units into one logical stream of output
15/// units.
16///
17/// `transcode` is the main streaming API. It transforms a provided input
18/// segment and writes as much output as available buffer space allows.
19///
20/// A transcoder instance has a simple lifecycle:
21///
22/// 1. A newly created or reset instance is ready for a new logical stream.
23/// 2. Call [`Transcoder::transcode`] zero or more times while input is
24///    available.
25/// 3. Preserve any tail reported by [`crate::TranscodeStatus::NeedInput`] in
26///    the caller-owned input buffer.
27/// 4. Call [`Transcoder::finish`] after the caller knows no more input remains
28///    and has handled any incomplete tail. Size this final output with
29///    [`Transcoder::max_finish_output_len`].
30/// 5. After [`Transcoder::finish`] succeeds, call [`Transcoder::reset`] with a
31///    buffer sized by [`Transcoder::max_reset_output_len`] before starting
32///    another logical stream with the same instance.
33///
34/// The method is suitable for:
35/// - pull-style consumers that call conversion repeatedly as buffers arrive;
36/// - bounded output sinks that use `NeedOutput` progress during `transcode`;
37/// - stateless and stateful codecs that all return progress-oriented stopping
38///   reasons.
39///
40/// `finish` finalizes retained state only; it does not receive source input and
41/// does not reinterpret a tail previously reported by `NeedInput`. For
42/// codec-backed decoders, this means the underlying codec should be able to
43/// decide each value boundary from the visible prefix plus its own state. If a
44/// format needs EOF-aware maximal-munch parsing or must delay whether a prefix
45/// is complete until the next chunk or EOF, implement that policy in a custom
46/// `Transcoder` or a value-level facade.
47///
48/// `Transcoder` is intentionally independent from any charset
49/// semantics:
50///
51/// - Use `Transcoder` directly for custom, policy-free unit transforms.
52/// - Use `Transcoder` when you want to own malformed/unmappable decisions at
53///   the call site.
54///
55/// # Example: streaming byte-to-word decoder
56///
57/// ```rust
58/// use core::num::NonZeroUsize;
59/// use qubit_codec::{
60///     CodecDecodeError,
61///     TranscodeError,
62///     TranscodeProgress,
63///     TranscodeStatus,
64///     Transcoder,
65/// };
66///
67/// #[derive(Default)]
68/// struct U16BeBytesDecoder;
69///
70/// impl Transcoder<u8, u16> for U16BeBytesDecoder {
71///     type Error = CodecDecodeError<core::convert::Infallible>;
72///
73///     fn max_output_len(&self, input_len: usize) -> Result<usize, qubit_codec::CapacityError> {
74///         Ok(input_len / 2)
75///     }
76///
77///     fn reset(
78///         &mut self,
79///         output: &mut [u16],
80///         output_index: usize,
81///     ) -> Result<usize, TranscodeError<Self::Error>> {
82///         TranscodeError::ensure_output_index(output.len(), output_index)?;
83///         Ok(0)
84///     }
85///
86///     fn transcode(
87///         &mut self,
88///         input: &[u8],
89///         input_index: usize,
90///         output: &mut [u16],
91///         output_index: usize,
92///     ) -> Result<TranscodeProgress, TranscodeError<Self::Error>> {
93///         TranscodeError::ensure_transcode_indices(
94///             input.len(),
95///             input_index,
96///             output.len(),
97///             output_index,
98///         )?;
99///
100///         let mut read = 0;
101///         let mut written = 0;
102///         while input_index + read + 1 < input.len() {
103///             if output_index + written == output.len() {
104///                 let status = TranscodeStatus::NeedOutput {
105///                     output_index: output_index + written,
106///                     required: NonZeroUsize::MIN,
107///                     available: 0,
108///                 };
109///                 return Ok(TranscodeProgress::new(status, read, written));
110///             }
111///             let high = input[input_index + read] as u16;
112///             let low = input[input_index + read + 1] as u16;
113///             output[output_index + written] = (high << 8) | low;
114///             read += 2;
115///             written += 1;
116///         }
117///         if input_index + read == input.len() {
118///             Ok(TranscodeProgress::complete(read, written))
119///         } else {
120///             let available = input.len() - (input_index + read);
121///             let status = TranscodeStatus::NeedInput {
122///                 input_index: input_index + read,
123///                 required: qubit_io::nz!(2),
124///                 available,
125///             };
126///             Ok(TranscodeProgress::new(status, read, written))
127///         }
128///     }
129///
130///     fn finish(
131///         &mut self,
132///         output: &mut [u16],
133///         output_index: usize,
134///     ) -> Result<usize, TranscodeError<Self::Error>> {
135///         TranscodeError::ensure_output_index(output.len(), output_index)?;
136///         Ok(0)
137///     }
138/// }
139///
140/// let mut transcoder = U16BeBytesDecoder;
141/// let mut output = [0_u16; 1];
142/// let progress = transcoder
143///     .transcode(&[0x12, 0x34, 0xab, 0xcd], 0, &mut output, 0)
144///     .expect("decoding cannot fail");
145/// assert_eq!(TranscodeStatus::NeedOutput {
146///     output_index: 1,
147///     required: NonZeroUsize::MIN,
148///     available: 0,
149/// }, progress.status());
150/// assert_eq!(2, progress.read());
151/// assert_eq!(1, progress.written());
152/// assert_eq!([0x1234], output);
153///
154/// let mut output = [0_u16; 2];
155/// let progress = transcoder
156///     .transcode(&[0x12, 0x34, 0xab], 0, &mut output, 0)
157///     .expect("decoding cannot fail");
158/// assert_eq!(TranscodeStatus::NeedInput {
159///     input_index: 2,
160///     required: qubit_io::nz!(2),
161///     available: 1,
162/// }, progress.status());
163/// assert_eq!(2, progress.read());
164/// assert_eq!(1, progress.written());
165/// assert_eq!([0x1234, 0], output);
166///
167/// assert!(matches!(
168///     transcoder.transcode(&[0x12], 2, &mut output, 0),
169///     Err(TranscodeError::InvalidInputIndex { .. }),
170/// ));
171/// assert!(matches!(
172///     transcoder.transcode(&[0x12], 0, &mut output, 3),
173///     Err(TranscodeError::InvalidOutputIndex { .. }),
174/// ));
175/// ```
176///
177/// The trait is intentionally independent from charset concepts. Implementors
178/// use `input_index` and `output_index` as absolute positions in the supplied
179/// slices. Returned progress counters are relative counts from those positions.
180/// For raw codecs this gives a compact API; higher-level workflows can wrap
181/// this trait with their own semantic policies.
182///
183/// # Type Parameters
184///
185/// - `Input`: Input unit type accepted by this transcoder.
186/// - `Output`: Output unit type produced by this transcoder.
187pub trait Transcoder<Input, Output> {
188    /// Domain error reported by semantic conversion failures.
189    type Error;
190
191    /// Returns an upper bound for output units emitted when resetting stream
192    /// state.
193    ///
194    /// Stateful encoders may need a stream-start sequence, such as a byte
195    /// order mark, before the first encoded value. Callers use this bound to
196    /// size the output buffer passed to [`Transcoder::reset`].
197    ///
198    /// # Returns
199    ///
200    /// Returns `Ok(bound)` when the upper bound can be represented as `usize`.
201    /// Returns [`CapacityError::OutputLengthOverflow`] when capacity arithmetic
202    /// overflows. Stateless transcoders default to `Ok(0)`.
203    #[must_use = "capacity planning can fail on overflow"]
204    #[inline(always)]
205    fn max_reset_output_len(&self) -> Result<usize, CapacityError> {
206        Ok(0)
207    }
208
209    /// Returns an upper bound for output units produced from `input_len` units.
210    ///
211    /// For stateful transcoders, this bound is evaluated against the current
212    /// instance state and must include any already-retained output that may be
213    /// emitted before or alongside output derived from the supplied input.
214    ///
215    /// # Parameters
216    ///
217    /// - `input_len`: Number of input units the caller plans to transcode.
218    ///
219    /// # Returns
220    ///
221    /// Returns `Ok(bound)` when the upper bound can be represented as `usize`.
222    /// Returns [`CapacityError::OutputLengthOverflow`] when capacity arithmetic
223    /// overflows.
224    #[must_use = "capacity planning can fail on overflow"]
225    fn max_output_len(&self, input_len: usize) -> Result<usize, CapacityError>;
226
227    /// Returns an upper bound for output units produced by stream finalization.
228    ///
229    /// This bound is evaluated against the transcoder's current state. It does
230    /// not include output that may be produced by future
231    /// [`Transcoder::transcode`] calls. Use it before
232    /// [`Transcoder::finish`] when the caller wants to size a final
233    /// output buffer for the already supplied input.
234    ///
235    /// # Returns
236    ///
237    /// Returns `Ok(bound)` when the upper bound can be represented as `usize`.
238    /// Returns [`CapacityError::OutputLengthOverflow`] when capacity arithmetic
239    /// overflows. Stateless transcoders default to `Ok(0)`.
240    #[must_use = "capacity planning can fail on overflow"]
241    #[inline(always)]
242    fn max_finish_output_len(&self) -> Result<usize, CapacityError> {
243        Ok(0)
244    }
245
246    /// Resets stream state and emits stream-start output into `output`.
247    ///
248    /// This starts a new logical stream while keeping configuration such as
249    /// byte order, charset policy, replacement values, and cryptographic keys.
250    /// Pending input, pending output, and completed-stream state must be
251    /// discarded by stateful implementations. The caller must provide enough
252    /// output capacity for [`Transcoder::max_reset_output_len`].
253    ///
254    /// # Parameters
255    ///
256    /// - `output`: Complete output unit slice visible to the transcoder.
257    /// - `output_index`: Absolute output unit index where writing starts.
258    ///
259    /// # Returns
260    ///
261    /// Returns the number of units written while resetting stream state.
262    /// Stateless transcoders return `0`.
263    ///
264    /// # Errors
265    ///
266    /// Returns contract errors (`invalid_output_index`, `insufficient_output`)
267    /// when capacity checks fail, or policy errors when reset itself fails.
268    fn reset(
269        &mut self,
270        output: &mut [Output],
271        output_index: usize,
272    ) -> Result<usize, TranscodeError<Self::Error>>;
273
274    /// Converts available input units into output units.
275    ///
276    /// This method processes an input segment without closing the logical input
277    /// stream. When the current segment ends in a partial value, the transcoder
278    /// reports [`crate::TranscodeStatus::NeedInput`] without consuming that
279    /// tail. The caller owns input-buffer refill and EOF incomplete-tail
280    /// policy.
281    ///
282    /// # Parameters
283    ///
284    /// - `input`: Complete input unit slice visible to the transcoder.
285    /// - `input_index`: Absolute input unit index where conversion starts.
286    /// - `output`: Complete output unit slice visible to the transcoder.
287    /// - `output_index`: Absolute output unit index where writing starts.
288    ///
289    /// # Returns
290    ///
291    /// Returns progress describing how many units were consumed and produced
292    /// and why conversion stopped.
293    ///
294    /// # Errors
295    ///
296    /// Returns `Self::Error` for semantic conversion failures that the
297    /// transcoder's policy does not absorb, including caller-supplied
298    /// `input_index` or `output_index` values outside their corresponding
299    /// slices.
300    fn transcode(
301        &mut self,
302        input: &[Input],
303        input_index: usize,
304        output: &mut [Output],
305        output_index: usize,
306    ) -> Result<TranscodeProgress, TranscodeError<Self::Error>>;
307
308    /// Finishes internally retained output after all input has been supplied.
309    ///
310    /// `transcode` handles ordinary input consumption. `finish` is called once
311    /// after the caller knows no more input remains and has handled any
312    /// incomplete input tail reported by `transcode`. It emits final output
313    /// derived from internal state, such as reset bytes, checksums, digests, or
314    /// trailers. The caller must provide enough output capacity for
315    /// [`Transcoder::max_finish_output_len`].
316    ///
317    /// After `finish` succeeds, the logical stream is closed. Portable callers
318    /// should call [`Transcoder::reset`] with a buffer sized by
319    /// [`Transcoder::max_reset_output_len`] before passing input for another
320    /// logical stream to the same instance.
321    ///
322    /// # Example
323    ///
324    /// ```rust
325    /// use core::num::NonZeroUsize;
326    /// use qubit_codec::{
327    ///     CodecConvertError,
328    ///     TranscodeError,
329    ///     Transcoder,
330    ///     TranscodeStatus,
331    /// };
332    ///
333    /// #[derive(Default)]
334    /// struct ByteCopy;
335    ///
336    /// impl Transcoder<u8, u8> for ByteCopy {
337    ///     type Error =
338    ///         CodecConvertError<core::convert::Infallible, core::convert::Infallible>;
339    ///
340    ///     fn max_output_len(&self, input_len: usize) -> Result<usize, qubit_codec::CapacityError> {
341    ///         Ok(input_len)
342    ///     }
343    ///
344    ///     fn reset(
345    ///         &mut self,
346    ///         output: &mut [u8],
347    ///         output_index: usize,
348    ///     ) -> Result<usize, TranscodeError<Self::Error>> {
349    ///         TranscodeError::<Self::Error>::ensure_output_index(output.len(), output_index)?;
350    ///         Ok(0)
351    ///     }
352    ///
353    ///     fn transcode(
354    ///         &mut self,
355    ///         input: &[u8],
356    ///         input_index: usize,
357    ///         output: &mut [u8],
358    ///         output_index: usize,
359    ///     ) -> Result<qubit_codec::TranscodeProgress, TranscodeError<Self::Error>> {
360    ///         let mut read = 0;
361    ///         let mut written = 0;
362    ///         while input_index + read < input.len() && output_index + written < output.len() {
363    ///             output[output_index + written] = input[input_index + read];
364    ///             read += 1;
365    ///             written += 1;
366    ///         }
367    ///         if input_index + read == input.len() {
368    ///             Ok(qubit_codec::TranscodeProgress::complete(read, written))
369    ///         } else {
370    ///             let status = qubit_codec::TranscodeStatus::NeedOutput {
371    ///                 output_index: output_index + written,
372    ///                 required: NonZeroUsize::MIN,
373    ///                 available: output.len().saturating_sub(output_index + written),
374    ///             };
375    ///             Ok(qubit_codec::TranscodeProgress::new(
376    ///                 status,
377    ///                 read,
378    ///                 written,
379    ///             ))
380    ///         }
381    ///     }
382    ///
383    ///     fn finish(
384    ///         &mut self,
385    ///         output: &mut [u8],
386    ///         output_index: usize,
387    ///     ) -> Result<usize, TranscodeError<Self::Error>> {
388    ///         TranscodeError::<Self::Error>::ensure_output_index(output.len(), output_index)?;
389    ///         Ok(0)
390    ///     }
391    /// }
392    ///
393    /// let mut transcoder = ByteCopy;
394    /// let mut output = [1_u8; 1];
395    /// let progress = transcoder
396    ///     .transcode(&[7], 0, &mut output, 0)
397    ///     .expect("writer consumes one unit");
398    /// assert_eq!(TranscodeStatus::Complete, progress.status());
399    ///
400    /// let written = transcoder
401    ///     .finish(&mut output, 1)
402    ///     .expect("finish does not emit final state for no-op transcoders");
403    /// assert_eq!(0, written);
404    /// ```
405    ///
406    /// # Parameters
407    ///
408    /// - `output`: Complete output unit slice visible to the transcoder.
409    /// - `output_index`: Absolute output unit index where writing starts.
410    ///
411    /// # Returns
412    ///
413    /// Returns the number of units written during finalization. Stateless
414    /// transcoders return `0`.
415    ///
416    /// # Errors
417    ///
418    /// Returns contract errors (`invalid_output_index`, `insufficient_output`)
419    /// when capacity checks fail, or policy errors when finish itself
420    /// fails.
421    fn finish(
422        &mut self,
423        output: &mut [Output],
424        output_index: usize,
425    ) -> Result<usize, TranscodeError<Self::Error>>;
426}