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