Skip to main content

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}