Skip to main content

base64_ng/v2/
ordinary_string.rs

1//! Policy-carrying ordinary Base64 strings.
2
3use alloc::string::String;
4
5use super::{
6    ordinary::OneShotError,
7    specifications::{Base64, Codec, CodecSettings},
8};
9
10/// An owned ordinary Base64 string validated by one exact codec policy.
11///
12/// The value retains the [`Base64<S>`] used to encode or validate its text, so
13/// its [`decode`](Self::decode) methods cannot accidentally select a different
14/// alphabet, padding, or trailing-bit policy. Construction either encodes
15/// bytes through that codec or validates complete encoded text before
16/// ownership is returned. There is deliberately no mutable string access.
17///
18/// This is an ordinary, visibly printable, cloneable value. It performs no
19/// cleanup and is not suitable for keys, tokens, passwords, or other secret
20/// material. Use the `secret` module for secret-bearing data.
21#[derive(Clone, Debug, Eq, Hash, PartialEq)]
22pub struct Base64String<S: Codec> {
23    codec: Base64<S>,
24    encoded: String,
25}
26
27impl<S: Codec> Base64String<S> {
28    /// Encodes bytes and retains the exact codec policy with the result.
29    ///
30    /// Allocation and length errors use the same contract as
31    /// [`Base64::encode_to_string`].
32    pub fn encode(codec: Base64<S>, input: &[u8]) -> Result<Self, OneShotError> {
33        let encoded = codec.encode_to_string(input)?;
34        Ok(Self { codec, encoded })
35    }
36
37    /// Validates and adopts an existing owned string without copying it.
38    ///
39    /// The complete string must satisfy the supplied codec's decode policy.
40    /// On error, this function consumes and drops the supplied ordinary
41    /// string.
42    pub fn from_string(codec: Base64<S>, encoded: String) -> Result<Self, OneShotError> {
43        codec.validate(encoded.as_bytes())?;
44        Ok(Self { codec, encoded })
45    }
46
47    /// Validates and copies an encoded string slice into owned storage.
48    ///
49    /// Validation completes before allocation. The copy uses
50    /// `try_reserve_exact`, returning [`OneShotError::AllocationFailed`] if the
51    /// reservation cannot be made.
52    pub fn parse(codec: Base64<S>, encoded: &str) -> Result<Self, OneShotError> {
53        Self::parse_with_reserver(codec, encoded, |output, required| {
54            output
55                .try_reserve_exact(required)
56                .map_err(|_| OneShotError::AllocationFailed {
57                    requested: required,
58                })
59        })
60    }
61
62    fn parse_with_reserver<F>(
63        codec: Base64<S>,
64        encoded: &str,
65        reserve: F,
66    ) -> Result<Self, OneShotError>
67    where
68        F: FnOnce(&mut String, usize) -> Result<(), OneShotError>,
69    {
70        codec.validate(encoded.as_bytes())?;
71        let mut owned = String::new();
72        reserve(&mut owned, encoded.len())?;
73        owned.push_str(encoded);
74        Ok(Self {
75            codec,
76            encoded: owned,
77        })
78    }
79
80    #[cfg(test)]
81    pub(super) fn parse_with_injected_reserver<F>(
82        codec: Base64<S>,
83        encoded: &str,
84        reserve: F,
85    ) -> Result<Self, OneShotError>
86    where
87        F: FnOnce(&mut String, usize) -> Result<(), OneShotError>,
88    {
89        Self::parse_with_reserver(codec, encoded, reserve)
90    }
91
92    /// Returns the exact codec retained by this string.
93    #[must_use]
94    pub const fn codec(&self) -> &Base64<S> {
95        &self.codec
96    }
97
98    /// Returns the retained codec settings.
99    #[must_use]
100    pub fn settings(&self) -> CodecSettings {
101        self.codec.settings()
102    }
103
104    /// Returns the validated encoded text.
105    ///
106    /// Passing this ordinary view to another codec can deliberately discard
107    /// the retained policy. Use [`Self::decode`] to preserve it.
108    #[must_use]
109    pub fn as_str(&self) -> &str {
110        self.encoded.as_str()
111    }
112
113    /// Returns the validated encoded bytes.
114    ///
115    /// Passing this ordinary view to another codec can deliberately discard
116    /// the retained policy. Use [`Self::decode`] to preserve it.
117    #[must_use]
118    pub fn as_bytes(&self) -> &[u8] {
119        self.encoded.as_bytes()
120    }
121
122    /// Returns the encoded byte length.
123    #[must_use]
124    pub fn len(&self) -> usize {
125        self.encoded.len()
126    }
127
128    /// Returns whether the encoded text is empty.
129    #[must_use]
130    pub fn is_empty(&self) -> bool {
131        self.encoded.is_empty()
132    }
133
134    /// Decodes the validated text with its retained codec.
135    pub fn decode(&self) -> Result<alloc::vec::Vec<u8>, OneShotError> {
136        self.codec.decode_to_vec(self.encoded.as_bytes())
137    }
138
139    /// Decodes with an exact maximum output length.
140    pub fn decode_with_limit(
141        &self,
142        max_output_len: usize,
143    ) -> Result<alloc::vec::Vec<u8>, OneShotError> {
144        self.codec
145            .decode_to_vec_with_limit(self.encoded.as_bytes(), max_output_len)
146    }
147
148    /// Consumes the wrapper and returns the validated ordinary string.
149    ///
150    /// The returned `String` no longer carries the codec policy.
151    #[must_use]
152    pub fn into_string(self) -> String {
153        self.encoded
154    }
155}
156
157impl<S: Codec> AsRef<str> for Base64String<S> {
158    fn as_ref(&self) -> &str {
159        self.as_str()
160    }
161}
162
163impl<S: Codec> AsRef<[u8]> for Base64String<S> {
164    fn as_ref(&self) -> &[u8] {
165        self.as_bytes()
166    }
167}
168
169impl<S: Codec> core::fmt::Display for Base64String<S> {
170    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
171        formatter.write_str(self.as_str())
172    }
173}