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}