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    #[inline]
48    fn eq(&self, other: &Self) -> bool {
49        self.config == other.config
50    }
51}
52
53impl Eq for Base64Codec {}
54
55impl Base64Codec {
56    /// Creates a standard Base64 codec that requires canonical padding for a
57    /// partial final group. Complete groups need no `=`.
58    ///
59    /// # Returns
60    /// Standard Base64 codec.
61    #[inline]
62    pub fn standard() -> Self {
63        Self {
64            config: Base64Config::Standard,
65        }
66    }
67
68    /// Creates a standard Base64 codec that rejects padding and accepts
69    /// unpadded partial final groups.
70    ///
71    /// # Returns
72    /// Standard no-padding Base64 codec.
73    #[inline]
74    pub fn standard_no_pad() -> Self {
75        Self {
76            config: Base64Config::StandardNoPad,
77        }
78    }
79
80    /// Creates a URL-safe Base64 codec that requires canonical padding for a
81    /// partial final group. Complete groups need no `=`.
82    ///
83    /// # Returns
84    /// URL-safe Base64 codec.
85    #[inline]
86    pub fn url_safe() -> Self {
87        Self {
88            config: Base64Config::UrlSafe,
89        }
90    }
91
92    /// Creates a URL-safe Base64 codec that rejects padding and accepts
93    /// unpadded partial final groups.
94    ///
95    /// # Returns
96    /// URL-safe no-padding Base64 codec.
97    #[inline]
98    pub fn url_safe_no_pad() -> Self {
99        Self {
100            config: Base64Config::UrlSafeNoPad,
101        }
102    }
103
104    /// Encodes bytes into Base64 text.
105    ///
106    /// # Parameters
107    /// - `bytes`: Bytes to encode.
108    ///
109    /// # Returns
110    /// Encoded Base64 text.
111    #[inline]
112    #[must_use]
113    pub fn encode(&self, bytes: &[u8]) -> String {
114        self.config.engine().encode(bytes)
115    }
116
117    /// Decodes Base64 text into bytes.
118    ///
119    /// # Parameters
120    /// - `text`: Base64 text.
121    ///
122    /// # Returns
123    /// Decoded bytes.
124    ///
125    /// # Errors
126    /// Returns [`MiscCodecError::InvalidBase64`] when `text` uses another
127    /// alphabet, violates this codec's padding policy, contains whitespace, or
128    /// has non-canonical trailing bits.
129    #[inline]
130    pub fn decode(&self, text: &str) -> MiscCodecResult<Vec<u8>> {
131        self.config.engine().decode(text).map_err(MiscCodecError::from)
132    }
133
134    /// Creates an independent encoder with this codec's configuration.
135    ///
136    /// The returned encoder does not borrow this codec, uses fixed-size state,
137    /// and must be reset before streaming.
138    ///
139    /// # Returns
140    /// An independent streaming encoder with the same alphabet and padding
141    /// policy.
142    #[inline]
143    pub fn encoder(&self) -> Base64Encoder {
144        Base64Encoder::from_config(self.config)
145    }
146
147    /// Creates an independent decoder with this codec's configuration.
148    ///
149    /// The returned decoder does not borrow this codec, uses fixed-size state,
150    /// and must be reset before streaming.
151    ///
152    /// # Returns
153    /// An independent streaming decoder with the same alphabet and padding
154    /// policy.
155    #[inline]
156    pub fn decoder(&self) -> Base64Decoder {
157        Base64Decoder::from_config(self.config)
158    }
159}
160
161impl Default for Base64Codec {
162    /// Creates a standard Base64 codec with padding.
163    #[inline]
164    fn default() -> Self {
165        Self::standard()
166    }
167}
168
169impl ValueEncoder<[u8]> for Base64Codec {
170    type Error = MiscCodecError;
171    type Output = String;
172
173    /// Encodes bytes into Base64 text without a fallible operation.
174    ///
175    /// # Parameters
176    /// - `input`: Bytes to encode.
177    ///
178    /// # Returns
179    /// Encoded Base64 text.
180    ///
181    /// # Errors
182    /// This implementation does not return an error.
183    #[inline]
184    fn encode(&mut self, input: &[u8]) -> Result<Self::Output, Self::Error> {
185        Ok(Base64Codec::encode(self, input))
186    }
187}
188
189impl ValueDecoder<str> for Base64Codec {
190    type Error = MiscCodecError;
191    type Output = Vec<u8>;
192
193    /// Decodes Base64 text into bytes using this codec's alphabet and padding
194    /// policy.
195    ///
196    /// # Parameters
197    /// - `input`: Base64 text to decode.
198    ///
199    /// # Returns
200    /// Decoded bytes.
201    ///
202    /// # Errors
203    /// Returns [`MiscCodecError::InvalidBase64`] when the input is malformed
204    /// or violates this codec's alphabet or padding policy.
205    #[inline]
206    fn decode(&mut self, input: &str) -> Result<Self::Output, Self::Error> {
207        Base64Codec::decode(self, input)
208    }
209}