async_coap_uri/
uri.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/// Unsized string-slice type guaranteed to contain a well-formed [IETF-RFC3986] URI
20/// *or* [network path](index.html#network-path-support).
21///
22/// The sized counterpart is [`crate::UriBuf`].
23///
24/// You can create static constants with this class by using the [`uri!`] macro:
25///
26/// ```
27/// # use async_coap_uri::*;
28/// # fn main() {
29///     let uri = uri!("http://example.com/test");
30///     let components = uri.components();
31///     assert_eq!(Some("http"),        components.scheme());
32///     assert_eq!(Some("example.com"), components.raw_host());
33///     assert_eq!(None,                components.port());
34///     assert_eq!("/test",             components.raw_path());
35/// # }
36/// ```
37///
38/// [`uri!`]: macro.uri.html
39/// [IETF-RFC3986]: https://tools.ietf.org/html/rfc3986
40#[derive(Eq, Hash)]
41pub struct Uri(UriRef);
42
43_impl_uri_traits_base!(Uri);
44
45impl Deref for Uri {
46    type Target = UriRef;
47
48    fn deref(&self) -> &Self::Target {
49        self.as_uri_ref()
50    }
51}
52
53impl AsRef<UriRef> for Uri {
54    fn as_ref(&self) -> &UriRef {
55        &self.0
56    }
57}
58
59impl AnyUriRef for Uri {
60    fn write_to<T: core::fmt::Write + ?Sized>(
61        &self,
62        write: &mut T,
63    ) -> Result<(), core::fmt::Error> {
64        write.write_str(self.as_str())
65    }
66
67    fn is_empty(&self) -> bool {
68        self.0.is_empty()
69    }
70
71    /// Determines what kind of URI this is.
72    ///
73    /// This function may return any one of the following values:
74    ///
75    /// * [`UriType::Uri`](enum.UriType.html#variant.Uri)
76    /// * [`UriType::UriNoAuthority`](enum.UriType.html#variant.UriNoAuthority)
77    /// * [`UriType::UriCannotBeABase`](enum.UriType.html#variant.UriCannotBeABase)
78    /// * [`UriType::NetworkPath`](enum.UriType.html#variant.NetworkPath)
79    fn uri_type(&self) -> UriType {
80        if self.0.starts_with("//") {
81            UriType::NetworkPath
82        } else {
83            let i = self.find(':').expect("Uri contract broken");
84            if self[i..].starts_with("://") {
85                UriType::Uri
86            } else if self[i..].starts_with(":/") {
87                UriType::UriNoAuthority
88            } else {
89                UriType::UriCannotBeABase
90            }
91        }
92    }
93
94    fn components(&self) -> UriRawComponents<'_> {
95        self.0.components()
96    }
97}
98
99impl std::fmt::Display for Uri {
100    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
101        self.write_to(f)
102    }
103}
104
105impl Uri {
106    /// Attempts to convert a string slice into a [`&Uri`](Uri), returning `Err(ParseError)`
107    /// if the string slice contains data that is not a valid URI.
108    ///
109    /// Example:
110    ///
111    /// ```
112    /// use async_coap_uri::prelude::*;
113    /// assert_eq!(Uri::from_str("http://example.com"), Ok(uri!("http://example.com")));
114    /// assert!(Uri::from_str("/a/b/c").is_err());
115    /// ```
116    pub fn from_str(s: &str) -> Result<&Uri, ParseError> {
117        let str_ref = s.as_ref();
118        // TODO: Replace this with an optimized validity check.
119        //       We are currently using `UriRawComponents::from_str()` as a crutch here;
120        //       it includes extraneous operations that are not related to verifying if a
121        //       URI is well-formed.
122        if UriRawComponents::from_str(str_ref)?
123            .uri_type()
124            .can_borrow_as_uri()
125        {
126            Ok(unsafe { Self::from_str_unchecked(s.as_ref()) })
127        } else {
128            Err(ParseError::new("Not a URI", None))
129        }
130    }
131
132    /// Determines if the given string can be considered a valid URI.
133    ///
134    /// The key difference between this and [`crate::UriRef::is_str_valid`] is that this
135    /// function will return false for [relative-references] like `/a/b/c`, whereas
136    /// `UriRef::is_str_valid` would return true.
137    ///
138    /// Example:
139    ///
140    /// ```
141    /// use async_coap_uri::Uri;
142    /// assert!(Uri::is_str_valid("http://example.com"));
143    /// assert!(!Uri::is_str_valid("/a/b/c"));
144    /// assert!(!Uri::is_str_valid("Not a URI"));
145    /// ```
146    /// [relative-reference]: https://tools.ietf.org/html/rfc3986#section-4.2
147    pub fn is_str_valid<S: AsRef<str>>(s: S) -> bool {
148        let str_ref = s.as_ref();
149        // TODO: Replace this with an optimized validity check.
150        //       We are currently using `UriRawComponents::from_str()` as a crutch here;
151        //       it includes extraneous operations that are not related to verifying if a
152        //       URI is well-formed.
153        if let Ok(components) = UriRawComponents::from_str(str_ref) {
154            if components.uri_type().can_borrow_as_uri() {
155                return true;
156            }
157        }
158        return false;
159    }
160
161    /// Reinterpret this [`&Uri`][Uri] as a [`&UriRef`][UriRef].
162    #[inline(always)]
163    pub const fn as_uri_ref(&self) -> &UriRef {
164        &self.0
165    }
166
167    /// Copy the content of this [`&Uri`][Uri] into a new [`UriBuf`] and return it.
168    pub fn to_uri_buf(&self) -> UriBuf {
169        unsafe { UriBuf::from_string_unchecked(self.to_string()) }
170    }
171}
172
173/// ## Splitting
174impl Uri {
175    /// Splits this URI into the base and relative portions.
176    pub fn split(&self) -> (&Uri, &RelRef) {
177        let (uri_base, uri_rel) = self.0.split();
178        (uri_base.unwrap(), uri_rel)
179    }
180}
181
182/// ## Trimming
183impl Uri {
184    /// Returns this URI without a fragment.
185    ///
186    /// ## Examples
187    ///
188    /// ```
189    /// use async_coap_uri::prelude::*;
190    /// assert_eq!(uri!("http://a/#frag").trim_fragment(),  uri!("http://a/"));
191    /// assert_eq!(uri!("//a/b/c?blah#frag").trim_fragment(), uri!("//a/b/c?blah"));
192    /// ```
193    pub fn trim_fragment(&self) -> &Uri {
194        unsafe { Uri::from_str_unchecked(self.0.trim_fragment().as_str()) }
195    }
196
197    /// Returns this URI without a query or fragment.
198    ///
199    /// ## Examples
200    ///
201    /// ```
202    /// use async_coap_uri::prelude::*;
203    /// assert_eq!(uri!("//foo/?bar").trim_query(),      uri!("//foo/"));
204    /// assert_eq!(uri!("http://a/#frag").trim_query(),  uri!("http://a/"));
205    /// ```
206    pub fn trim_query(&self) -> &Uri {
207        unsafe { Uri::from_str_unchecked(self.0.trim_query().as_str()) }
208    }
209
210    /// Returns this URI without a path, query, or fragment.
211    ///
212    /// ## Examples
213    ///
214    /// ```
215    /// use async_coap_uri::prelude::*;
216    /// assert_eq!(uri!("//foo/?bar").trim_path(),      uri!("//foo"));
217    /// assert_eq!(uri!("http://a/#frag").trim_path(),  uri!("http://a"));
218    /// ```
219    pub fn trim_path(&self) -> &Uri {
220        unsafe { Uri::from_str_unchecked(self.0.trim_path().as_str()) }
221    }
222
223    /// Returns this URI without the trailing part of the path that would be
224    /// removed during relative-reference resolution.
225    ///
226    /// ## Examples
227    ///
228    /// ```
229    /// use async_coap_uri::prelude::*;
230    /// assert_eq!(uri!("//foo/?bar").trim_resource(),      uri!("//foo/"));
231    /// assert_eq!(uri!("http://a/#frag").trim_resource(),  uri!("http://a/"));
232    /// ```
233    pub fn trim_resource(&self) -> &Uri {
234        unsafe { Uri::from_str_unchecked(self.0.trim_resource().as_str()) }
235    }
236}
237
238/// # Unsafe Methods
239///
240/// `Uri` needs some unsafe methods in order to function properly. This section is where
241/// they are all located.
242impl Uri {
243    /// Creates a `Uri` slice from a string slice without checking that the content
244    /// of the string slice is a valid URI.
245    ///
246    /// Since containing a valid URI is a fundamental guarantee of the `Uri` type, this method is
247    /// `unsafe`.
248    #[inline(always)]
249    pub unsafe fn from_str_unchecked(s: &str) -> &Uri {
250        &*(s as *const str as *const Uri)
251    }
252
253    /// Creates a mutable `Uri` slice from a mutable string slice without checking that the content
254    /// of the mutable string slice is a valid URI.
255    ///
256    /// Since containing a valid URI is a fundamental guarantee of the `Uri` type, this method is
257    /// `unsafe`.
258    #[inline(always)]
259    pub unsafe fn from_str_unchecked_mut(s: &mut str) -> &mut Uri {
260        &mut *(s as *mut str as *mut Uri)
261    }
262
263    /// Returns this slice as a mutable `str` slice.
264    ///
265    /// ## Safety
266    ///
267    /// This is unsafe because it allows you to change the contents of the slice in
268    /// such a way that would make it no longer consistent with the `Uri`'s promise
269    /// that it can only ever contain a valid URI.
270    #[inline(always)]
271    pub unsafe fn as_mut_str(&mut self) -> &mut str {
272        self.0.as_mut_str()
273    }
274}
275
276#[cfg(test)]
277mod tests {
278    use super::*;
279
280    #[test]
281    fn test_uri_type() {
282        assert_eq!(uri!("scheme://example").uri_type(), UriType::Uri);
283        assert_eq!(uri!("scheme:/example").uri_type(), UriType::UriNoAuthority);
284        assert_eq!(uri!("scheme:example").uri_type(), UriType::UriCannotBeABase);
285        assert_eq!(
286            uri!("scheme:example/://not_a_url").uri_type(),
287            UriType::UriCannotBeABase
288        );
289    }
290}