qubit_codec/codec/decode_failure.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//! Failures returned by low-level codec decode operations.
9
10use core::num::NonZeroUsize;
11
12/// Failure reported by [`crate::Codec::decode`].
13///
14/// This type separates stream-control failures from codec-domain failures.
15/// [`Incomplete`](Self::Incomplete) tells buffered adapters to preserve the
16/// current input tail and request more units. [`Invalid`](Self::Invalid)
17/// carries the codec-specific malformed, non-canonical, or otherwise invalid
18/// input error with a known non-zero consumption length, or with unknown
19/// consumption when set to `None`.
20///
21/// # Type Parameters
22///
23/// - `E`: Codec-specific invalid-input error type.
24///
25/// # Examples
26///
27/// ```
28/// use core::num::NonZeroUsize;
29///
30/// use qubit_codec::DecodeFailure;
31///
32/// let failure = DecodeFailure::<()>::incomplete(NonZeroUsize::MIN);
33/// assert_eq!(failure.required_total(), Some(NonZeroUsize::MIN));
34/// assert_eq!(failure.invalid_source(), None);
35/// ```
36#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
37#[must_use]
38pub enum DecodeFailure<E> {
39 /// The visible input is a valid prefix but not enough to decode a value.
40 #[non_exhaustive]
41 Incomplete {
42 /// Codec-specific context for the incomplete prefix, when available.
43 ///
44 /// Stream drivers may ignore this field while retrying input. Once the
45 /// stream reaches EOF, adapters can use it to preserve detailed
46 /// diagnostics instead of reconstructing an error from the length
47 /// bound alone.
48 source: Option<E>,
49 /// Current non-zero minimum total units required before retrying from
50 /// the value start.
51 ///
52 /// A later retry may raise this lower bound after the codec sees a
53 /// longer prefix.
54 ///
55 /// When returned by `Codec::decode` or `Codec::decode_eof`, this must
56 /// be greater than the number of units currently available from the
57 /// value start and no greater than
58 /// `Codec::MAX_DECODE_UNITS_PER_VALUE`. It is a minimum total length
59 /// from that start, not an additional-unit count; for example, with
60 /// two units available and at least one more needed, report three.
61 required_total: NonZeroUsize,
62 },
63
64 /// The input is invalid for the codec.
65 #[non_exhaustive]
66 Invalid {
67 /// Codec-specific invalid-input error.
68 source: E,
69 /// Invalid units that may be consumed by a non-strict policy, when
70 /// known.
71 consumed: Option<NonZeroUsize>,
72 },
73}
74
75impl<E> DecodeFailure<E> {
76 /// Creates an incomplete-input decode failure.
77 ///
78 /// # Parameters
79 ///
80 /// - `required_total`: Current non-zero minimum total units required before
81 /// retrying from the value start. A later retry may raise this lower
82 /// bound. When returned by `Codec::decode` or `Codec::decode_eof`, it
83 /// must be greater than the units currently available from that start and
84 /// no greater than `Codec::MAX_DECODE_UNITS_PER_VALUE`. It counts total
85 /// units from the value start, not additional units to append. This
86 /// constructor has no input or Codec context and cannot validate those
87 /// conditions; the Codec implementation is responsible for them when
88 /// returning the failure.
89 ///
90 /// # Returns
91 ///
92 /// Returns an incomplete decode failure.
93 #[inline]
94 pub const fn incomplete(required_total: NonZeroUsize) -> Self {
95 Self::Incomplete {
96 source: None,
97 required_total,
98 }
99 }
100
101 /// Creates an incomplete-input decode failure with codec-specific context.
102 ///
103 /// # Parameters
104 ///
105 /// - `source`: Codec-specific error describing the visible incomplete
106 /// prefix.
107 /// - `required_total`: Current non-zero minimum units required before
108 /// retrying from the value start. When returned by `Codec::decode` or
109 /// `Codec::decode_eof`, it must be greater than the units currently
110 /// available from that start and no greater than
111 /// `Codec::MAX_DECODE_UNITS_PER_VALUE`. It counts total units from the
112 /// value start, not additional units to append. This constructor has no
113 /// input or Codec context and cannot validate those conditions; the Codec
114 /// implementation is responsible for them when returning the failure.
115 ///
116 /// # Returns
117 ///
118 /// Returns an incomplete decode failure retaining `source` for adapters
119 /// that need detailed EOF diagnostics.
120 #[inline]
121 pub const fn incomplete_with_source(source: E, required_total: NonZeroUsize) -> Self {
122 Self::Incomplete {
123 source: Some(source),
124 required_total,
125 }
126 }
127
128 /// Creates an invalid-input decode failure with a consumption hint.
129 ///
130 /// # Parameters
131 ///
132 /// - `source`: Codec-specific invalid-input error.
133 /// - `consumed`: Invalid input units that may be consumed.
134 ///
135 /// # Returns
136 ///
137 /// Returns an invalid decode failure.
138 #[inline]
139 pub const fn invalid(source: E, consumed: NonZeroUsize) -> Self {
140 Self::Invalid {
141 source,
142 consumed: Some(consumed),
143 }
144 }
145
146 /// Creates an invalid-input decode failure with unknown consumption.
147 ///
148 /// # Parameters
149 ///
150 /// - `source`: Codec-specific invalid-input error.
151 ///
152 /// # Returns
153 ///
154 /// Returns an invalid decode failure.
155 #[inline]
156 pub const fn invalid_unknown(source: E) -> Self {
157 Self::Invalid { source, consumed: None }
158 }
159
160 /// Returns the current minimum input length required before retrying an
161 /// incomplete prefix.
162 ///
163 /// # Returns
164 ///
165 /// Returns `Some(required_total)` for incomplete failures, where the value
166 /// is a lower bound that a later retry may raise, or `None` for
167 /// invalid-input failures.
168 #[inline]
169 pub const fn required_total(&self) -> Option<NonZeroUsize> {
170 match self {
171 Self::Incomplete { required_total, .. } => Some(*required_total),
172 Self::Invalid { .. } => None,
173 }
174 }
175
176 /// Borrows the codec-specific error retained for an incomplete prefix.
177 ///
178 /// # Returns
179 ///
180 /// Returns `Some(source)` when the codec supplied incomplete-input context,
181 /// or `None` when the failure only carries the retry bound or is invalid.
182 #[inline]
183 pub const fn incomplete_source(&self) -> Option<&E> {
184 match self {
185 Self::Incomplete { source, .. } => source.as_ref(),
186 Self::Invalid { .. } => None,
187 }
188 }
189
190 /// Borrows the codec-specific invalid-input error.
191 ///
192 /// # Returns
193 ///
194 /// Returns `Some(source)` for invalid-input failures, or `None` for
195 /// incomplete failures.
196 #[inline]
197 pub const fn invalid_source(&self) -> Option<&E> {
198 match self {
199 Self::Invalid { source, .. } => Some(source),
200 Self::Incomplete { .. } => None,
201 }
202 }
203
204 /// Returns invalid units that may be consumed by a non-strict policy.
205 ///
206 /// # Returns
207 ///
208 /// Returns `Some(consumed)` for [`Invalid`](Self::Invalid), or `None` when
209 /// the failure is incomplete or invalid with unknown consumption.
210 #[inline]
211 pub const fn consumed_units(&self) -> Option<NonZeroUsize> {
212 match self {
213 Self::Invalid { consumed, .. } => *consumed,
214 Self::Incomplete { .. } => None,
215 }
216 }
217}