Skip to main content

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 {}