fluent_uri/pct_enc/
estring.rs

1use super::{Assert, EStr, Encoder};
2use crate::utf8::Utf8Chunks;
3use alloc::{borrow::ToOwned, string::String};
4use core::{borrow::Borrow, cmp::Ordering, fmt, hash, marker::PhantomData, ops::Deref};
5
6/// A percent-encoded, growable string.
7///
8/// The borrowed counterpart of `EString` is [`EStr`].
9/// See its documentation for the meaning of the type parameter `E`.
10///
11/// # Comparison
12///
13/// `EString`s are compared [lexicographically](Ord#lexicographical-comparison)
14/// by their byte values. Normalization is **not** performed prior to comparison.
15///
16/// # Examples
17///
18/// Encode key-value pairs to a query string and use it to build a URI reference:
19///
20/// ```
21/// use fluent_uri::{
22///     pct_enc::{
23///         encoder::{Data, Query},
24///         EStr, EString, Encoder, Table,
25///     },
26///     UriRef,
27/// };
28///
29/// let pairs = [("name", "张三"), ("speech", "¡Olé!")];
30/// let mut buf = EString::<Query>::new();
31/// for (k, v) in pairs {
32///     if !buf.is_empty() {
33///         buf.push('&');
34///     }
35///
36///     // WARNING: Absolutely do not confuse data with delimiters!
37///     // Use `Data` (or `IData`) to encode data contained in a URI
38///     // (or an IRI) unless you know what you're doing!
39///     buf.encode_str::<Data>(k);
40///     buf.push('=');
41///     buf.encode_str::<Data>(v);
42/// }
43///
44/// assert_eq!(buf, "name=%E5%BC%A0%E4%B8%89&speech=%C2%A1Ol%C3%A9%21");
45///
46/// let uri_ref = UriRef::builder()
47///     .path(EStr::EMPTY)
48///     .query(&buf)
49///     .build()
50///     .unwrap();
51/// assert_eq!(uri_ref.as_str(), "?name=%E5%BC%A0%E4%B8%89&speech=%C2%A1Ol%C3%A9%21");
52/// ```
53///
54/// Encode a path whose segments may contain the slash (`'/'`) character
55/// by using a custom sub-encoder:
56///
57/// ```
58/// use fluent_uri::pct_enc::{encoder::Path, EString, Encoder, Table};
59///
60/// struct PathSegment;
61///
62/// impl Encoder for PathSegment {
63///     const TABLE: &'static Table = &Path::TABLE.sub(&Table::new(b"/"));
64/// }
65///
66/// let mut path = EString::<Path>::new();
67/// path.push('/');
68/// path.encode_str::<PathSegment>("foo/bar");
69///
70/// assert_eq!(path, "/foo%2Fbar");
71/// ```
72#[derive(Clone, Default)]
73pub struct EString<E: Encoder> {
74    pub(crate) buf: String,
75    encoder: PhantomData<E>,
76}
77
78impl<E: Encoder> Deref for EString<E> {
79    type Target = EStr<E>;
80
81    fn deref(&self) -> &EStr<E> {
82        EStr::new_validated(&self.buf)
83    }
84}
85
86impl<E: Encoder> EString<E> {
87    pub(crate) fn new_validated(buf: String) -> Self {
88        Self {
89            buf,
90            encoder: PhantomData,
91        }
92    }
93
94    /// Creates a new empty `EString`.
95    #[must_use]
96    pub fn new() -> Self {
97        Self::new_validated(String::new())
98    }
99
100    /// Creates a new empty `EString` with at least the specified capacity.
101    #[must_use]
102    pub fn with_capacity(capacity: usize) -> Self {
103        Self::new_validated(String::with_capacity(capacity))
104    }
105
106    /// Coerces to an `EStr` slice.
107    #[must_use]
108    pub fn as_estr(&self) -> &EStr<E> {
109        self
110    }
111
112    /// Returns this `EString`'s capacity, in bytes.
113    #[must_use]
114    pub fn capacity(&self) -> usize {
115        self.buf.capacity()
116    }
117
118    /// Encodes a string with a sub-encoder and appends the result onto the end of this `EString`.
119    ///
120    /// A character will be preserved if `SubE::TABLE` [allows] it and percent-encoded otherwise.
121    ///
122    /// In most cases, use [`Data`] (for URI) or [`IData`] (for IRI) as the sub-encoder.
123    /// When using other sub-encoders, make sure that `SubE::TABLE` does not [allow][allows]
124    /// the component delimiters that delimit the data.
125    ///
126    /// Note that this method will **not** encode `U+0020` (space) as `U+002B` (+).
127    ///
128    /// If you need to encode arbitrary bytes, use [`encode_bytes`][Self::encode_bytes] instead.
129    ///
130    /// [allows]: super::Table::allows
131    /// [`Data`]: super::encoder::Data
132    /// [`IData`]: super::encoder::IData
133    ///
134    /// # Panics
135    ///
136    /// Panics at compile time if `SubE` is not a [sub-encoder](Encoder#sub-encoders) of `E`,
137    /// or if `SubE::TABLE` does not [allow percent-encoded octets].
138    ///
139    /// [allow percent-encoded octets]: super::Table::allows_pct_encoded
140    pub fn encode_str<SubE: Encoder>(&mut self, s: &str) {
141        () = Assert::<SubE, E>::L_IS_SUB_ENCODER_OF_R;
142        () = EStr::<SubE>::ASSERT_ALLOWS_PCT_ENCODED;
143
144        for ch in s.chars() {
145            SubE::TABLE.encode(ch, &mut self.buf);
146        }
147    }
148
149    /// Encodes a byte sequence with a sub-encoder and appends the result onto the end of this `EString`.
150    ///
151    /// A byte will be preserved if it is part of a UTF-8-encoded character
152    /// that `SubE::TABLE` [allows] and percent-encoded otherwise.
153    ///
154    /// In most cases, use [`Data`] (for URI) or [`IData`] (for IRI) as the sub-encoder.
155    /// When using other sub-encoders, make sure that `SubE::TABLE` does not [allow][allows]
156    /// the component delimiters that delimit the data.
157    ///
158    /// Note that this method will **not** encode `0x20` (space) as `U+002B` (+).
159    ///
160    /// If you need to encode a string, use [`encode_str`][Self::encode_str] instead.
161    ///
162    /// [allows]: super::Table::allows
163    /// [`Data`]: super::encoder::Data
164    /// [`IData`]: super::encoder::IData
165    ///
166    /// # Panics
167    ///
168    /// Panics at compile time if `SubE` is not a [sub-encoder](Encoder#sub-encoders) of `E`,
169    /// or if `SubE::TABLE` does not [allow percent-encoded octets].
170    ///
171    /// [allow percent-encoded octets]: super::Table::allows_pct_encoded
172    pub fn encode_bytes<SubE: Encoder>(&mut self, bytes: &[u8]) {
173        () = Assert::<SubE, E>::L_IS_SUB_ENCODER_OF_R;
174        () = EStr::<SubE>::ASSERT_ALLOWS_PCT_ENCODED;
175
176        for chunk in Utf8Chunks::new(bytes) {
177            for ch in chunk.valid().chars() {
178                SubE::TABLE.encode(ch, &mut self.buf);
179            }
180            for &x in chunk.invalid() {
181                super::encode_byte(x, &mut self.buf);
182            }
183        }
184    }
185
186    /// Appends an unencoded character onto the end of this `EString`.
187    ///
188    /// # Panics
189    ///
190    /// Panics if `E::TABLE` does not [allow] the character.
191    ///
192    /// [allow]: super::Table::allows
193    pub fn push(&mut self, ch: char) {
194        assert!(E::TABLE.allows(ch), "table does not allow the char");
195        self.buf.push(ch);
196    }
197
198    /// Appends an `EStr` slice onto the end of this `EString`.
199    pub fn push_estr(&mut self, s: &EStr<E>) {
200        self.buf.push_str(s.as_str());
201    }
202
203    /// Truncates this `EString`, removing all contents.
204    pub fn clear(&mut self) {
205        self.buf.clear();
206    }
207
208    /// Consumes this `EString` and yields the underlying `String`.
209    #[must_use]
210    pub fn into_string(self) -> String {
211        self.buf
212    }
213}
214
215impl<E: Encoder> AsRef<EStr<E>> for EString<E> {
216    fn as_ref(&self) -> &EStr<E> {
217        self
218    }
219}
220
221impl<E: Encoder> AsRef<str> for EString<E> {
222    fn as_ref(&self) -> &str {
223        &self.buf
224    }
225}
226
227impl<E: Encoder> Borrow<EStr<E>> for EString<E> {
228    fn borrow(&self) -> &EStr<E> {
229        self
230    }
231}
232
233impl<E: Encoder> From<&EStr<E>> for EString<E> {
234    fn from(s: &EStr<E>) -> Self {
235        s.to_owned()
236    }
237}
238
239impl<E: Encoder> PartialEq for EString<E> {
240    fn eq(&self, other: &Self) -> bool {
241        self.as_str() == other.as_str()
242    }
243}
244
245impl<E: Encoder> PartialEq<EStr<E>> for EString<E> {
246    fn eq(&self, other: &EStr<E>) -> bool {
247        self.as_str() == other.as_str()
248    }
249}
250
251impl<E: Encoder> PartialEq<EString<E>> for EStr<E> {
252    fn eq(&self, other: &EString<E>) -> bool {
253        self.as_str() == other.as_str()
254    }
255}
256
257impl<E: Encoder> PartialEq<&EStr<E>> for EString<E> {
258    fn eq(&self, other: &&EStr<E>) -> bool {
259        self.as_str() == other.as_str()
260    }
261}
262
263impl<E: Encoder> PartialEq<EString<E>> for &EStr<E> {
264    fn eq(&self, other: &EString<E>) -> bool {
265        self.as_str() == other.as_str()
266    }
267}
268
269impl<E: Encoder> PartialEq<str> for EString<E> {
270    fn eq(&self, other: &str) -> bool {
271        self.as_str() == other
272    }
273}
274
275impl<E: Encoder> PartialEq<EString<E>> for str {
276    fn eq(&self, other: &EString<E>) -> bool {
277        self == other.as_str()
278    }
279}
280
281impl<E: Encoder> PartialEq<&str> for EString<E> {
282    fn eq(&self, other: &&str) -> bool {
283        self.as_str() == *other
284    }
285}
286
287impl<E: Encoder> PartialEq<EString<E>> for &str {
288    fn eq(&self, other: &EString<E>) -> bool {
289        *self == other.as_str()
290    }
291}
292
293impl<E: Encoder> Eq for EString<E> {}
294
295impl<E: Encoder> hash::Hash for EString<E> {
296    fn hash<H: hash::Hasher>(&self, state: &mut H) {
297        self.buf.hash(state);
298    }
299}
300
301impl<E: Encoder> PartialOrd for EString<E> {
302    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
303        Some(self.cmp(other))
304    }
305}
306
307impl<E: Encoder> Ord for EString<E> {
308    fn cmp(&self, other: &Self) -> Ordering {
309        self.inner.cmp(&other.inner)
310    }
311}
312
313impl<E: Encoder> fmt::Debug for EString<E> {
314    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
315        self.as_str().fmt(f)
316    }
317}
318
319impl<E: Encoder> fmt::Display for EString<E> {
320    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
321        self.as_str().fmt(f)
322    }
323}