# run().unwrap();
```
+## Serde
+
+Enable the `serde` feature to include `Deserialize` and `Serialize` implementations for `url::Url`.
# Base URL
# Ok(())
# }
# run().unwrap();
+```
+
+# Feature: `serde`
+
+If you enable the `serde` feature, [`Url`](struct.Url.html) will implement
+[`serde::Serialize`](https://docs.rs/serde/1/serde/trait.Serialize.html) and
+[`serde::Deserialize`](https://docs.rs/serde/1/serde/trait.Deserialize.html).
+See [serde documentation](https://serde.rs) for more information.
+
+```toml
+url = { version = "2", features = ["serde"] }
+```
*/
-#![doc(html_root_url = "https://docs.rs/url/2.1.1")]
+#![doc(html_root_url = "https://docs.rs/url/2.2.1")]
#[macro_use]
extern crate matches;
-extern crate idna;
-extern crate percent_encoding;
+pub use form_urlencoded;
+
#[cfg(feature = "serde")]
extern crate serde;
-use host::HostInternal;
-use parser::{to_u32, Context, Parser, SchemeType, PATH_SEGMENT, USERINFO};
+use crate::host::HostInternal;
+use crate::parser::{to_u32, Context, Parser, SchemeType, PATH_SEGMENT, USERINFO};
use percent_encoding::{percent_decode, percent_encode, utf8_percent_encode};
use std::borrow::Borrow;
use std::cmp;
-#[cfg(feature = "serde")]
-use std::error::Error;
use std::fmt::{self, Write};
use std::hash;
use std::io;
use std::path::{Path, PathBuf};
use std::str;
-pub use host::Host;
-pub use origin::{OpaqueOrigin, Origin};
-pub use parser::{ParseError, SyntaxViolation};
-pub use path_segments::PathSegmentsMut;
-pub use query_encoding::EncodingOverride;
-pub use slicing::Position;
+use std::convert::TryFrom;
+
+pub use crate::host::Host;
+pub use crate::origin::{OpaqueOrigin, Origin};
+pub use crate::parser::{ParseError, SyntaxViolation};
+pub use crate::path_segments::PathSegmentsMut;
+pub use crate::slicing::Position;
+pub use form_urlencoded::EncodingOverride;
mod host;
mod origin;
mod parser;
mod path_segments;
-mod query_encoding;
mod slicing;
-pub mod form_urlencoded;
#[doc(hidden)]
pub mod quirks;
}
/// Parse an URL string with the configuration so far.
- pub fn parse(self, input: &str) -> Result<Url, ::ParseError> {
+ pub fn parse(self, input: &str) -> Result<Url, crate::ParseError> {
Parser {
serialization: String::with_capacity(input.len()),
base_url: self.base_url,
///
/// [`ParseError`]: enum.ParseError.html
#[inline]
- pub fn parse(input: &str) -> Result<Url, ::ParseError> {
+ pub fn parse(input: &str) -> Result<Url, crate::ParseError> {
Url::options().parse(input)
}
/// # fn run() -> Result<(), ParseError> {
/// let url = Url::parse_with_params("https://example.net?dont=clobberme",
/// &[("lang", "rust"), ("browser", "servo")])?;
+ /// assert_eq!("https://example.net/?dont=clobberme&lang=rust&browser=servo", url.as_str());
/// # Ok(())
/// # }
/// # run().unwrap();
///
/// [`ParseError`]: enum.ParseError.html
#[inline]
- pub fn parse_with_params<I, K, V>(input: &str, iter: I) -> Result<Url, ::ParseError>
+ pub fn parse_with_params<I, K, V>(input: &str, iter: I) -> Result<Url, crate::ParseError>
where
I: IntoIterator,
I::Item: Borrow<(K, V)>,
///
/// [`ParseError`]: enum.ParseError.html
#[inline]
- pub fn join(&self, input: &str) -> Result<Url, ::ParseError> {
+ pub fn join(&self, input: &str) -> Result<Url, crate::ParseError> {
Url::options().base_url(Some(self)).parse(input)
}
/// # run().unwrap();
/// ```
pub fn username(&self) -> &str {
- if self.has_authority() {
- self.slice(self.scheme_end + ("://".len() as u32)..self.username_end)
+ let scheme_separator_len = "://".len() as u32;
+ if self.has_authority() && self.username_end > self.scheme_end + scheme_separator_len {
+ self.slice(self.scheme_end + scheme_separator_len..self.username_end)
} else {
""
}
/// Return the string representation of the host (domain or IP address) for this URL, if any.
///
- /// Non-ASCII domains are punycode-encoded per IDNA.
+ /// Non-ASCII domains are punycode-encoded per IDNA if this is the host
+ /// of a special URL, or percent encoded for non-special URLs.
/// IPv6 addresses are given between `[` and `]` brackets.
///
/// Cannot-be-a-base URLs (typical of `data:` and `mailto:`) and some `file:` URLs
}
/// Return the parsed representation of the host for this URL.
- /// Non-ASCII domain labels are punycode-encoded per IDNA.
+ /// Non-ASCII domain labels are punycode-encoded per IDNA if this is the host
+ /// of a special URL, or percent encoded for non-special URLs.
///
/// Cannot-be-a-base URLs (typical of `data:` and `mailto:`) and some `file:` URLs
/// don’t have a host.
}
/// If this URL has a host and it is a domain name (not an IP address), return it.
+ /// Non-ASCII domains are punycode-encoded per IDNA if this is the host
+ /// of a special URL, or percent encoded for non-special URLs.
///
/// # Examples
///
/// Return the port number for this URL, or the default port number if it is known.
///
/// This method only knows the default port number
- /// of the `http`, `https`, `ws`, `wss`, `ftp`, and `gopher` schemes.
+ /// of the `http`, `https`, `ws`, `wss` and `ftp` schemes.
///
/// For URLs in these schemes, this method always returns `Some(_)`.
/// For other schemes, it is the same as `Url::port()`.
/// use url::Url;
/// # use std::error::Error;
///
- /// # fn run() -> Result<(), Box<Error>> {
+ /// # fn run() -> Result<(), Box<dyn Error>> {
/// let url = Url::parse("https://example.com/foo/bar")?;
/// let mut path_segments = url.path_segments().ok_or_else(|| "cannot be base")?;
/// assert_eq!(path_segments.next(), Some("foo"));
/// # }
/// # run().unwrap();
/// ```
- pub fn path_segments(&self) -> Option<str::Split<char>> {
+ #[allow(clippy::manual_strip)] // introduced in 1.45, MSRV is 1.36
+ pub fn path_segments(&self) -> Option<str::Split<'_, char>> {
let path = self.path();
if path.starts_with('/') {
Some(path[1..].split('/'))
///
#[inline]
- pub fn query_pairs(&self) -> form_urlencoded::Parse {
+ pub fn query_pairs(&self) -> form_urlencoded::Parse<'_> {
form_urlencoded::parse(self.query().unwrap_or("").as_bytes())
}
})
}
- fn mutate<F: FnOnce(&mut Parser) -> R, R>(&mut self, f: F) -> R {
+ fn mutate<F: FnOnce(&mut Parser<'_>) -> R, R>(&mut self, f: F) -> R {
let mut parser = Parser::for_setter(mem::replace(&mut self.serialization, String::new()));
let result = f(&mut parser);
self.serialization = parser.serialization;
/// not `url.set_query(None)`.
///
/// The state of `Url` is unspecified if this return value is leaked without being dropped.
- pub fn query_pairs_mut(&mut self) -> form_urlencoded::Serializer<UrlQuery> {
+ pub fn query_pairs_mut(&mut self) -> form_urlencoded::Serializer<'_, UrlQuery<'_>> {
let fragment = self.take_fragment();
let query_start;
/// Return an object with methods to manipulate this URL’s path segments.
///
/// Return `Err(())` if this URL is cannot-be-a-base.
- pub fn path_segments_mut(&mut self) -> Result<PathSegmentsMut, ()> {
+ #[allow(clippy::clippy::result_unit_err)]
+ pub fn path_segments_mut(&mut self) -> Result<PathSegmentsMut<'_>, ()> {
if self.cannot_be_a_base() {
Err(())
} else {
/// use url::Url;
/// # use std::error::Error;
///
- /// # fn run() -> Result<(), Box<Error>> {
+ /// # fn run() -> Result<(), Box<dyn Error>> {
/// let mut url = Url::parse("ssh://example.net:2048/")?;
///
/// url.set_port(Some(4096)).map_err(|_| "cannot be base")?;
/// use url::Url;
/// # use std::error::Error;
///
- /// # fn run() -> Result<(), Box<Error>> {
+ /// # fn run() -> Result<(), Box<dyn Error>> {
/// let mut url = Url::parse("https://example.org/")?;
///
/// url.set_port(Some(443)).map_err(|_| "cannot be base")?;
/// # }
/// # run().unwrap();
/// ```
+ #[allow(clippy::clippy::result_unit_err)]
pub fn set_port(&mut self, mut port: Option<u16>) -> Result<(), ()> {
// has_host implies !cannot_be_a_base
if !self.has_host() || self.host() == Some(Host::Domain("")) || self.scheme() == "file" {
}
if let Some(host) = host {
- if host == "" && SchemeType::from(self.scheme()).is_special() {
+ if host.is_empty() && SchemeType::from(self.scheme()).is_special() {
return Err(ParseError::EmptyHost);
}
let mut host_substr = host;
let scheme_type = SchemeType::from(self.scheme());
if scheme_type.is_special() {
return Err(ParseError::EmptyHost);
- } else {
- if self.serialization.len() == self.path_start as usize {
- self.serialization.push('/');
- }
+ } else if self.serialization.len() == self.path_start as usize {
+ self.serialization.push('/');
}
debug_assert!(self.byte_at(self.scheme_end) == b':');
debug_assert!(self.byte_at(self.path_start) == b'/');
/// # run().unwrap();
/// ```
///
+ #[allow(clippy::clippy::result_unit_err)]
pub fn set_ip_host(&mut self, address: IpAddr) -> Result<(), ()> {
if self.cannot_be_a_base() {
return Err(());
/// # }
/// # run().unwrap();
/// ```
+ #[allow(clippy::clippy::result_unit_err)]
pub fn set_password(&mut self, password: Option<&str>) -> Result<(), ()> {
// has_host implies !cannot_be_a_base
if !self.has_host() || self.host() == Some(Host::Domain("")) || self.scheme() == "file" {
/// # }
/// # run().unwrap();
/// ```
+ #[allow(clippy::clippy::result_unit_err)]
pub fn set_username(&mut self, username: &str) -> Result<(), ()> {
// has_host implies !cannot_be_a_base
if !self.has_host() || self.host() == Some(Host::Domain("")) || self.scheme() == "file" {
/// Change this URL’s scheme.
///
- /// Do nothing and return `Err` if:
+ /// Do nothing and return `Err` under the following circumstances:
///
- /// * The new scheme is not in `[a-zA-Z][a-zA-Z0-9+.-]+`
- /// * This URL is cannot-be-a-base and the new scheme is one of
- /// `http`, `https`, `ws`, `wss`, `ftp`, or `gopher`
+ /// * If the new scheme is not in `[a-zA-Z][a-zA-Z0-9+.-]+`
+ /// * If this URL is cannot-be-a-base and the new scheme is one of
+ /// `http`, `https`, `ws`, `wss` or `ftp`
+ /// * If either the old or new scheme is `http`, `https`, `ws`,
+ /// `wss` or `ftp` and the other is not one of these
+ /// * If the new scheme is `file` and this URL includes credentials
+ /// or has a non-null port
+ /// * If this URL's scheme is `file` and its host is empty or null
+ ///
+ /// See also [the URL specification's section on legal scheme state
+ /// overrides](https://url.spec.whatwg.org/#scheme-state).
///
/// # Examples
///
/// # }
/// # run().unwrap();
/// ```
+ #[allow(clippy::result_unit_err, clippy::suspicious_operation_groupings)]
pub fn set_scheme(&mut self, scheme: &str) -> Result<(), ()> {
let mut parser = Parser::for_setter(String::new());
let remaining = parser.parse_scheme(parser::Input::new(scheme))?;
/// # }
/// ```
#[cfg(any(unix, windows, target_os = "redox"))]
+ #[allow(clippy::clippy::result_unit_err)]
pub fn from_file_path<P: AsRef<Path>>(path: P) -> Result<Url, ()> {
let mut serialization = "file://".to_owned();
let host_start = serialization.len() as u32;
/// Note that `std::path` does not consider trailing slashes significant
/// and usually does not include them (e.g. in `Path::parent()`).
#[cfg(any(unix, windows, target_os = "redox"))]
+ #[allow(clippy::clippy::result_unit_err)]
pub fn from_directory_path<P: AsRef<Path>>(path: P) -> Result<Url, ()> {
let mut url = Url::from_file_path(path)?;
if !url.serialization.ends_with('/') {
/// for a Windows path, is not UTF-8.)
#[inline]
#[cfg(any(unix, windows, target_os = "redox"))]
+ #[allow(clippy::clippy::result_unit_err)]
pub fn to_file_path(&self) -> Result<PathBuf, ()> {
if let Some(segments) = self.path_segments() {
let host = match self.host() {
type Err = ParseError;
#[inline]
- fn from_str(input: &str) -> Result<Url, ::ParseError> {
+ fn from_str(input: &str) -> Result<Url, crate::ParseError> {
Url::parse(input)
}
}
+impl<'a> TryFrom<&'a str> for Url {
+ type Error = ParseError;
+
+ fn try_from(s: &'a str) -> Result<Self, Self::Error> {
+ Url::parse(s)
+ }
+}
+
/// Display the serialization of this URL.
impl fmt::Display for Url {
#[inline]
- fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+ fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.serialization, formatter)
}
}
impl fmt::Debug for Url {
#[inline]
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
- fmt::Debug::fmt(&self.serialization, formatter)
+ formatter
+ .debug_struct("Url")
+ .field("scheme", &self.scheme())
+ .field("username", &self.username())
+ .field("password", &self.password())
+ .field("host", &self.host())
+ .field("port", &self.port())
+ .field("path", &self.path())
+ .field("query", &self.query())
+ .field("fragment", &self.fragment())
+ .finish()
}
}
where
E: Error,
{
- Url::parse(s)
- .map_err(|err| Error::invalid_value(Unexpected::Str(s), &err.description()))
+ Url::parse(s).map_err(|err| {
+ let err_s = format!("{}", err);
+ Error::invalid_value(Unexpected::Str(s), &err_s.as_str())
+ })
}
}
#[cfg(any(unix, target_os = "redox"))]
fn file_url_segments_to_pathbuf(
host: Option<&str>,
- segments: str::Split<char>,
+ segments: str::Split<'_, char>,
) -> Result<PathBuf, ()> {
use std::ffi::OsStr;
use std::os::unix::prelude::OsStrExt;
bytes.extend(percent_decode(segment.as_bytes()));
}
// A windows drive letter must end with a slash.
- if bytes.len() > 2 {
- if matches!(bytes[bytes.len() - 2], b'a'..=b'z' | b'A'..=b'Z')
- && matches!(bytes[bytes.len() - 1], b':' | b'|')
- {
- bytes.push(b'/');
- }
+ if bytes.len() > 2
+ && matches!(bytes[bytes.len() - 2], b'a'..=b'z' | b'A'..=b'Z')
+ && matches!(bytes[bytes.len() - 1], b':' | b'|')
+ {
+ bytes.push(b'/');
}
let os_str = OsStr::from_bytes(&bytes);
let path = PathBuf::from(os_str);
#[cfg_attr(not(windows), allow(dead_code))]
fn file_url_segments_to_pathbuf_windows(
host: Option<&str>,
- mut segments: str::Split<char>,
+ mut segments: str::Split<'_, char>,
) -> Result<PathBuf, ()> {
let mut string = if let Some(host) = host {
r"\\".to_owned() + host
fragment: Option<String>,
}
+// `as_mut_string` string here exposes the internal serialization of an `Url`,
+// which should not be exposed to users.
+// We achieve that by not giving users direct access to `UrlQuery`:
+// * Its fields are private
+// (and so can not be constructed with struct literal syntax outside of this crate),
+// * It has no constructor
+// * It is only visible (on the type level) to users in the return type of
+// `Url::query_pairs_mut` which is `Serializer<UrlQuery>`
+// * `Serializer` keeps its target in a private field
+// * Unlike in other `Target` impls, `UrlQuery::finished` does not return `Self`.
+impl<'a> form_urlencoded::Target for UrlQuery<'a> {
+ fn as_mut_string(&mut self) -> &mut String {
+ &mut self.url.as_mut().unwrap().serialization
+ }
+
+ fn finish(mut self) -> &'a mut Url {
+ let url = self.url.take().unwrap();
+ url.restore_already_parsed_fragment(self.fragment.take());
+ url
+ }
+
+ type Finished = &'a mut Url;
+}
+
impl<'a> Drop for UrlQuery<'a> {
fn drop(&mut self) {
if let Some(url) = self.url.take() {