qubit_codec/value/codec_value_ext.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//! Value-level convenience methods for low-level codecs.
9
10use core::num::NonZeroUsize;
11
12use crate::{
13 CapacityError,
14 Codec,
15 CodecDecodeError,
16 CodecEncodeError,
17};
18
19/// Extension trait for checked one-value codec operations.
20///
21/// `CodecValueExt` keeps convenience operations out of the low-level
22/// [`Codec`] contract while still making them available to all codec
23/// implementations. The methods compose primitive reset, encode, decode, and
24/// flush hooks with capacity checks and adapter-level error wrapping.
25pub trait CodecValueExt: Codec {
26 /// Returns the maximum unit count emitted by one reset-prefixed value
27 /// encode.
28 ///
29 /// This is the checked sum of
30 /// [`Codec::max_encode_reset_units`] and
31 /// [`Codec::max_units_per_value`]. It is useful for callers that want to
32 /// reuse scratch storage for repeated one-value encodes.
33 ///
34 /// # Returns
35 ///
36 /// Returns the maximum reset-plus-value output length.
37 ///
38 /// # Errors
39 ///
40 /// Returns [`CapacityError::OutputLengthOverflow`] when the sum cannot be
41 /// represented as `usize`.
42 #[inline(always)]
43 #[must_use = "capacity planning can fail on overflow"]
44 fn max_encode_value_units(&self) -> Result<usize, CapacityError> {
45 self.max_encode_reset_units()
46 .checked_add(self.max_units_per_value().get())
47 .ok_or(CapacityError::OutputLengthOverflow)
48 }
49
50 /// Encodes one value after emitting reset output into a caller buffer.
51 ///
52 /// The method validates the output index and the combined reset-plus-value
53 /// capacity before calling the unchecked codec hooks. It is a convenience
54 /// wrapper for code paths that need one complete value and want to reuse
55 /// caller-owned storage.
56 ///
57 /// # Parameters
58 ///
59 /// - `value`: Value to encode.
60 /// - `output`: Destination unit buffer.
61 /// - `output_index`: Start index in `output`.
62 ///
63 /// # Returns
64 ///
65 /// Returns the total number of reset and value units written.
66 ///
67 /// # Errors
68 ///
69 /// Returns [`CodecEncodeError::UnencodableValue`] when `value` is outside
70 /// this codec's encodable domain,
71 /// [`CodecEncodeError::InvalidOutputIndex`] when `output_index` is outside
72 /// `output`, [`CodecEncodeError::InsufficientOutput`] when the writable
73 /// suffix cannot hold the reset output plus exact encoded value width,
74 /// [`CodecEncodeError::OutputLengthOverflow`] when the bound overflows, or
75 /// [`CodecEncodeError::Encode`] when reset or value encoding fails.
76 ///
77 /// # Panics
78 ///
79 /// Panics when the codec writes or reports more units than its declared
80 /// reset or value bound.
81 fn encode_value_with_reset(
82 &mut self,
83 value: &Self::Value,
84 output: &mut [Self::Unit],
85 output_index: usize,
86 ) -> Result<usize, CodecEncodeError<Self::EncodeError>> {
87 if !self.can_encode_value(value) {
88 return Err(CodecEncodeError::unencodable_value(0));
89 }
90 let reset_units = self.max_encode_reset_units();
91 let value_units = self.encode_len(value).get();
92 let required = reset_units
93 .checked_add(value_units)
94 .ok_or_else(CodecEncodeError::output_length_overflow)?;
95 CodecEncodeError::ensure_output_capacity(
96 output.len(),
97 output_index,
98 required,
99 )?;
100
101 let reset_written = unsafe {
102 // SAFETY: The capacity check above reserves the combined
103 // reset-plus-value output bound at `output_index`.
104 self.encode_reset(output, output_index)
105 }
106 .map_err(|error| CodecEncodeError::encode(error, 0))?;
107 assert!(
108 reset_written <= reset_units,
109 "Codec::encode_reset wrote beyond its reset bound",
110 );
111
112 let value_written = unsafe {
113 // SAFETY: `reset_written <= reset_units` and the earlier combined
114 // capacity check leave the exact value width writable.
115 self.encode(value, output, output_index + reset_written)
116 }
117 .map_err(|error| CodecEncodeError::encode(error, 0))?
118 .get();
119 assert!(
120 value_written == value_units,
121 "Codec::encode wrote a different length than Codec::encode_len",
122 );
123 Ok(reset_written + value_written)
124 }
125
126 /// Decodes one value and flushes decode-side state into caller storage.
127 ///
128 /// The method validates input and flush-output bounds before entering the
129 /// unchecked codec hooks. It returns the decoded value, the consumed input
130 /// count, and the number of flushed values written to `flush_output`.
131 ///
132 /// # Parameters
133 ///
134 /// - `input`: Source unit buffer.
135 /// - `input_index`: Start index in `input`.
136 /// - `flush_output`: Destination value buffer for decode-flush output.
137 /// - `flush_output_index`: Start index in `flush_output`.
138 ///
139 /// # Returns
140 ///
141 /// Returns `(value, consumed, flushed)`.
142 ///
143 /// # Errors
144 ///
145 /// Returns [`CodecDecodeError::InvalidInputIndex`] when `input_index` is
146 /// outside `input`, [`CodecDecodeError::Incomplete`] when fewer than
147 /// [`Codec::min_units_per_value`] units are readable,
148 /// [`CodecDecodeError::InvalidOutputIndex`] or
149 /// [`CodecDecodeError::InsufficientOutput`] when flush output cannot hold
150 /// [`Codec::max_decode_flush_values`], or [`CodecDecodeError::Decode`]
151 /// when decoding or flushing fails.
152 ///
153 /// # Panics
154 ///
155 /// Panics when the codec consumes beyond available input or flushes more
156 /// values than its declared bound.
157 fn decode_value_with_flush(
158 &mut self,
159 input: &[Self::Unit],
160 input_index: usize,
161 flush_output: &mut [Self::Value],
162 flush_output_index: usize,
163 ) -> Result<
164 (Self::Value, NonZeroUsize, usize),
165 CodecDecodeError<Self::DecodeError>,
166 > {
167 CodecDecodeError::ensure_input_index(input.len(), input_index)?;
168 let min_units = self.min_units_per_value().get();
169 CodecDecodeError::ensure_min_input(
170 input.len(),
171 input_index,
172 min_units,
173 )?;
174
175 let flush_cap = self.max_decode_flush_values();
176 CodecDecodeError::ensure_output_capacity(
177 flush_output.len(),
178 flush_output_index,
179 flush_cap,
180 )?;
181
182 let (value, consumed) = unsafe {
183 // SAFETY: The input checks above guarantee the minimum readable
184 // units required by `Codec::decode`.
185 self.decode(input, input_index)
186 }
187 .map_err(|error| CodecDecodeError::decode(error, input_index))?;
188 let available = input.len() - input_index;
189 assert!(
190 consumed.get() <= available,
191 "Codec::decode consumed beyond available input",
192 );
193
194 let flushed = unsafe {
195 // SAFETY: The flush-output checks above reserve the declared flush
196 // output bound at `flush_output_index`.
197 self.decode_flush(flush_output, flush_output_index)
198 }
199 .map_err(|error| {
200 CodecDecodeError::decode(error, input_index + consumed.get())
201 })?;
202 assert!(
203 flushed <= flush_cap,
204 "Codec::decode_flush wrote beyond its flush bound",
205 );
206 Ok((value, consumed, flushed))
207 }
208
209 /// Decodes exactly one value and then flushes decode-side state.
210 ///
211 /// Unlike [`decode_value_with_flush`](Self::decode_value_with_flush), this
212 /// helper requires the supplied input slice to contain exactly one encoded
213 /// value. It validates trailing input before calling
214 /// [`Codec::decode_flush`], preserving whole-value decoder semantics while
215 /// still centralizing flush scratch-buffer handling.
216 ///
217 /// # Parameters
218 ///
219 /// - `input`: Source units for exactly one encoded value.
220 /// - `flush_output`: Destination value buffer for decode-flush output.
221 /// - `flush_output_index`: Start index in `flush_output`.
222 ///
223 /// # Returns
224 ///
225 /// Returns `(value, flushed)`.
226 ///
227 /// # Errors
228 ///
229 /// Returns [`CodecDecodeError::Incomplete`] when fewer than
230 /// [`Codec::min_units_per_value`] units are available,
231 /// [`CodecDecodeError::TrailingInput`] when decode succeeds but leaves
232 /// extra units, output-capacity errors for invalid flush storage, or
233 /// [`CodecDecodeError::Decode`] when decoding or flushing fails.
234 ///
235 /// # Panics
236 ///
237 /// Panics when the codec consumes beyond available input or flushes more
238 /// values than its declared bound.
239 fn decode_exact_value_with_flush(
240 &mut self,
241 input: &[Self::Unit],
242 flush_output: &mut [Self::Value],
243 flush_output_index: usize,
244 ) -> Result<(Self::Value, usize), CodecDecodeError<Self::DecodeError>> {
245 let min_units = self.min_units_per_value().get();
246 CodecDecodeError::ensure_min_input(input.len(), 0, min_units)?;
247
248 let flush_cap = self.max_decode_flush_values();
249 CodecDecodeError::ensure_output_capacity(
250 flush_output.len(),
251 flush_output_index,
252 flush_cap,
253 )?;
254
255 let (value, consumed) = unsafe {
256 // SAFETY: The input check above guarantees the minimum readable
257 // units required by `Codec::decode` at index 0.
258 self.decode(input, 0)
259 }
260 .map_err(|error| CodecDecodeError::decode(error, 0))?;
261 assert!(
262 consumed.get() <= input.len(),
263 "Codec::decode consumed beyond available input",
264 );
265 CodecDecodeError::ensure_no_trailing_input(
266 consumed.get(),
267 input.len(),
268 )?;
269
270 let flushed = unsafe {
271 // SAFETY: The flush-output checks above reserve the declared flush
272 // output bound at `flush_output_index`.
273 self.decode_flush(flush_output, flush_output_index)
274 }
275 .map_err(|error| CodecDecodeError::decode(error, consumed.get()))?;
276 assert!(
277 flushed <= flush_cap,
278 "Codec::decode_flush wrote beyond its flush bound",
279 );
280 Ok((value, flushed))
281 }
282}
283
284impl<C> CodecValueExt for C where C: Codec + ?Sized {}