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