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