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}