Skip to main content

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}