Skip to main content

urly/
lib.rs

1//! URL manipulation library with an API inspired by Python's
2//! [yarl](https://yarl.aio-libs.org/).
3//!
4//! [`Url`] is an immutable, cheaply cloneable, normalized URL reference stored
5//! in a single [`ByteString`](ntex_bytes::ByteString). Accessors return
6//! borrowed, percent-encoded components with methods to decode them;
7//! modifications return or produce a new normalized URL.
8//!
9//! ```
10//! use urly::Url;
11//!
12//! let url = Url::from_static("https://example.com/api/v1?page=2");
13//! assert_eq!(url.host(), Some("example.com"));
14//! assert_eq!(url.port_or_known_default(), Some(443));
15//!
16//! let users = &url / "users" / "john doe";
17//! assert_eq!(users, "https://example.com/api/v1/users/john%20doe");
18//! assert_eq!(users.path().segments().last().unwrap(), "john%20doe");
19//!
20//! let mut url = users.join("../groups?id=1").unwrap();
21//! url.extend_query_pairs([("sort", "name asc")]);
22//! assert_eq!(url, "https://example.com/api/v1/groups?id=1&sort=name+asc");
23//!
24//! let url = Url::builder()
25//!     .scheme("http")
26//!     .host("münchen.de")
27//!     .path("/a b")
28//!     .query_pair("q", "x&y")
29//!     .build()
30//!     .unwrap();
31//! assert_eq!(url, "http://xn--mnchen-3ya.de/a%20b?q=x%26y");
32//! ```
33//!
34//! # Features
35//!
36//! * `http` - conversions between [`Url`] and `http::Uri`
37//! * `serde` - `Serialize` and `Deserialize` for [`Url`]
38#![deny(missing_docs)]
39
40macro_rules! str_type {
41    ($name:ident) => {
42        impl $name {
43            #[allow(dead_code)]
44            pub(crate) const fn from_str_unchecked(s: &str) -> &$name {
45                // SAFETY: the type is a `repr(transparent)` wrapper around `str`
46                unsafe { &*(s as *const str as *const $name) }
47            }
48
49            /// Returns the component as a string slice.
50            pub const fn as_str(&self) -> &str {
51                &self.0
52            }
53        }
54
55        impl<'a> TryFrom<&'a str> for &'a $name {
56            type Error = $crate::InvalidUrl;
57
58            fn try_from(s: &'a str) -> Result<Self, Self::Error> {
59                $name::new(s)
60            }
61        }
62
63        str_fmt!($name);
64    };
65}
66
67/// `AsRef<str>`, `Display` and `Debug` via `as_str()`.
68macro_rules! str_fmt {
69    ($name:ident) => {
70        impl AsRef<str> for $name {
71            fn as_ref(&self) -> &str {
72                self.as_str()
73            }
74        }
75
76        impl std::fmt::Display for $name {
77            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
78                f.write_str(self.as_str())
79            }
80        }
81
82        impl std::fmt::Debug for $name {
83            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
84                std::fmt::Debug::fmt(self.as_str(), f)
85            }
86        }
87    };
88}
89
90/// Comparison and hashing via `as_str()`.
91macro_rules! str_eq {
92    ($name:ident) => {
93        impl PartialEq for $name {
94            fn eq(&self, other: &$name) -> bool {
95                self.as_str() == other.as_str()
96            }
97        }
98
99        impl Eq for $name {}
100
101        impl PartialEq<str> for $name {
102            fn eq(&self, other: &str) -> bool {
103                self.as_str() == other
104            }
105        }
106
107        impl PartialEq<&str> for $name {
108            fn eq(&self, other: &&str) -> bool {
109                self.as_str() == *other
110            }
111        }
112
113        impl PartialEq<$name> for str {
114            fn eq(&self, other: &$name) -> bool {
115                self == other.as_str()
116            }
117        }
118
119        impl PartialEq<$name> for &str {
120            fn eq(&self, other: &$name) -> bool {
121                *self == other.as_str()
122            }
123        }
124
125        impl std::hash::Hash for $name {
126            fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
127                self.as_str().hash(state)
128            }
129        }
130
131        impl PartialOrd for $name {
132            fn partial_cmp(&self, other: &$name) -> Option<std::cmp::Ordering> {
133                Some(self.cmp(other))
134            }
135        }
136
137        impl Ord for $name {
138            fn cmp(&self, other: &$name) -> std::cmp::Ordering {
139                self.as_str().cmp(other.as_str())
140            }
141        }
142    };
143}
144
145mod authority;
146mod builder;
147mod chars;
148mod error;
149mod host;
150mod idna;
151mod parse;
152mod path;
153mod query;
154pub mod quoting;
155mod scheme;
156mod url;
157
158pub use crate::authority::{Authority, Port, UserInfo};
159pub use crate::builder::{Builder, Parts};
160pub use crate::error::{ErrorKind, InvalidUrl, InvalidUrlParts};
161pub use crate::host::Host;
162pub use crate::path::{Path, PathAndQuery, Segments};
163pub use crate::query::{Fragment, Query, QueryPairs};
164pub use crate::scheme::Scheme;
165pub use crate::url::Url;
166
167#[cfg(doctest)]
168#[doc = include_str!("../README.md")]
169struct ReadmeDoctests;