Skip to main content

qubit_codec/transcode/
transcode_progress.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// =============================================================================
8//! Progress reported by transcode operations.
9
10use core::num::NonZeroUsize;
11
12use super::super::TranscodeContractError;
13use super::super::TranscodeStatus;
14
15/// Counts how much work a [`crate::Transcoder`] completed before
16/// returning.
17///
18/// # Examples
19///
20/// ```
21/// use qubit_codec::TranscodeProgress;
22/// use qubit_codec::TranscodeStatus;
23///
24/// let progress = TranscodeProgress::complete(3, 6);
25/// assert_eq!(progress.read(), 3);
26/// assert_eq!(progress.written(), 6);
27/// assert!(matches!(progress.status(), TranscodeStatus::Complete));
28/// ```
29#[derive(Clone, Copy, Debug, Eq, PartialEq)]
30#[must_use]
31pub struct TranscodeProgress {
32    /// Stop reason reported by the transcoder.
33    status: TranscodeStatus,
34    /// Number of input units consumed from the requested input index.
35    read: usize,
36    /// Number of output units written from the requested output index.
37    written: usize,
38}
39
40impl TranscodeProgress {
41    /// Creates a progress value.
42    ///
43    /// # Parameters
44    ///
45    /// - `status`: The reason conversion stopped.
46    /// - `read`: Number of input units consumed from the call's input index.
47    /// - `written`: Number of output units written from the call's output
48    ///   index.
49    ///
50    /// # Returns
51    ///
52    /// Returns a progress value carrying the supplied counters.
53    #[inline]
54    pub const fn new(status: TranscodeStatus, read: usize, written: usize) -> Self {
55        Self { status, read, written }
56    }
57
58    /// Creates a completed progress value.
59    ///
60    /// # Parameters
61    ///
62    /// - `read`: Number of consumed input units.
63    /// - `written`: Number of produced output units.
64    ///
65    /// # Returns
66    ///
67    /// Returns a progress value whose status is [`TranscodeStatus::Complete`].
68    #[inline]
69    pub const fn complete(read: usize, written: usize) -> Self {
70        Self::new(TranscodeStatus::Complete, read, written)
71    }
72
73    /// Creates progress that stopped because more input is needed.
74    ///
75    /// # Parameters
76    ///
77    /// - `required`: Current minimum total input units required before retrying
78    ///   from the current input position. A later retry may raise this lower
79    ///   bound.
80    /// - `read`: Number of consumed input units.
81    /// - `written`: Number of produced output units.
82    ///
83    /// # Returns
84    ///
85    /// Returns a progress value with [`TranscodeStatus::NeedInput`].
86    #[inline]
87    pub const fn need_input(required: NonZeroUsize, read: usize, written: usize) -> Self {
88        Self::new(TranscodeStatus::need_input(required), read, written)
89    }
90
91    /// Creates progress that stopped because more output capacity is needed.
92    ///
93    /// # Parameters
94    ///
95    /// - `required`: Total output units required from the current output
96    ///   position.
97    /// - `read`: Number of consumed input units.
98    /// - `written`: Number of produced output units.
99    ///
100    /// # Returns
101    ///
102    /// Returns a progress value with [`TranscodeStatus::NeedOutput`].
103    #[inline]
104    pub const fn need_output(required: NonZeroUsize, read: usize, written: usize) -> Self {
105        Self::new(TranscodeStatus::need_output(required), read, written)
106    }
107
108    /// Returns the status that stopped conversion.
109    ///
110    /// # Returns
111    ///
112    /// Returns the stored [`TranscodeStatus`].
113    #[inline]
114    pub const fn status(self) -> TranscodeStatus {
115        self.status
116    }
117
118    /// Returns whether conversion consumed all currently supplied input.
119    ///
120    /// # Returns
121    ///
122    /// Returns `true` when the stored status is
123    /// [`TranscodeStatus::Complete`].
124    #[inline]
125    #[must_use]
126    pub const fn is_complete(self) -> bool {
127        matches!(self.status, TranscodeStatus::Complete)
128    }
129
130    /// Returns whether conversion stopped because more input is needed.
131    ///
132    /// # Returns
133    ///
134    /// Returns `true` when the stored status is
135    /// [`TranscodeStatus::NeedInput`].
136    #[inline]
137    #[must_use]
138    pub const fn is_need_input(self) -> bool {
139        matches!(self.status, TranscodeStatus::NeedInput { .. })
140    }
141
142    /// Returns whether conversion stopped because more output capacity is
143    /// needed.
144    ///
145    /// # Returns
146    ///
147    /// Returns `true` when the stored status is
148    /// [`TranscodeStatus::NeedOutput`].
149    #[inline]
150    #[must_use]
151    pub const fn is_need_output(self) -> bool {
152        matches!(self.status, TranscodeStatus::NeedOutput { .. })
153    }
154
155    /// Returns the number of input units consumed by the call.
156    ///
157    /// # Returns
158    ///
159    /// Returns a count relative to the input index passed to the conversion
160    /// call.
161    #[inline]
162    #[must_use]
163    pub const fn read(self) -> usize {
164        self.read
165    }
166
167    /// Returns the number of output units written by the call.
168    ///
169    /// # Returns
170    ///
171    /// Returns a count relative to the output index passed to the conversion
172    /// call.
173    #[inline]
174    #[must_use]
175    pub const fn written(self) -> usize {
176        self.written
177    }
178
179    /// Validates this progress against the call bounds supplied to a
180    /// transcoder.
181    ///
182    /// Buffered drivers should call this before using [`Self::read`] or
183    /// [`Self::written`] to advance unchecked input or output cursors. The
184    /// method checks relative counters, unsatisfied `NeedInput` / `NeedOutput`
185    /// requirements, and the `Complete` invariant that all visible input was
186    /// consumed.
187    ///
188    /// This is a contract checker, not a semantic recovery policy. It performs
189    /// the same validation in every build profile. Drivers that advance unsafe
190    /// cursors may convert failures into their own error type, while
191    /// convenience helpers may assert that validation succeeds. Custom
192    /// [`crate::Transcoder`] implementations must therefore return progress
193    /// that satisfies the documented contract in all builds.
194    ///
195    /// # Parameters
196    ///
197    /// - `input_index`: Input index originally passed to the transcoder.
198    /// - `available_input`: Number of input units visible from `input_index`.
199    /// - `output_index`: Output index originally passed to the transcoder.
200    /// - `available_output`: Number of output slots visible from
201    ///   `output_index`.
202    ///
203    /// # Returns
204    ///
205    /// Returns `Ok(())` when progress is internally consistent with the
206    /// supplied call bounds.
207    ///
208    /// # Errors
209    ///
210    /// Returns [`TranscodeContractError`] when a custom transcoder reports
211    /// counters or missing-capacity requirements that do not match the buffers
212    /// supplied by the caller.
213    pub fn validate(
214        &self,
215        input_index: usize,
216        available_input: usize,
217        output_index: usize,
218        available_output: usize,
219    ) -> Result<(), TranscodeContractError> {
220        if self.read > available_input {
221            return Err(TranscodeContractError::OverRead {
222                read: self.read,
223                available: available_input,
224            });
225        }
226        if self.written > available_output {
227            return Err(TranscodeContractError::OverWritten {
228                written: self.written,
229                available: available_output,
230            });
231        }
232
233        input_index
234            .checked_add(self.read)
235            .ok_or(TranscodeContractError::ProgressIndexOverflow {
236                index: input_index,
237                advanced: self.read,
238            })?;
239        output_index
240            .checked_add(self.written)
241            .ok_or(TranscodeContractError::ProgressIndexOverflow {
242                index: output_index,
243                advanced: self.written,
244            })?;
245        match self.status {
246            TranscodeStatus::Complete => {
247                if self.read != available_input {
248                    return Err(TranscodeContractError::CompleteWithRemainingInput {
249                        read: self.read,
250                        available: available_input,
251                    });
252                }
253                Ok(())
254            }
255            TranscodeStatus::NeedInput { required } => {
256                let available = available_input - self.read;
257                if required.get() <= available {
258                    return Err(TranscodeContractError::SatisfiedNeed {
259                        required: required.get(),
260                        available,
261                    });
262                }
263                Ok(())
264            }
265            TranscodeStatus::NeedOutput { required } => {
266                let available = available_output - self.written;
267                if required.get() <= available {
268                    return Err(TranscodeContractError::SatisfiedNeed {
269                        required: required.get(),
270                        available,
271                    });
272                }
273                Ok(())
274            }
275        }
276    }
277}