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}