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 ::base64::engine::GeneralPurpose;
12use ::base64::engine::general_purpose::STANDARD;
13use ::base64::engine::general_purpose::STANDARD_NO_PAD;
14use ::base64::engine::general_purpose::URL_SAFE;
15use ::base64::engine::general_purpose::URL_SAFE_NO_PAD;
16use qubit_codec::ValueDecoder;
17use qubit_codec::ValueEncoder;
18
19use crate::MiscCodecError;
20use crate::MiscCodecResult;
21
22/// Stable identity for the standard padded Base64 configuration.
23static STANDARD_ENGINE: GeneralPurpose = STANDARD;
24/// Stable identity for the standard unpadded Base64 configuration.
25static STANDARD_NO_PAD_ENGINE: GeneralPurpose = STANDARD_NO_PAD;
26/// Stable identity for the URL-safe padded Base64 configuration.
27static URL_SAFE_ENGINE: GeneralPurpose = URL_SAFE;
28/// Stable identity for the URL-safe unpadded Base64 configuration.
29static URL_SAFE_NO_PAD_ENGINE: GeneralPurpose = URL_SAFE_NO_PAD;
30
31/// Encodes and decodes Base64 byte strings.
32///
33/// This facade intentionally remains a whole-value codec backed by the
34/// `base64` crate. Final partial quantum handling and optional `=` padding are
35/// facade/transcoder responsibilities, not part of the low-level quantum codec.
36/// Decoding is strict: each constructor accepts only its configured alphabet
37/// and padding policy, rejects whitespace and non-canonical trailing bits, and
38/// does not permit padded and unpadded partial groups to be interchanged.
39/// Complete three-byte groups such as `TWFu` need no `=` and are accepted by
40/// both padding policies.
41///
42/// # Examples
43///
44/// ```
45/// use qubit_codec_misc::Base64Codec;
46///
47/// let codec = Base64Codec::standard();
48/// assert_eq!(codec.encode(b"hello"), "aGVsbG8=");
49/// ```
50#[must_use]
51#[derive(Debug, Clone, Copy)]
52pub struct Base64Codec {
53    /// Static engine that defines this codec's alphabet and padding policy.
54    engine: &'static GeneralPurpose,
55}
56
57impl PartialEq for Base64Codec {
58    // Every constructor refers to one of this module's four explicit static
59    // engines, so engine address identity represents the fixed configuration.
60    // Do not construct this facade from references to dependency const values.
61    #[inline]
62    fn eq(&self, other: &Self) -> bool {
63        core::ptr::eq(self.engine, other.engine)
64    }
65}
66
67impl Eq for Base64Codec {}
68
69impl Base64Codec {
70    /// Creates a standard Base64 codec that requires canonical padding for a
71    /// partial final group. Complete groups need no `=`.
72    ///
73    /// # Returns
74    /// Standard Base64 codec.
75    #[inline]
76    pub fn standard() -> Self {
77        Self {
78            engine: &STANDARD_ENGINE,
79        }
80    }
81
82    /// Creates a standard Base64 codec that rejects padding and accepts
83    /// unpadded partial final groups.
84    ///
85    /// # Returns
86    /// Standard no-padding Base64 codec.
87    #[inline]
88    pub fn standard_no_pad() -> Self {
89        Self {
90            engine: &STANDARD_NO_PAD_ENGINE,
91        }
92    }
93
94    /// Creates a URL-safe Base64 codec that requires canonical padding for a
95    /// partial final group. Complete groups need no `=`.
96    ///
97    /// # Returns
98    /// URL-safe Base64 codec.
99    #[inline]
100    pub fn url_safe() -> Self {
101        Self {
102            engine: &URL_SAFE_ENGINE,
103        }
104    }
105
106    /// Creates a URL-safe Base64 codec that rejects padding and accepts
107    /// unpadded partial final groups.
108    ///
109    /// # Returns
110    /// URL-safe no-padding Base64 codec.
111    #[inline]
112    pub fn url_safe_no_pad() -> Self {
113        Self {
114            engine: &URL_SAFE_NO_PAD_ENGINE,
115        }
116    }
117
118    /// Encodes bytes into Base64 text.
119    ///
120    /// # Parameters
121    /// - `bytes`: Bytes to encode.
122    ///
123    /// # Returns
124    /// Encoded Base64 text.
125    #[inline]
126    #[must_use]
127    pub fn encode(&self, bytes: &[u8]) -> String {
128        self.engine.encode(bytes)
129    }
130
131    /// Decodes Base64 text into bytes.
132    ///
133    /// # Parameters
134    /// - `text`: Base64 text.
135    ///
136    /// # Returns
137    /// Decoded bytes.
138    ///
139    /// # Errors
140    /// Returns [`MiscCodecError::InvalidBase64`] when `text` uses another
141    /// alphabet, violates this codec's padding policy, contains whitespace, or
142    /// has non-canonical trailing bits.
143    #[inline]
144    pub fn decode(&self, text: &str) -> MiscCodecResult<Vec<u8>> {
145        self.engine.decode(text).map_err(MiscCodecError::from)
146    }
147}
148
149impl Default for Base64Codec {
150    /// Creates a standard Base64 codec with padding.
151    #[inline]
152    fn default() -> Self {
153        Self::standard()
154    }
155}
156
157impl ValueEncoder<[u8]> for Base64Codec {
158    type Error = MiscCodecError;
159    type Output = String;
160
161    /// Encodes bytes into Base64 text.
162    #[inline]
163    fn encode(&mut self, input: &[u8]) -> Result<Self::Output, Self::Error> {
164        Ok(Base64Codec::encode(self, input))
165    }
166}
167
168impl ValueDecoder<str> for Base64Codec {
169    type Error = MiscCodecError;
170    type Output = Vec<u8>;
171
172    /// Decodes Base64 text into bytes.
173    #[inline]
174    fn decode(&mut self, input: &str) -> Result<Self::Output, Self::Error> {
175        Base64Codec::decode(self, input)
176    }
177}