1use super::{
4 chunks::{EncodedChunk, EncodedChunks},
5 contracts::BackendFault,
6 ordinary::OneShotError,
7 specifications::{Base64, Codec, CodecSettings},
8};
9
10#[derive(Clone, Copy, Debug, Eq, PartialEq)]
12#[non_exhaustive]
13pub enum FormatWriteError {
14 Encoding(OneShotError),
16 Formatter {
18 confirmed: usize,
20 },
21 Backend {
23 fault: BackendFault,
25 confirmed: usize,
27 },
28}
29
30impl FormatWriteError {
31 #[must_use]
37 pub const fn confirmed(&self) -> usize {
38 match self {
39 Self::Encoding(_) => 0,
40 Self::Formatter { confirmed } | Self::Backend { confirmed, .. } => *confirmed,
41 }
42 }
43}
44
45impl core::fmt::Display for FormatWriteError {
46 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
47 match self {
48 Self::Encoding(error) => error.fmt(formatter),
49 Self::Formatter { confirmed } => write!(
50 formatter,
51 "formatter failed after {confirmed} confirmed Base64 bytes"
52 ),
53 Self::Backend { fault, confirmed } => write!(
54 formatter,
55 "Base64 backend {} failed after {confirmed} confirmed bytes",
56 fault.as_str()
57 ),
58 }
59 }
60}
61
62#[cfg(feature = "std")]
63impl std::error::Error for FormatWriteError {
64 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
65 match self {
66 Self::Encoding(error) => Some(error),
67 Self::Formatter { .. } | Self::Backend { .. } => None,
68 }
69 }
70}
71
72pub trait CountedSink {
80 type Error;
82
83 fn write(&mut self, bytes: &[u8]) -> Result<usize, Self::Error>;
85}
86
87#[derive(Debug)]
89#[non_exhaustive]
90pub enum CountedWriteError<E> {
91 Encoding(OneShotError),
93 Sink {
95 error: E,
97 committed: usize,
99 },
100 WriteZero {
102 committed: usize,
104 },
105 InvalidCount {
107 reported: usize,
109 offered: usize,
111 committed: usize,
113 },
114 Backend {
116 fault: BackendFault,
118 committed: usize,
120 },
121}
122
123impl<E> CountedWriteError<E> {
124 #[must_use]
126 pub const fn committed(&self) -> usize {
127 match self {
128 Self::Encoding(_) => 0,
129 Self::Sink { committed, .. }
130 | Self::WriteZero { committed }
131 | Self::InvalidCount { committed, .. }
132 | Self::Backend { committed, .. } => *committed,
133 }
134 }
135}
136
137impl<E: core::fmt::Display> core::fmt::Display for CountedWriteError<E> {
138 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
139 match self {
140 Self::Encoding(error) => error.fmt(formatter),
141 Self::Sink { error, committed } => write!(
142 formatter,
143 "counted sink failed after {committed} committed Base64 bytes: {error}"
144 ),
145 Self::WriteZero { committed } => write!(
146 formatter,
147 "counted sink accepted zero bytes after {committed} committed Base64 bytes"
148 ),
149 Self::InvalidCount {
150 reported,
151 offered,
152 committed,
153 } => write!(
154 formatter,
155 "counted sink reported {reported} bytes for a {offered}-byte write after \
156 {committed} committed Base64 bytes"
157 ),
158 Self::Backend { fault, committed } => write!(
159 formatter,
160 "Base64 backend {} failed after {committed} committed bytes",
161 fault.as_str()
162 ),
163 }
164 }
165}
166
167#[cfg(feature = "std")]
168impl<E: std::error::Error + 'static> std::error::Error for CountedWriteError<E> {
169 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
170 match self {
171 Self::Encoding(error) => Some(error),
172 Self::Sink { error, .. } => Some(error),
173 Self::WriteZero { .. } | Self::InvalidCount { .. } | Self::Backend { .. } => None,
174 }
175 }
176}
177
178#[derive(Clone, Copy)]
184pub struct EncodedDisplay<'a> {
185 settings: CodecSettings,
186 input: &'a [u8],
187}
188
189impl core::fmt::Debug for EncodedDisplay<'_> {
190 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
191 formatter
192 .debug_struct("EncodedDisplay")
193 .field("input_len", &self.input.len())
194 .finish_non_exhaustive()
195 }
196}
197
198impl core::fmt::Display for EncodedDisplay<'_> {
199 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
200 for chunk in EncodedChunks::new(self.settings, self.input) {
201 formatter.write_str(chunk_text(&chunk).map_err(|_| core::fmt::Error)?)?;
202 }
203 Ok(())
204 }
205}
206
207impl<S: Codec> Base64<S> {
208 pub fn display<'a>(&self, input: &'a [u8]) -> Result<EncodedDisplay<'a>, OneShotError> {
210 self.encoded_len(input.len())?;
211 Ok(EncodedDisplay {
212 settings: self.settings(),
213 input,
214 })
215 }
216
217 pub fn encode_to_fmt<W: core::fmt::Write + ?Sized>(
223 &self,
224 input: &[u8],
225 writer: &mut W,
226 ) -> Result<usize, FormatWriteError> {
227 let chunks = self
228 .encoded_chunks(input)
229 .map_err(FormatWriteError::Encoding)?;
230 let mut confirmed = 0;
231 for chunk in chunks {
232 let text = chunk_text(&chunk).map_err(|_| FormatWriteError::Backend {
233 fault: BackendFault::ImpossibleState,
234 confirmed,
235 })?;
236 writer
237 .write_str(text)
238 .map_err(|_| FormatWriteError::Formatter { confirmed })?;
239 confirmed += text.len();
240 }
241 Ok(confirmed)
242 }
243
244 pub fn encode_to_counted<W: CountedSink + ?Sized>(
246 &self,
247 input: &[u8],
248 writer: &mut W,
249 ) -> Result<usize, CountedWriteError<W::Error>> {
250 let chunks = self
251 .encoded_chunks(input)
252 .map_err(CountedWriteError::Encoding)?;
253 let mut committed = 0;
254 for chunk in chunks {
255 let mut pending = chunk.as_bytes();
256 while !pending.is_empty() {
257 let written = writer
258 .write(pending)
259 .map_err(|error| CountedWriteError::Sink { error, committed })?;
260 if written == 0 {
261 return Err(CountedWriteError::WriteZero { committed });
262 }
263 if written > pending.len() {
264 return Err(CountedWriteError::InvalidCount {
265 reported: written,
266 offered: pending.len(),
267 committed,
268 });
269 }
270 committed += written;
271 pending = &pending[written..];
272 }
273 }
274 Ok(committed)
275 }
276}
277
278fn chunk_text(chunk: &EncodedChunk) -> Result<&str, core::str::Utf8Error> {
279 chunk.as_str()
280}