Skip to main content

qubit_codec/transcoder/
transcoder.rs

1/*******************************************************************************
2 *
3 *    Copyright (c) 2026 Haixing Hu.
4 *
5 *    SPDX-License-Identifier: Apache-2.0
6 *
7 *    Licensed under the Apache License, Version 2.0.
8 *
9 ******************************************************************************/
10use super::transcode_progress::TranscodeProgress;
11
12/// Converts one logical stream of input units into one logical stream of output units.
13///
14/// `transcode` is the main streaming API. It transforms a provided input segment and
15/// writes as much output as available buffer space allows, without automatically
16/// finalizing internal pending state.
17///
18/// A transcoder instance has a simple lifecycle:
19///
20/// 1. A newly created or reset instance is ready for a new logical stream.
21/// 2. Call [`Transcoder::transcode`] zero or more times while input is available.
22/// 3. Call [`Transcoder::finish`] after the caller knows no more input remains.
23/// 4. Continue calling [`Transcoder::finish`] while it reports
24///    [`crate::TranscodeStatus::NeedOutput`].
25/// 5. After [`Transcoder::finish`] reports [`crate::TranscodeStatus::Complete`],
26///    call [`Transcoder::reset`] before starting another logical stream with the
27///    same instance.
28///
29/// The method is suitable for:
30/// - pull-style consumers that call conversion repeatedly as buffers arrive;
31/// - bounded output sinks that need `NeedOutput` progress when capacity is hit;
32/// - stateless and stateful codecs that all return progress-oriented stopping
33///   reasons.
34///
35/// `Transcoder` is intentionally independent from any charset semantics:
36///
37/// - Use `Transcoder` directly for custom, policy-free unit transforms.
38/// - Use `Transcoder` when you want to own malformed/unmappable decisions at the call site.
39///
40/// # Example: streaming byte-to-word decoder
41///
42/// ```rust
43/// use qubit_codec::{Transcoder, TranscodeProgress, TranscodeStatus};
44///
45/// #[derive(Default)]
46/// struct U16BeBytesDecoder;
47///
48/// impl Transcoder<u8, u16> for U16BeBytesDecoder {
49///     type Error = core::convert::Infallible;
50///
51///     fn max_output_len(&self, input_len: usize) -> Option<usize> {
52///         Some(input_len / 2)
53///     }
54///
55///     fn transcode(
56///         &mut self,
57///         input: &[u8],
58///         input_index: usize,
59///         output: &mut [u16],
60///         output_index: usize,
61///     ) -> Result<TranscodeProgress, Self::Error> {
62///         let mut read = 0;
63///         let mut written = 0;
64///         while input_index + read + 1 < input.len() {
65///             if output_index + written == output.len() {
66///                 let status = TranscodeStatus::NeedOutput {
67///                     output_index: output_index + written,
68///                     required: 1,
69///                     available: 0,
70///                 };
71///                 return Ok(TranscodeProgress::new(status, read, written));
72///             }
73///             let high = input[input_index + read] as u16;
74///             let low = input[input_index + read + 1] as u16;
75///             output[output_index + written] = (high << 8) | low;
76///             read += 2;
77///             written += 1;
78///         }
79///         if input_index + read == input.len() {
80///             Ok(TranscodeProgress::complete(read, written))
81///         } else {
82///             let status = TranscodeStatus::NeedInput {
83///                 input_index: input_index + read,
84///                 required: 2,
85///                 available: input.len() - (input_index + read),
86///             };
87///             Ok(TranscodeProgress::new(status, read, written))
88///         }
89///     }
90/// }
91///
92/// let mut transcoder = U16BeBytesDecoder;
93/// let mut output = [0_u16; 1];
94/// let progress = transcoder
95///     .transcode(&[0x12, 0x34, 0xab, 0xcd], 0, &mut output, 0)
96///     .expect("decoding cannot fail");
97/// assert_eq!(TranscodeStatus::NeedOutput {
98///     output_index: 1,
99///     required: 1,
100///     available: 0,
101/// }, progress.status());
102/// assert_eq!(2, progress.read());
103/// assert_eq!(1, progress.written());
104/// assert_eq!([0x1234], output);
105///
106/// let mut output = [0_u16; 2];
107/// let progress = transcoder
108///     .transcode(&[0x12, 0x34, 0xab], 0, &mut output, 0)
109///     .expect("decoding cannot fail");
110/// assert_eq!(TranscodeStatus::NeedInput {
111///     input_index: 2,
112///     required: 2,
113///     available: 1,
114/// }, progress.status());
115/// assert_eq!(2, progress.read());
116/// assert_eq!(1, progress.written());
117/// assert_eq!([0x1234, 0], output);
118/// ```
119///
120/// The trait is intentionally independent from charset concepts. Implementors
121/// use `input_index` and `output_index` as absolute positions in the supplied
122/// slices. Returned progress counters are relative counts from those positions.
123/// For raw codecs this gives a compact API; higher-level workflows can wrap this
124/// trait with their own semantic policies.
125///
126/// # Type Parameters
127///
128/// - `Input`: Input unit type accepted by this transcoder.
129/// - `Output`: Output unit type produced by this transcoder.
130pub trait Transcoder<Input, Output> {
131    /// Error reported for semantic conversion failures.
132    type Error;
133
134    /// Returns an upper bound for output units produced from `input_len` units.
135    ///
136    /// # Parameters
137    ///
138    /// - `input_len`: Number of input units the caller plans to transcode.
139    ///
140    /// # Returns
141    ///
142    /// Returns `Some(bound)` when the transcoder can provide a finite upper bound.
143    /// Returns `None` when the bound is not known.
144    #[must_use]
145    fn max_output_len(&self, input_len: usize) -> Option<usize>;
146
147    /// Returns an upper bound for output units produced by finalization.
148    ///
149    /// This bound is evaluated against the transcoder's current state. It does
150    /// not include output that may be produced by future [`Transcoder::transcode`]
151    /// calls. Use it before [`Transcoder::finish`] when the caller wants to size
152    /// a flush buffer for the already supplied input.
153    ///
154    /// # Returns
155    ///
156    /// Returns `Some(bound)` when the transcoder can provide a finite upper bound
157    /// for finalization output. Returns `None` when the bound is not known.
158    /// Stateless transcoders default to `Some(0)`.
159    #[must_use]
160    #[inline]
161    fn max_finish_output_len(&self) -> Option<usize> {
162        Some(0)
163    }
164
165    /// Resets state retained between conversion calls.
166    ///
167    /// This starts a new logical stream while keeping configuration such as
168    /// byte order, charset policy, replacement values, and cryptographic keys.
169    /// Pending input, pending output, and completed-stream state must be
170    /// discarded by stateful implementations. Stateless transcoders may keep
171    /// the default no-op implementation.
172    #[inline]
173    fn reset(&mut self) {}
174
175    /// Converts available input units into output units.
176    ///
177    /// This method processes an input segment without closing the logical input
178    /// stream. When the current segment ends in a partial value, a transcoder may
179    /// either keep enough internal state to continue later or report
180    /// [`crate::TranscodeStatus::NeedInput`]. Callers that have reached EOF must
181    /// call [`Transcoder::finish`] so the transcoder can either flush, replace,
182    /// ignore, or reject pending incomplete state according to its policy.
183    ///
184    /// # Parameters
185    ///
186    /// - `input`: Complete input unit slice visible to the transcoder.
187    /// - `input_index`: Absolute input unit index where conversion starts.
188    /// - `output`: Complete output unit slice visible to the transcoder.
189    /// - `output_index`: Absolute output unit index where writing starts.
190    ///
191    /// # Returns
192    ///
193    /// Returns progress describing how many units were consumed and produced and
194    /// why conversion stopped.
195    ///
196    /// # Errors
197    ///
198    /// Returns `Self::Error` for semantic conversion failures that the transcoder's
199    /// policy does not absorb.
200    fn transcode(
201        &mut self,
202        input: &[Input],
203        input_index: usize,
204        output: &mut [Output],
205        output_index: usize,
206    ) -> Result<TranscodeProgress, Self::Error>;
207
208    /// Finalizes the current logical stream after all input has been supplied.
209    ///
210    /// `transcode` handles ordinary input consumption. `finish` is called only
211    /// after the caller knows no more input remains. It is responsible for
212    /// flushing buffered output, validating pending incomplete input, and
213    /// emitting any stream trailer required by the concrete transcoder. If the
214    /// provided output buffer is too small, `finish` returns
215    /// [`crate::TranscodeStatus::NeedOutput`] and may be called again with more
216    /// output capacity.
217    ///
218    /// After `finish` returns [`crate::TranscodeStatus::Complete`], the logical
219    /// stream is closed. Portable callers should call [`Transcoder::reset`]
220    /// before passing input for another logical stream to the same instance.
221    ///
222    /// # Example
223    ///
224    /// ```rust
225    /// use qubit_codec::{Transcoder, TranscodeStatus};
226    ///
227    /// #[derive(Default)]
228    /// struct ByteCopy;
229    ///
230    /// impl Transcoder<u8, u8> for ByteCopy {
231    ///     type Error = core::convert::Infallible;
232    ///
233    ///     fn max_output_len(&self, input_len: usize) -> Option<usize> {
234    ///         Some(input_len)
235    ///     }
236    ///
237    ///     fn transcode(
238    ///         &mut self,
239    ///         input: &[u8],
240    ///         input_index: usize,
241    ///         output: &mut [u8],
242    ///         output_index: usize,
243    ///     ) -> Result<qubit_codec::TranscodeProgress, Self::Error> {
244    ///         let mut read = 0;
245    ///         let mut written = 0;
246    ///         while input_index + read < input.len() && output_index + written < output.len() {
247    ///             output[output_index + written] = input[input_index + read];
248    ///             read += 1;
249    ///             written += 1;
250    ///         }
251    ///         if input_index + read == input.len() {
252    ///             Ok(qubit_codec::TranscodeProgress::complete(read, written))
253    ///         } else {
254    ///             let status = qubit_codec::TranscodeStatus::NeedOutput {
255    ///                 output_index: output_index + written,
256    ///                 required: 1,
257    ///                 available: output.len().saturating_sub(output_index + written),
258    ///             };
259    ///             Ok(qubit_codec::TranscodeProgress::new(
260    ///                 status,
261    ///                 read,
262    ///                 written,
263    ///             ))
264    ///         }
265    ///     }
266    /// }
267    ///
268    /// let mut transcoder = ByteCopy;
269    /// let mut output = [1_u8; 1];
270    /// let progress = transcoder
271    ///     .transcode(&[7], 0, &mut output, 0)
272    ///     .expect("writer consumes one unit");
273    /// assert_eq!(TranscodeStatus::Complete, progress.status());
274    ///
275    /// let finish = transcoder
276    ///     .finish(&mut output, 1)
277    ///     .expect("finish does not emit buffered state for no-op transcoders");
278    /// assert_eq!(TranscodeStatus::Complete, finish.status());
279    /// ```
280    ///
281    /// # Parameters
282    ///
283    /// - `output`: Complete output unit slice visible to the transcoder.
284    /// - `output_index`: Absolute output unit index where writing starts.
285    ///
286    /// # Returns
287    ///
288    /// Returns progress for units written during finalization. The `read` counter
289    /// is normally zero because no new input is supplied to `finish`. Stateless
290    /// transcoders return a completed progress value with zero counters.
291    ///
292    /// # Errors
293    ///
294    /// Returns `Self::Error` if pending state cannot be flushed according to the
295    /// transcoder's policy.
296    #[inline]
297    fn finish(&mut self, _output: &mut [Output], _output_index: usize) -> Result<TranscodeProgress, Self::Error> {
298        Ok(TranscodeProgress::complete(0, 0))
299    }
300}