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 required_total: NonZeroUsize,
55 },
56
57 /// The input is invalid for the codec.
58 #[non_exhaustive]
59 Invalid {
60 /// Codec-specific invalid-input error.
61 source: E,
62 /// Invalid units that may be consumed by a non-strict policy, when
63 /// known.
64 consumed: Option<NonZeroUsize>,
65 },
66}
67
68impl<E> DecodeFailure<E> {
69 /// Creates an incomplete-input decode failure.
70 ///
71 /// # Parameters
72 ///
73 /// - `required_total`: Current non-zero minimum total units required before
74 /// retrying from the value start. A later retry may raise this lower
75 /// bound.
76 ///
77 /// # Returns
78 ///
79 /// Returns an incomplete decode failure.
80 #[inline]
81 pub const fn incomplete(required_total: NonZeroUsize) -> Self {
82 Self::Incomplete {
83 source: None,
84 required_total,
85 }
86 }
87
88 /// Creates an incomplete-input decode failure with codec-specific context.
89 ///
90 /// # Parameters
91 ///
92 /// - `source`: Codec-specific error describing the visible incomplete
93 /// prefix.
94 /// - `required_total`: Current non-zero minimum units required before
95 /// retrying from the value start.
96 ///
97 /// # Returns
98 ///
99 /// Returns an incomplete decode failure retaining `source` for adapters
100 /// that need detailed EOF diagnostics.
101 #[inline]
102 pub const fn incomplete_with_source(source: E, required_total: NonZeroUsize) -> Self {
103 Self::Incomplete {
104 source: Some(source),
105 required_total,
106 }
107 }
108
109 /// Creates an invalid-input decode failure with a consumption hint.
110 ///
111 /// # Parameters
112 ///
113 /// - `source`: Codec-specific invalid-input error.
114 /// - `consumed`: Invalid input units that may be consumed.
115 ///
116 /// # Returns
117 ///
118 /// Returns an invalid decode failure.
119 #[inline]
120 pub const fn invalid(source: E, consumed: NonZeroUsize) -> Self {
121 Self::Invalid {
122 source,
123 consumed: Some(consumed),
124 }
125 }
126
127 /// Creates an invalid-input decode failure with unknown consumption.
128 ///
129 /// # Parameters
130 ///
131 /// - `source`: Codec-specific invalid-input error.
132 ///
133 /// # Returns
134 ///
135 /// Returns an invalid decode failure.
136 #[inline]
137 pub const fn invalid_unknown(source: E) -> Self {
138 Self::Invalid { source, consumed: None }
139 }
140
141 /// Returns the current minimum input length required before retrying an
142 /// incomplete prefix.
143 ///
144 /// # Returns
145 ///
146 /// Returns `Some(required_total)` for incomplete failures, where the value
147 /// is a lower bound that a later retry may raise, or `None` for
148 /// invalid-input failures.
149 #[inline]
150 #[must_use]
151 pub const fn required_total(&self) -> Option<NonZeroUsize> {
152 match self {
153 Self::Incomplete { required_total, .. } => Some(*required_total),
154 Self::Invalid { .. } => None,
155 }
156 }
157
158 /// Borrows the codec-specific error retained for an incomplete prefix.
159 ///
160 /// # Returns
161 ///
162 /// Returns `Some(source)` when the codec supplied incomplete-input context,
163 /// or `None` when the failure only carries the retry bound or is invalid.
164 #[inline]
165 #[must_use]
166 pub const fn incomplete_source(&self) -> Option<&E> {
167 match self {
168 Self::Incomplete { source, .. } => source.as_ref(),
169 Self::Invalid { .. } => None,
170 }
171 }
172
173 /// Borrows the codec-specific invalid-input error.
174 ///
175 /// # Returns
176 ///
177 /// Returns `Some(source)` for invalid-input failures, or `None` for
178 /// incomplete failures.
179 #[inline]
180 #[must_use]
181 pub const fn invalid_source(&self) -> Option<&E> {
182 match self {
183 Self::Invalid { source, .. } => Some(source),
184 Self::Incomplete { .. } => None,
185 }
186 }
187
188 /// Returns invalid units that may be consumed by a non-strict policy.
189 ///
190 /// # Returns
191 ///
192 /// Returns `Some(consumed)` for [`Invalid`](Self::Invalid), or `None` when
193 /// the failure is incomplete or invalid with unknown consumption.
194 #[inline]
195 #[must_use]
196 pub const fn consumed_units(&self) -> Option<NonZeroUsize> {
197 match self {
198 Self::Invalid { consumed, .. } => *consumed,
199 Self::Incomplete { .. } => None,
200 }
201 }
202}