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}