Skip to main content

pct_str/
sized.rs

1use std::{
2	borrow::Borrow,
3	cmp::Ordering,
4	fmt::{self, Display, Formatter},
5	hash::{Hash, Hasher},
6	str::FromStr,
7};
8
9use crate::{Encoder, InvalidPctString, PctStr};
10
11/// Owned, mutable percent-encoded string.
12///
13/// This is the equivalent of [`String`] for percent-encoded strings.
14/// It implements [`Deref`](`std::ops::Deref`) to [`PctStr`] meaning that all methods on [`PctStr`] slices are
15/// available on `PctString` values as well.
16pub struct PctString(Vec<u8>);
17
18impl PctString {
19	/// Create a new owned percent-encoded string.
20	///
21	/// The input string is checked for correct percent-encoding.
22	/// If the test fails, a [`InvalidPctString`] error is returned.
23	pub fn new<B: Into<Vec<u8>>>(bytes: B) -> Result<Self, InvalidPctString<Vec<u8>>> {
24		let bytes = bytes.into();
25		if PctStr::validate(bytes.iter().copied()) {
26			Ok(Self(bytes))
27		} else {
28			Err(InvalidPctString(bytes))
29		}
30	}
31
32	pub fn from_string(string: String) -> Result<Self, InvalidPctString<String>> {
33		Self::new(string).map_err(|e| {
34			e.map(|bytes| unsafe {
35				// SAFETY: the bytes come from the UTF-8 encoded input `string`.
36				String::from_utf8_unchecked(bytes)
37			})
38		})
39	}
40
41	/// Creates a new owned percent-encoded string without validation.
42	///
43	/// # Safety
44	///
45	/// The input string must be correctly percent-encoded.
46	pub unsafe fn new_unchecked<B: Into<Vec<u8>>>(bytes: B) -> Self {
47		Self(bytes.into())
48	}
49
50	/// Encode a string into a percent-encoded string.
51	///
52	/// This function takes an [`Encoder`] instance to decide which character of the string must
53	/// be encoded.
54	///
55	/// Note that the character `%` will always be encoded regardless of the provided [`Encoder`].
56	///
57	/// # Example
58	///
59	/// ```
60	/// use pct_str::{PctString, UriReserved};
61	///
62	/// let pct_string = PctString::encode("Hello World!".chars(), UriReserved::Any);
63	/// println!("{}", pct_string.as_str()); // => Hello World%21
64	/// ```
65	pub fn encode<E: Encoder>(src: impl Iterator<Item = char>, encoder: E) -> PctString {
66		use std::fmt::Write;
67
68		let mut buf = String::with_capacity(4);
69		let mut encoded = String::new();
70		for c in src {
71			if encoder.encode(c) || c == '%' {
72				buf.clear();
73				buf.push(c);
74				for byte in buf.bytes() {
75					write!(encoded, "%{:02X}", byte).unwrap();
76				}
77			} else {
78				encoded.push(c);
79			}
80		}
81
82		PctString(encoded.into_bytes())
83	}
84
85	/// Return this string as a borrowed percent-encoded string slice.
86	#[inline]
87	pub fn as_pct_str(&self) -> &PctStr {
88		unsafe {
89			// SAFETY: the bytes have been validated.
90			PctStr::new_unchecked(&self.0)
91		}
92	}
93
94	/// Return the internal string of the [`PctString`], consuming it
95	#[inline]
96	pub fn into_string(self) -> String {
97		unsafe {
98			// SAFETY: the bytes have been validated, and a percent-encoded
99			//         string is a valid UTF-8 string.
100			String::from_utf8_unchecked(self.0)
101		}
102	}
103
104	#[inline]
105	pub fn into_bytes(self) -> Vec<u8> {
106		self.0
107	}
108}
109
110impl std::ops::Deref for PctString {
111	type Target = PctStr;
112
113	#[inline]
114	fn deref(&self) -> &PctStr {
115		self.as_pct_str()
116	}
117}
118
119impl Borrow<PctStr> for PctString {
120	fn borrow(&self) -> &PctStr {
121		self.as_pct_str()
122	}
123}
124
125impl AsRef<PctStr> for PctString {
126	fn as_ref(&self) -> &PctStr {
127		self.as_pct_str()
128	}
129}
130
131impl AsRef<str> for PctString {
132	fn as_ref(&self) -> &str {
133		self.as_str()
134	}
135}
136
137impl AsRef<[u8]> for PctString {
138	fn as_ref(&self) -> &[u8] {
139		self.as_bytes()
140	}
141}
142
143impl PartialEq for PctString {
144	#[inline]
145	fn eq(&self, other: &PctString) -> bool {
146		let mut a = self.chars();
147		let mut b = other.chars();
148
149		loop {
150			match (a.next(), b.next()) {
151				(Some(a), Some(b)) if a != b => return false,
152				(Some(_), None) => return false,
153				(None, Some(_)) => return false,
154				(None, None) => break,
155				_ => (),
156			}
157		}
158
159		true
160	}
161}
162
163impl Eq for PctString {}
164
165impl PartialEq<PctStr> for PctString {
166	#[inline]
167	fn eq(&self, other: &PctStr) -> bool {
168		let mut a = self.chars();
169		let mut b = other.chars();
170
171		loop {
172			match (a.next(), b.next()) {
173				(Some(a), Some(b)) if a != b => return false,
174				(Some(_), None) => return false,
175				(None, Some(_)) => return false,
176				(None, None) => break,
177				_ => (),
178			}
179		}
180
181		true
182	}
183}
184
185impl PartialEq<&str> for PctString {
186	#[inline]
187	fn eq(&self, other: &&str) -> bool {
188		let mut a = self.chars();
189		let mut b = other.chars();
190
191		loop {
192			match (a.next(), b.next()) {
193				(Some(a), Some(b)) if a != b => return false,
194				(Some(_), None) => return false,
195				(None, Some(_)) => return false,
196				(None, None) => break,
197				_ => (),
198			}
199		}
200
201		true
202	}
203}
204
205impl PartialEq<str> for PctString {
206	#[inline]
207	fn eq(&self, other: &str) -> bool {
208		self.eq(&other)
209	}
210}
211
212impl PartialOrd for PctString {
213	fn partial_cmp(&self, other: &PctString) -> Option<Ordering> {
214		self.as_pct_str().partial_cmp(other.as_pct_str())
215	}
216}
217
218impl PartialOrd<PctStr> for PctString {
219	fn partial_cmp(&self, other: &PctStr) -> Option<Ordering> {
220		self.as_pct_str().partial_cmp(other)
221	}
222}
223
224impl Hash for PctString {
225	#[inline]
226	fn hash<H: Hasher>(&self, hasher: &mut H) {
227		for c in self.chars() {
228			c.hash(hasher)
229		}
230	}
231}
232
233impl Display for PctString {
234	fn fmt(&self, f: &mut Formatter) -> fmt::Result {
235		Display::fmt(self.as_str(), f)
236	}
237}
238
239impl fmt::Debug for PctString {
240	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
241		fmt::Debug::fmt(self.as_str(), f)
242	}
243}
244
245impl FromStr for PctString {
246	type Err = InvalidPctString<String>;
247
248	fn from_str(s: &str) -> Result<Self, Self::Err> {
249		Self::from_string(s.to_string())
250	}
251}
252
253impl TryFrom<String> for PctString {
254	type Error = InvalidPctString<String>;
255
256	fn try_from(value: String) -> Result<Self, Self::Error> {
257		Self::from_string(value)
258	}
259}
260
261impl<'a> TryFrom<&'a str> for PctString {
262	type Error = InvalidPctString<String>;
263
264	fn try_from(value: &'a str) -> Result<Self, Self::Error> {
265		Self::from_string(value.to_owned())
266	}
267}
268
269impl<'a> TryFrom<&'a str> for &'a PctStr {
270	type Error = InvalidPctString<&'a str>;
271
272	fn try_from(value: &'a str) -> Result<Self, Self::Error> {
273		PctStr::new(value)
274	}
275}