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}