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}