fluent_uri/pct_enc/estring.rs
1use super::{Assert, EStr, Encoder};
2use crate::{pct_enc::Encode, 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 /// [allows]: super::Table::allows
129 /// [`Data`]: super::encoder::Data
130 /// [`IData`]: super::encoder::IData
131 ///
132 /// # Panics
133 ///
134 /// Panics at compile time if `SubE` is not a [sub-encoder](Encoder#sub-encoders) of `E`,
135 /// or if `SubE::TABLE` does not [allow percent-encoded octets].
136 ///
137 /// [allow percent-encoded octets]: super::Table::allows_pct_encoded
138 pub fn encode_str<SubE: Encoder>(&mut self, s: &str) {
139 () = Assert::<SubE, E>::L_IS_SUB_ENCODER_OF_R;
140 () = EStr::<SubE>::ASSERT_ALLOWS_PCT_ENCODED;
141
142 for chunk in Encode::new(SubE::TABLE, s) {
143 self.buf.push_str(chunk.as_str());
144 }
145 }
146
147 /// Encodes a byte sequence with a sub-encoder and appends the result onto the end of this `EString`.
148 ///
149 /// A byte will be preserved if it is part of a UTF-8-encoded character
150 /// that `SubE::TABLE` [allows] and percent-encoded otherwise.
151 ///
152 /// In most cases, use [`Data`] (for URI) or [`IData`] (for IRI) as the sub-encoder.
153 /// When using other sub-encoders, make sure that `SubE::TABLE` does not [allow][allows]
154 /// the component delimiters that delimit the data.
155 ///
156 /// Note that this method will **not** encode `0x20` (space) as `U+002B` (+).
157 ///
158 /// If you need to encode a string, use [`encode_str`][Self::encode_str] instead.
159 ///
160 /// [allows]: super::Table::allows
161 /// [`Data`]: super::encoder::Data
162 /// [`IData`]: super::encoder::IData
163 ///
164 /// # Deprecation
165 ///
166 /// This method is deprecated because percent-encoding non-UTF-8 bytes is
167 /// a non-standard operation. If you're developing a new protocol, use
168 /// other encodings such as Base64 instead. If you absolutely must, here's
169 /// a workaround:
170 ///
171 /// ```
172 /// use fluent_uri::pct_enc::{encoder::Path, EStr, EString};
173 ///
174 /// let mut buf = EString::<Path>::new();
175 ///
176 /// for chunk in b"D\xFCrst".utf8_chunks() {
177 /// buf.encode_str::<Path>(chunk.valid());
178 /// for &x in chunk.invalid() {
179 /// buf.push_estr(EStr::encode_byte(x));
180 /// }
181 /// }
182 ///
183 /// assert_eq!(buf, "D%FCrst");
184 /// ```
185 ///
186 /// # Panics
187 ///
188 /// Panics at compile time if `SubE` is not a [sub-encoder](Encoder#sub-encoders) of `E`,
189 /// or if `SubE::TABLE` does not [allow percent-encoded octets].
190 ///
191 /// [allow percent-encoded octets]: super::Table::allows_pct_encoded
192 #[deprecated = "use `<[u8]>::utf8_chunks`, `EString::encode_str`, `EStr::encode_byte`, and `EString::push_estr` instead"]
193 pub fn encode_bytes<SubE: Encoder>(&mut self, bytes: &[u8]) {
194 () = Assert::<SubE, E>::L_IS_SUB_ENCODER_OF_R;
195 () = EStr::<SubE>::ASSERT_ALLOWS_PCT_ENCODED;
196
197 for chunk in Utf8Chunks::new(bytes) {
198 for chunk in Encode::new(SubE::TABLE, chunk.valid()) {
199 self.buf.push_str(chunk.as_str());
200 }
201 for &x in chunk.invalid() {
202 self.buf.push_str(super::encode_byte(x));
203 }
204 }
205 }
206
207 /// Appends an unencoded character onto the end of this `EString`.
208 ///
209 /// # Panics
210 ///
211 /// Panics if `E::TABLE` does not [allow] the character.
212 ///
213 /// [allow]: super::Table::allows
214 pub fn push(&mut self, ch: char) {
215 assert!(E::TABLE.allows(ch), "table does not allow the char");
216 self.buf.push(ch);
217 }
218
219 /// Appends an `EStr` slice onto the end of this `EString`.
220 pub fn push_estr(&mut self, s: &EStr<E>) {
221 self.buf.push_str(s.as_str());
222 }
223
224 /// Truncates this `EString`, removing all contents.
225 pub fn clear(&mut self) {
226 self.buf.clear();
227 }
228
229 /// Consumes this `EString` and yields the underlying `String`.
230 #[must_use]
231 pub fn into_string(self) -> String {
232 self.buf
233 }
234}
235
236impl<E: Encoder> AsRef<EStr<E>> for EString<E> {
237 fn as_ref(&self) -> &EStr<E> {
238 self
239 }
240}
241
242impl<E: Encoder> AsRef<str> for EString<E> {
243 fn as_ref(&self) -> &str {
244 &self.buf
245 }
246}
247
248impl<E: Encoder> Borrow<EStr<E>> for EString<E> {
249 fn borrow(&self) -> &EStr<E> {
250 self
251 }
252}
253
254impl<E: Encoder> From<&EStr<E>> for EString<E> {
255 fn from(s: &EStr<E>) -> Self {
256 s.to_owned()
257 }
258}
259
260impl<E: Encoder> PartialEq for EString<E> {
261 fn eq(&self, other: &Self) -> bool {
262 self.as_str() == other.as_str()
263 }
264}
265
266impl<E: Encoder> PartialEq<EStr<E>> for EString<E> {
267 fn eq(&self, other: &EStr<E>) -> bool {
268 self.as_str() == other.as_str()
269 }
270}
271
272impl<E: Encoder> PartialEq<EString<E>> for EStr<E> {
273 fn eq(&self, other: &EString<E>) -> bool {
274 self.as_str() == other.as_str()
275 }
276}
277
278impl<E: Encoder> PartialEq<&EStr<E>> for EString<E> {
279 fn eq(&self, other: &&EStr<E>) -> bool {
280 self.as_str() == other.as_str()
281 }
282}
283
284impl<E: Encoder> PartialEq<EString<E>> for &EStr<E> {
285 fn eq(&self, other: &EString<E>) -> bool {
286 self.as_str() == other.as_str()
287 }
288}
289
290impl<E: Encoder> PartialEq<str> for EString<E> {
291 fn eq(&self, other: &str) -> bool {
292 self.as_str() == other
293 }
294}
295
296impl<E: Encoder> PartialEq<EString<E>> for str {
297 fn eq(&self, other: &EString<E>) -> bool {
298 self == other.as_str()
299 }
300}
301
302impl<E: Encoder> PartialEq<&str> for EString<E> {
303 fn eq(&self, other: &&str) -> bool {
304 self.as_str() == *other
305 }
306}
307
308impl<E: Encoder> PartialEq<EString<E>> for &str {
309 fn eq(&self, other: &EString<E>) -> bool {
310 *self == other.as_str()
311 }
312}
313
314impl<E: Encoder> Eq for EString<E> {}
315
316impl<E: Encoder> hash::Hash for EString<E> {
317 fn hash<H: hash::Hasher>(&self, state: &mut H) {
318 self.buf.hash(state);
319 }
320}
321
322impl<E: Encoder> PartialOrd for EString<E> {
323 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
324 Some(self.cmp(other))
325 }
326}
327
328impl<E: Encoder> Ord for EString<E> {
329 fn cmp(&self, other: &Self) -> Ordering {
330 self.inner.cmp(&other.inner)
331 }
332}
333
334impl<E: Encoder> fmt::Debug for EString<E> {
335 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
336 self.as_str().fmt(f)
337 }
338}
339
340impl<E: Encoder> fmt::Display for EString<E> {
341 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
342 self.as_str().fmt(f)
343 }
344}