fluent_uri/encoding/
estring.rs

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