async_coap_uri/uri_ref_buf.rs
1// Copyright 2019 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14//
15
16use super::*;
17use std::ops::Deref;
18
19/// Sized, heap-allocated string type containing either a URI or a relative-reference.
20///
21/// Similar to `String`, but with additional guarantees on internal structure.
22/// The unsized counterpart is `UriRef`.
23///
24/// This type implements [`std::ops::Deref<UriRef>`], so you can also use all of the
25/// methods from [`UriRef`](crate::UriRef) on this type.
26#[derive(Clone, Eq, Hash)]
27pub struct UriRefBuf(pub(super) String);
28
29_impl_uri_buf_traits_base!(UriRefBuf, UriRef);
30
31impl Default for UriRefBuf {
32 fn default() -> Self {
33 Self::new()
34 }
35}
36
37impl Deref for UriRefBuf {
38 type Target = UriRef;
39
40 fn deref(&self) -> &Self::Target {
41 self.as_uri_ref()
42 }
43}
44
45impl AsRef<String> for UriRefBuf {
46 fn as_ref(&self) -> &String {
47 &self.0
48 }
49}
50
51impl std::fmt::Display for UriRefBuf {
52 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
53 self.write_to(f)
54 }
55}
56
57/// # Constructors
58impl UriRefBuf {
59 /// Creates a new, empty [`UriRefBuf`].
60 pub fn new() -> UriRefBuf {
61 UriRefBuf(String::new())
62 }
63
64 /// Creates a new, empty [`UriRefBuf`] with a capacity of `capacity`.
65 pub fn with_capacity(capacity: usize) -> UriRefBuf {
66 UriRefBuf(String::with_capacity(capacity))
67 }
68
69 /// Attempts to create a new [`UriRefBuf`] from a string reference.
70 pub fn from_str<S: AsRef<str> + Copy>(s: S) -> Result<Self, ParseError> {
71 let str_ref = s.as_ref();
72 UriRef::from_str(str_ref)?;
73 Ok(UriRefBuf(str_ref.to_string()))
74 }
75
76 /// Attempts to create a new [`UriRefBuf`] from a [`String`].
77 pub fn from_string(s: String) -> Result<Self, ParseError> {
78 UriRef::from_str(s.as_str())?;
79 Ok(UriRefBuf(s))
80 }
81}
82
83/// # Conversions
84impl UriRefBuf {
85 /// Borrows a string slice containing this URI reference.
86 #[inline(always)]
87 pub fn as_str(&self) -> &str {
88 self.0.as_str()
89 }
90
91 /// Borrows a [`UriRef`] slice containing this URI reference.
92 #[inline(always)]
93 pub fn as_uri_ref(&self) -> &UriRef {
94 unsafe { UriRef::from_str_unchecked(self.as_str()) }
95 }
96
97 /// Borrows a mutable [`UriRef`] slice containing this URI reference.
98 #[inline(always)]
99 pub fn as_mut_uri_ref(&mut self) -> &mut UriRef {
100 unsafe { UriRef::from_str_unchecked_mut(self.as_mut_str()) }
101 }
102}
103
104/// # Manipulation
105impl UriRefBuf {
106 /// Removes fragment component, if present.
107 pub fn truncate_fragment(&mut self) {
108 if let Some(i) = self.fragment_start() {
109 self.0.truncate(i)
110 }
111 }
112
113 /// Truncates the authority, path, query, and fragment components of a `UriRefBuf`.
114 pub fn truncate_heir_part(&mut self) {
115 self.0.truncate(self.heir_part_start())
116 }
117
118 /// Removes query and fragment components, if present.
119 /// E.g.:
120 ///
121 /// ```
122 /// # use async_coap_uri::*;
123 /// # fn main() {
124 /// let mut uri = uri_ref!("http://example.com/blah/bleh?query").to_uri_ref_buf();
125 /// uri.truncate_query();
126 /// assert_eq!("http://example.com/blah/bleh", uri.as_str());
127 /// # }
128 /// ```
129 /// ```
130 /// # use async_coap_uri::*;
131 /// # fn main() {
132 /// let mut uri = uri_ref!("http://example.com/blah/bleh#foobar").to_uri_ref_buf();
133 /// uri.truncate_query();
134 /// assert_eq!("http://example.com/blah/bleh", uri.as_str());
135 /// # }
136 /// ```
137 /// ```
138 /// # use async_coap_uri::*;
139 /// # fn main() {
140 /// let mut uri = uri_ref!("another?foo#bar").to_uri_ref_buf();
141 /// uri.truncate_query();
142 /// assert_eq!("another", uri.as_str());
143 /// # }
144 /// ```
145 pub fn truncate_query(&mut self) {
146 if let Some(i) = self.query_start().or(self.fragment_start()) {
147 self.0.truncate(i)
148 }
149 }
150
151 /// Truncates the path, query, and fragments components of a `UriRefBuf`.
152 pub fn truncate_path(&mut self) {
153 self.0.truncate(self.path_start());
154 }
155
156 /// Removes the last path component (up to, but not including, the last slash), along with
157 /// the query and fragment components, if present.
158 /// E.g.:
159 ///
160 /// ```
161 /// # use async_coap_uri::*;
162 /// # fn main() {
163 /// let mut uri = uri_ref!("http://example.com/blah/bleh").to_uri_ref_buf();
164 /// uri.truncate_resource();
165 /// assert_eq!("http://example.com/blah/", uri.as_str());
166 /// # }
167 /// ```
168 /// ```
169 /// # use async_coap_uri::*;
170 /// # fn main() {
171 /// let mut uri = uri_ref!("http://example.com/blah/").to_uri_ref_buf();
172 /// uri.truncate_resource();
173 /// assert_eq!("http://example.com/blah/", uri.as_str());
174 /// # }
175 /// ```
176 /// ```
177 /// # use async_coap_uri::*;
178 /// # fn main() {
179 /// let mut uri = uri_ref!("foo#bar").to_uri_ref_buf();
180 /// uri.truncate_resource();
181 /// assert_eq!("", uri.as_str());
182 /// # }
183 /// ```
184 /// * `http://example.com/blah/bleh` becomes `http://example.com/blah/`
185 /// * `http://example.com/blah/` becomes `http://example.com/blah/`
186 /// * `foo#bar` becomes ``
187 pub fn truncate_resource(&mut self) {
188 self.truncate_query();
189 let path_start = self.as_uri_ref().path_start();
190
191 if let Some(i) = self.as_str().rfind('/') {
192 if i + 1 > path_start {
193 self.0.truncate(i + 1);
194 }
195 } else if path_start == 0 {
196 self.clear();
197 }
198 }
199
200 /// Removes the last path item, along with
201 /// the query and fragment components, if present.
202 ///
203 /// This method will only result in an empty path if the path was empty to begin with.
204 ///
205 /// E.g.:
206 ///
207 /// ```
208 /// # use async_coap_uri::*;
209 /// # fn main() {
210 /// let mut uri = uri_ref!("http://example.com/blah/bleh").to_uri_ref_buf();
211 /// uri.truncate_last_path_segment();
212 /// assert_eq!("http://example.com/blah/", uri.as_str());
213 /// uri.truncate_last_path_segment();
214 /// assert_eq!("http://example.com/", uri.as_str());
215 /// uri.truncate_last_path_segment();
216 /// assert_eq!("http://example.com/", uri.as_str());
217 /// # }
218 /// ```
219 /// ```
220 /// # use async_coap_uri::*;
221 /// # fn main() {
222 /// let mut uri = uri_ref!("foo#bar").to_uri_ref_buf();
223 /// uri.truncate_last_path_segment();
224 /// assert_eq!("./", uri.as_str());
225 /// # }
226 /// ```
227 /// * `http://example.com/blah/bleh` becomes `http://example.com/blah/`
228 /// * `http://example.com/blah/` becomes `http://example.com/`
229 /// * `foo#bar` becomes ``
230 pub fn truncate_last_path_segment(&mut self) {
231 let path_start = self.as_uri_ref().path_start();
232 let path_end = self.as_uri_ref().path_end();
233
234 if path_start == path_end {
235 return;
236 }
237
238 self.truncate_query();
239
240 let mut s = self.as_str();
241
242 // Trim off any trailing slash (but only 1)
243 if s.ends_with('/') {
244 s = &s[..s.len() - 1];
245 }
246
247 if let Some(i) = s.rfind('/') {
248 if i + 1 > path_start {
249 self.0.truncate(i + 1);
250 }
251 } else if path_start == 0 {
252 self.clear();
253 }
254
255 if self.raw_path().is_empty() {
256 // The empty URI has special behaviors that we don't
257 // want here, so make ourselves "./" here instead.
258 self.0.push_str("./");
259 }
260 }
261
262 /// Completely clears this `UriRefBuf`, leaving it empty.
263 pub fn clear(&mut self) {
264 self.0.clear()
265 }
266
267 /// Adds a trailing slash to the path if there isn't a trailing slash already present.
268 pub fn add_trailing_slash(&mut self) -> bool {
269 if self.is_empty() {
270 self.0.push_str("./");
271 true
272 } else {
273 let path_end = self.path_end();
274 if path_end > 0 && &self[path_end - 1..path_end] == "/" {
275 false
276 } else {
277 self.0.insert(path_end, '/');
278 true
279 }
280 }
281 }
282
283 /// Adds a leading slash to the path if there isn't one already present.
284 pub fn add_leading_slash(&mut self) -> bool {
285 let path_begin = self.path_start();
286 if self.len() > 0 && &self[path_begin..path_begin + 1] == "/" {
287 false
288 } else {
289 self.0.insert(path_begin, '/');
290 true
291 }
292 }
293
294 /// Using this URI-reference as the base, performs "relative resolution" to the given instance
295 /// implementing [`AnyUriRef`], updating the content of this `UriRefBuf` with the result.
296 pub fn resolve<T: AnyUriRef + ?Sized>(&mut self, dest: &T) -> Result<(), ResolveError> {
297 if !dest.is_empty() {
298 *self = self.resolved(dest)?;
299 }
300 Ok(())
301 }
302
303 /// Percent-encodes and appends the given path segment to this URI-reference,
304 /// truncating any existing query or fragment in the process.
305 ///
306 /// If this URI-reference isn't empty and doesn't end with a slash, one
307 /// is first added. A trailing slash will be appended depending on the value
308 /// of the `trailing_slash` argument.
309 ///
310 /// # Special Cases
311 ///
312 /// Calling `x.push_path_segment(".", false)` will do nothing.
313 ///
314 /// Calling `x.push_path_segment(".", true)` is the same as calling `x.add_trailing_slash()`.
315 ///
316 /// Calling `x.push_path_segment("..", _)` is the same as calling
317 /// `x.truncate_last_path_segment()`. In this case the value of `trailing_slash` is ignored.
318 ///
319 /// # Example
320 ///
321 /// ```
322 /// # use async_coap_uri::*;
323 /// let mut uri = uri_ref!("http://example.com").to_uri_ref_buf();
324 ///
325 /// uri.push_path_segment("foobar", false);
326 /// assert_eq!(uri, uri_ref!("http://example.com/foobar"));
327 ///
328 /// uri.push_path_segment("a/b/c", true);
329 /// assert_eq!(uri, uri_ref!("http://example.com/foobar/a%2Fb%2Fc/"));
330 /// ```
331 pub fn push_path_segment(&mut self, segment: &str, trailing_slash: bool) {
332 if segment == "." {
333 if trailing_slash {
334 self.add_trailing_slash();
335 } else if self.is_empty() {
336 // this is the only case where we actually add the dot.
337 self.0.push_str(".");
338 }
339 return;
340 }
341
342 if segment == ".." {
343 self.truncate_last_path_segment();
344 return;
345 }
346
347 self.truncate_query();
348
349 if !self.is_empty() {
350 self.add_trailing_slash();
351 }
352
353 self.0.extend(segment.escape_uri());
354
355 if trailing_slash {
356 self.add_trailing_slash();
357 }
358 }
359
360 /// Percent-encodes and appends the given query item to this instance,
361 /// truncating any existing fragment in the process.
362 ///
363 /// If no query is present, the query item is preceded with a '?' to
364 /// indicate the start of the query component. Otherwise, this method
365 /// uses `&` to separate query items.
366 ///
367 /// This method follows the common convention where spaces are encoded
368 /// as `+` characters instead of `%20`.
369 ///
370 /// # Example
371 ///
372 /// ```
373 /// # use async_coap_uri::*;
374 /// let mut uri = uri_ref!("http://example.com").to_uri_ref_buf();
375 ///
376 /// uri.push_query_item("foobar");
377 /// assert_eq!(uri, uri_ref!("http://example.com?foobar"));
378 ///
379 /// uri.push_query_item("q=a vast query");
380 /// assert_eq!(uri, uri_ref!("http://example.com?foobar&q=a+vast+query"));
381 /// ```
382 pub fn push_query_item(&mut self, item: &str) {
383 self.truncate_fragment();
384 if let Some(_) = self.query_start() {
385 self.0.push('&');
386 } else {
387 self.0.push('?');
388 }
389 self.0.extend(item.escape_uri().for_query());
390 }
391
392 /// Percent-encodes and appends the given query key/value pair to this URI-reference,
393 /// truncating any existing fragment in the process.
394 ///
395 /// If no query is present, the query item is preceded with a '?' to
396 /// indicate the start of the query component. Otherwise, this method
397 /// uses `&` to separate query items.
398 ///
399 /// This method follows the common convention where spaces are encoded
400 /// as `+` characters instead of `%20`.
401 ///
402 /// # Example
403 ///
404 /// ```
405 /// # use async_coap_uri::*;
406 /// let mut uri = uri_ref!("http://example.com").to_uri_ref_buf();
407 ///
408 /// uri.push_query_key_value("foo","bar");
409 /// assert_eq!(uri, uri_ref!("http://example.com?foo=bar"));
410 ///
411 /// uri.push_query_key_value("q","a vast query");
412 /// assert_eq!(uri, uri_ref!("http://example.com?foo=bar&q=a+vast+query"));
413 /// ```
414 pub fn push_query_key_value(&mut self, key: &str, value: &str) {
415 self.truncate_fragment();
416 if let Some(_) = self.query_start() {
417 self.0.push('&');
418 } else {
419 self.0.push('?');
420 }
421 self.0.extend(key.escape_uri().for_query());
422 self.0.push('=');
423 self.0.extend(value.escape_uri().for_query());
424 }
425
426 /// Replaces the path, query, and fragment with that from `rel`.
427 pub fn replace_path(&mut self, rel: &RelRef) {
428 self.truncate_path();
429 if !rel.starts_with(|c| c == '/' || c == '?' || c == '#') {
430 self.add_trailing_slash();
431 }
432
433 self.0.push_str(rel.as_str());
434 }
435}
436
437/// # Unsafe Methods
438///
439/// `UriRefBuf` needs some unsafe methods in order to function properly. This section is where
440/// they are all located.
441impl UriRefBuf {
442 /// Unchecked version of [`UriRefBuf::from_string`].
443 ///
444 /// # Safety
445 ///
446 /// This method is marked as unsafe because it allows you to construct a `UriRefBuf` with
447 /// a value that is not a well-formed URI reference.
448 pub unsafe fn from_string_unchecked(s: String) -> UriRefBuf {
449 UriRefBuf(s)
450 }
451
452 /// Borrows a mutable string slice containing this URI reference.
453 ///
454 /// This method is marked as unsafe because it allows you to change the
455 /// content of the URI reference without any checks on syntax.
456 #[inline(always)]
457 pub unsafe fn as_mut_str(&mut self) -> &mut str {
458 self.0.as_mut_str()
459 }
460
461 /// Borrows a mutable [`String`] reference containing this URI reference.
462 ///
463 /// This method is marked as unsafe because it allows you to change the
464 /// content of the URI reference without any checks on syntax.
465 pub unsafe fn as_mut_string_ref(&mut self) -> &mut String {
466 &mut self.0
467 }
468}
469
470#[doc(hidden)]
471#[macro_export]
472macro_rules! inherits_uri_ref_buf {
473 ($BUF:ident) => {
474 /// # Methods inherited from [`UriRefBuf`]
475 impl $BUF {
476 /// Returns a string slice for this instance.
477 #[inline(always)]
478 pub fn as_str(&self) -> &str {
479 self.0.as_str()
480 }
481
482 /// Returns a mutable string slice (`&mut str`) for this instance.
483 ///
484 /// ## Safety
485 ///
486 /// This method is not safe because this type makes guarantees about
487 /// the structure of the content it contains, which may be violated by
488 /// using this method.
489 #[inline(always)]
490 pub unsafe fn as_mut_str(&mut self) -> &mut str {
491 self.0.as_mut_str()
492 }
493
494 /// Borrows a reference to this mutable instance as a mutable URI-Reference
495 /// (`&mut UriRef`).
496 #[inline(always)]
497 pub fn as_mut_uri_ref(&mut self) -> &mut UriRef {
498 self.0.as_mut_uri_ref()
499 }
500
501 /// Removes the authority, path, query, and fragment components, if present.
502 #[inline(always)]
503 pub fn truncate_heir_part(&mut self) {
504 self.0.truncate_heir_part()
505 }
506
507 /// Removes the path, query, and fragment components, if present.
508 #[inline(always)]
509 pub fn truncate_path(&mut self) {
510 self.0.truncate_path()
511 }
512
513 /// Removes the query, and fragment components, if present.
514 #[inline(always)]
515 pub fn truncate_query(&mut self) {
516 self.0.truncate_query()
517 }
518
519 /// Removes fragment component, if present.
520 #[inline(always)]
521 pub fn truncate_fragment(&mut self) {
522 self.0.truncate_fragment()
523 }
524
525 /// Removes the last path component (up to, but not including, the last slash),
526 /// along with the query and fragment components, if present.
527 ///
528 /// See [`UriRefBuf::truncate_resource`] for more information.
529 #[inline(always)]
530 pub fn truncate_resource(&mut self) {
531 self.0.truncate_resource()
532 }
533
534 /// Removes the last path item, along with
535 /// the query and fragment components, if present.
536 ///
537 /// See [`UriRefBuf::truncate_last_path_segment`] for more information.
538 #[inline(always)]
539 pub fn truncate_last_path_segment(&mut self) {
540 self.0.truncate_last_path_segment()
541 }
542
543 /// Adds a trailing slash to the path if there isn't a trailing slash already present.
544 #[inline(always)]
545 pub fn add_trailing_slash(&mut self) -> bool {
546 self.0.add_trailing_slash()
547 }
548
549 /// Adds a leading slash to the path if there isn't one already present.
550 #[inline(always)]
551 pub fn add_leading_slash(&mut self) -> bool {
552 self.0.add_leading_slash()
553 }
554
555 /// Percent-encodes and appends the given path segment to this instance,
556 /// truncating any existing query or fragment in the process.
557 ///
558 /// If this instance isn't empty and doesn't end with a slash, one
559 /// is first added. A trailing slash will be appended depending on the value
560 /// of the `trailing_slash` argument.
561 ///
562 #[inline(always)]
563 pub fn push_path_segment(&mut self, segment: &str, trailing_slash: bool) {
564 self.0.push_path_segment(segment, trailing_slash)
565 }
566
567 /// Percent-encodes and appends the given query item to this instance,
568 /// truncating any existing fragment in the process.
569 ///
570 /// If no query is present, the query item is preceded with a '?' to
571 /// indicate the start of the query component. Otherwise, this method
572 /// uses `&` to separate query items.
573 ///
574 /// This method follows the common convention where spaces are encoded
575 /// as `+` characters instead of `%20`.
576 #[inline(always)]
577 pub fn push_query_item(&mut self, item: &str) {
578 self.0.push_query_item(item)
579 }
580
581 /// Percent-encodes and appends the given query key/value pair to this URI-reference,
582 /// truncating any existing fragment in the process.
583 ///
584 /// If no query is present, the query item is preceded with a '?' to
585 /// indicate the start of the query component. Otherwise, this method
586 /// uses `&` to separate query items.
587 ///
588 /// This method follows the common convention where spaces are encoded
589 /// as `+` characters instead of `%20`.
590 #[inline(always)]
591 pub fn push_query_key_value(&mut self, key: &str, value: &str) {
592 self.0.push_query_key_value(key, value)
593 }
594 }
595 };
596}
597
598#[cfg(test)]
599mod tests {
600 use super::*;
601
602 #[test]
603 fn test_from_str() {
604 assert!(UriRefBuf::from_str("http://example.com/").is_ok());
605 }
606
607 #[test]
608 fn push_path_segment() {
609 let mut uri = uri_ref!("").to_uri_ref_buf();
610
611 uri.push_path_segment(".", false);
612 assert_eq!(uri, uri_ref!("."));
613
614 let mut uri = uri_ref!("").to_uri_ref_buf();
615
616 uri.push_path_segment("foobar", false);
617 assert_eq!(uri, uri_ref!("foobar"));
618
619 uri.push_path_segment("a/b/c", true);
620 assert_eq!(uri, uri_ref!("foobar/a%2Fb%2Fc/"));
621
622 uri.push_path_segment(".", true);
623 assert_eq!(uri, uri_ref!("foobar/a%2Fb%2Fc/"));
624
625 uri.push_path_segment("..", false);
626 assert_eq!(uri, uri_ref!("foobar/"));
627
628 uri.push_path_segment("..", false);
629 assert_eq!(uri, uri_ref!("./"));
630 }
631
632 #[test]
633 fn add_trailing_slash() {
634 let mut uri = uri_ref!("example/").to_uri_ref_buf();
635 assert_eq!(false, uri.add_trailing_slash());
636
637 let mut uri = uri_ref!("example").to_uri_ref_buf();
638 assert_eq!(true, uri.add_trailing_slash());
639 assert_eq!(uri_ref!("example/"), &uri);
640
641 let mut uri = uri_ref!("example?").to_uri_ref_buf();
642 assert_eq!(true, uri.add_trailing_slash());
643 assert_eq!(uri_ref!("example/?"), &uri);
644
645 let mut uri = uri_ref!("example#").to_uri_ref_buf();
646 assert_eq!(true, uri.add_trailing_slash());
647 assert_eq!(uri_ref!("example/#"), &uri);
648
649 let mut uri = uri_ref!("example?/#/").to_uri_ref_buf();
650 assert_eq!(true, uri.add_trailing_slash());
651 assert_eq!(uri_ref!("example/?/#/"), &uri);
652
653 let mut uri = uri_ref!("/e/x/a/m/p/l/e?/#/").to_uri_ref_buf();
654 assert_eq!(true, uri.add_trailing_slash());
655 assert_eq!(uri_ref!("/e/x/a/m/p/l/e/?/#/"), &uri);
656 }
657
658 #[test]
659 fn add_leading_slash() {
660 let mut uri = uri_ref!("/example").to_uri_ref_buf();
661 assert_eq!(false, uri.add_leading_slash());
662
663 let mut uri = uri_ref!("example").to_uri_ref_buf();
664 assert_eq!(true, uri.add_leading_slash());
665 assert_eq!(uri_ref!("/example"), &uri);
666
667 let mut uri = uri_ref!("example?").to_uri_ref_buf();
668 assert_eq!(true, uri.add_leading_slash());
669 assert_eq!(uri_ref!("/example?"), &uri);
670
671 let mut uri = uri_ref!("example#").to_uri_ref_buf();
672 assert_eq!(true, uri.add_leading_slash());
673 assert_eq!(uri_ref!("/example#"), &uri);
674
675 let mut uri = uri_ref!("example?/#/").to_uri_ref_buf();
676 assert_eq!(true, uri.add_leading_slash());
677 assert_eq!(uri_ref!("/example?/#/"), &uri);
678
679 let mut uri = uri_ref!("e/x/a/m/p/l/e/?/#/").to_uri_ref_buf();
680 assert_eq!(true, uri.add_leading_slash());
681 assert_eq!(uri_ref!("/e/x/a/m/p/l/e/?/#/"), &uri);
682 }
683}