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}