qubit_codec_misc/base64_codec.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//! Base64 byte codec.
9
10use ::base64::Engine;
11use qubit_codec::ValueDecoder;
12use qubit_codec::ValueEncoder;
13
14use crate::Base64Decoder;
15use crate::Base64Encoder;
16use crate::MiscCodecError;
17use crate::MiscCodecResult;
18use crate::internal::Base64Config;
19
20/// Encodes and decodes Base64 byte strings.
21///
22/// This facade intentionally remains a whole-value codec backed by the
23/// `base64` crate. Final partial quantum handling and optional `=` padding are
24/// facade/transcoder responsibilities, not part of the low-level quantum codec.
25/// Decoding is strict: each constructor accepts only its configured alphabet
26/// and padding policy, rejects whitespace and non-canonical trailing bits, and
27/// does not permit padded and unpadded partial groups to be interchanged.
28/// Complete three-byte groups such as `TWFu` need no `=` and are accepted by
29/// both padding policies.
30///
31/// # Examples
32///
33/// ```
34/// use qubit_codec_misc::Base64Codec;
35///
36/// let codec = Base64Codec::standard();
37/// assert_eq!(codec.encode(b"hello"), "aGVsbG8=");
38/// ```
39#[must_use]
40#[derive(Debug, Clone, Copy)]
41pub struct Base64Codec {
42 /// Configuration that defines this codec's alphabet and padding policy.
43 config: Base64Config,
44}
45
46impl PartialEq for Base64Codec {
47 /// Compares codecs by their alphabet and padding configuration.
48 ///
49 /// # Parameters
50 /// - `other`: Codec configuration to compare with this one.
51 #[inline]
52 fn eq(&self, other: &Self) -> bool {
53 self.config == other.config
54 }
55}
56
57impl Eq for Base64Codec {}
58
59impl Base64Codec {
60 /// Creates a standard Base64 codec that requires canonical padding for a
61 /// partial final group. Complete groups need no `=`.
62 ///
63 /// # Returns
64 /// Standard Base64 codec.
65 #[inline]
66 pub fn standard() -> Self {
67 Self {
68 config: Base64Config::Standard,
69 }
70 }
71
72 /// Creates a standard Base64 codec that rejects padding and accepts
73 /// unpadded partial final groups.
74 ///
75 /// # Returns
76 /// Standard no-padding Base64 codec.
77 #[inline]
78 pub fn standard_no_pad() -> Self {
79 Self {
80 config: Base64Config::StandardNoPad,
81 }
82 }
83
84 /// Creates a URL-safe Base64 codec that requires canonical padding for a
85 /// partial final group. Complete groups need no `=`.
86 ///
87 /// # Returns
88 /// URL-safe Base64 codec.
89 #[inline]
90 pub fn url_safe() -> Self {
91 Self {
92 config: Base64Config::UrlSafe,
93 }
94 }
95
96 /// Creates a URL-safe Base64 codec that rejects padding and accepts
97 /// unpadded partial final groups.
98 ///
99 /// # Returns
100 /// URL-safe no-padding Base64 codec.
101 #[inline]
102 pub fn url_safe_no_pad() -> Self {
103 Self {
104 config: Base64Config::UrlSafeNoPad,
105 }
106 }
107
108 /// Encodes bytes into Base64 text.
109 ///
110 /// # Parameters
111 /// - `bytes`: Bytes to encode.
112 ///
113 /// # Returns
114 /// Encoded Base64 text.
115 #[inline]
116 #[must_use]
117 pub fn encode(&self, bytes: &[u8]) -> String {
118 self.config.engine().encode(bytes)
119 }
120
121 /// Decodes Base64 text into bytes.
122 ///
123 /// # Parameters
124 /// - `text`: Base64 text.
125 ///
126 /// # Returns
127 /// Decoded bytes.
128 ///
129 /// # Errors
130 /// Returns [`MiscCodecError::InvalidBase64`] when `text` uses another
131 /// alphabet, violates this codec's padding policy, contains whitespace, or
132 /// has non-canonical trailing bits.
133 #[inline]
134 pub fn decode(&self, text: &str) -> MiscCodecResult<Vec<u8>> {
135 self.config.engine().decode(text).map_err(MiscCodecError::from)
136 }
137
138 /// Creates an independent encoder with this codec's configuration.
139 ///
140 /// The returned encoder does not borrow this codec, uses fixed-size state,
141 /// and must be reset before streaming.
142 ///
143 /// # Returns
144 /// An independent streaming encoder with the same alphabet and padding
145 /// policy.
146 #[inline]
147 pub fn encoder(&self) -> Base64Encoder {
148 Base64Encoder::from_config(self.config)
149 }
150
151 /// Creates an independent decoder with this codec's configuration.
152 ///
153 /// The returned decoder does not borrow this codec, uses fixed-size state,
154 /// and must be reset before streaming.
155 ///
156 /// # Returns
157 /// An independent streaming decoder with the same alphabet and padding
158 /// policy.
159 #[inline]
160 pub fn decoder(&self) -> Base64Decoder {
161 Base64Decoder::from_config(self.config)
162 }
163}
164
165impl Default for Base64Codec {
166 /// Creates a standard Base64 codec with padding.
167 #[inline]
168 fn default() -> Self {
169 Self::standard()
170 }
171}
172
173impl ValueEncoder<[u8]> for Base64Codec {
174 /// The encoding operation is infallible for byte slices.
175 type Error = MiscCodecError;
176
177 /// Encoded Base64 text.
178 type Output = String;
179
180 /// Encodes bytes into Base64 text without a fallible operation.
181 ///
182 /// # Parameters
183 /// - `input`: Bytes to encode.
184 ///
185 /// # Returns
186 /// Encoded Base64 text.
187 ///
188 /// # Errors
189 /// This implementation does not return an error.
190 #[inline]
191 fn encode(&mut self, input: &[u8]) -> Result<Self::Output, Self::Error> {
192 Ok(Base64Codec::encode(self, input))
193 }
194}
195
196impl ValueDecoder<str> for Base64Codec {
197 /// Decoding failures are reported as Base64 codec errors.
198 type Error = MiscCodecError;
199
200 /// Decoded bytes.
201 type Output = Vec<u8>;
202
203 /// Decodes Base64 text into bytes using this codec's alphabet and padding
204 /// policy.
205 ///
206 /// # Parameters
207 /// - `input`: Base64 text to decode.
208 ///
209 /// # Returns
210 /// Decoded bytes.
211 ///
212 /// # Errors
213 /// Returns [`MiscCodecError::InvalidBase64`] when the input is malformed
214 /// or violates this codec's alphabet or padding policy.
215 #[inline]
216 fn decode(&mut self, input: &str) -> Result<Self::Output, Self::Error> {
217 Base64Codec::decode(self, input)
218 }
219}