From 12335187338460a79992c9fb527b3c256f49d463 Mon Sep 17 00:00:00 2001 From: Matthew Pomes Date: Fri, 21 May 2021 22:41:51 -0500 Subject: [PATCH] Implement (De)Serialize for all URI variants. Closes #1593. --- core/http/src/uri/absolute.rs | 93 ++++++++++++++++++++++++++++ core/http/src/uri/asterisk.rs | 45 ++++++++++++++ core/http/src/uri/authority.rs | 89 +++++++++++++++++++++++++++ core/http/src/uri/origin.rs | 56 +++++++++++++++++ core/http/src/uri/reference.rs | 58 ++++++++++++++++++ core/http/src/uri/uri.rs | 5 ++ core/lib/tests/http_uri_serde.rs | 101 +++++++++++++++++++++++++++++++ 7 files changed, 447 insertions(+) create mode 100644 core/lib/tests/http_uri_serde.rs diff --git a/core/http/src/uri/absolute.rs b/core/http/src/uri/absolute.rs index 2ec3aad6..48544036 100644 --- a/core/http/src/uri/absolute.rs +++ b/core/http/src/uri/absolute.rs @@ -64,6 +64,21 @@ use crate::uri::{Authority, Path, Query, Data, Error, as_utf8_unchecked, fmt}; /// # assert!(!Absolute::parse(uri).unwrap().is_normalized()); /// # } /// ``` +/// +/// ## Serde +/// +/// For convience, `Absolute` implements `Serialize` and `Deserialize`. +/// Because `Absolute` has a lifetime parameter, serde requires a borrow +/// attribute for the derive macro to work. If you want to own the Uri, +/// rather than borrow from the deserializer, use `'static`. +/// +/// ```ignore +/// #[derive(Deserialize)] +/// struct Uris<'a> { +/// #[serde(borrow)] +/// absolute: Absolute<'a>, +/// } +/// ``` #[derive(Debug, Clone)] pub struct Absolute<'a> { pub(crate) source: Option>, @@ -115,6 +130,42 @@ impl<'a> Absolute<'a> { crate::parse::uri::absolute_from_str(string) } + /// Parses the string `string` into an `Absolute`. Parsing will never + /// May allocate on error. + /// + /// TODO: avoid allocation + /// + /// This method should be used instead of [`Absolute::parse()`] when + /// the source URI is already a `String`. Returns an `Error` if `string` is + /// not a valid absolute URI. + /// + /// # Example + /// + /// ```rust + /// # extern crate rocket; + /// use rocket::http::uri::Absolute; + /// + /// let source = format!("https://rocket.rs/foo/{}/three", 2); + /// let uri = Absolute::parse_owned(source).expect("valid URI"); + /// assert_eq!(uri.authority().unwrap().host(), "rocket.rs"); + /// assert_eq!(uri.path(), "/foo/2/three"); + /// assert!(uri.query().is_none()); + /// ``` + pub fn parse_owned(string: String) -> Result, Error<'static>> { + let absolute = Absolute::parse(&string).map_err(|e| e.into_owned())?; + debug_assert!(absolute.source.is_some(), "Origin source parsed w/o source"); + + let absolute = Absolute { + scheme: absolute.scheme.into_owned(), + authority: absolute.authority.into_owned(), + query: absolute.query.into_owned(), + path: absolute.path.into_owned(), + source: Some(Cow::Owned(string)), + }; + + Ok(absolute) + } + /// Returns the scheme part of the absolute URI. /// /// # Example @@ -383,6 +434,7 @@ impl<'a> Absolute<'a> { } /// PRIVATE. Used by codegen. + #[doc(hidden)] pub const fn const_new( scheme: &'a str, authority: Option>, @@ -460,3 +512,44 @@ impl std::fmt::Display for Absolute<'_> { Ok(()) } } + +#[cfg(feature = "serde")] +mod serde { + use std::fmt; + + use super::Absolute; + use _serde::{ser::{Serialize, Serializer}, de::{Deserialize, Deserializer, Error, Visitor}}; + + impl<'a> Serialize for Absolute<'a> { + fn serialize(&self, serializer: S) -> Result { + serializer.serialize_str(&self.to_string()) + } + } + + struct AbsoluteVistor; + + impl<'a> Visitor<'a> for AbsoluteVistor { + type Value = Absolute<'a>; + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(formatter, "absolute Uri") + } + + fn visit_str(self, v: &str) -> Result { + Absolute::parse_owned(v.to_string()).map_err(Error::custom) + } + + fn visit_string(self, v: String) -> Result { + Absolute::parse_owned(v).map_err(Error::custom) + } + + fn visit_borrowed_str(self, v: &'a str) -> Result { + Absolute::parse(v).map_err(Error::custom) + } + } + + impl<'a, 'de: 'a> Deserialize<'de> for Absolute<'a> { + fn deserialize>(deserializer: D) -> Result { + deserializer.deserialize_str(AbsoluteVistor) + } + } +} diff --git a/core/http/src/uri/asterisk.rs b/core/http/src/uri/asterisk.rs index 2edadd49..96095bfb 100644 --- a/core/http/src/uri/asterisk.rs +++ b/core/http/src/uri/asterisk.rs @@ -1,4 +1,8 @@ /// The literal `*` URI. +/// +/// ## Serde +/// +/// For convience, `Asterisk` implements `Serialize` and `Deserialize`. #[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)] pub struct Asterisk; @@ -7,3 +11,44 @@ impl std::fmt::Display for Asterisk { "*".fmt(f) } } + +#[cfg(feature = "serde")] +mod serde { + use std::fmt; + + use super::Asterisk; + use _serde::{ser::{Serialize, Serializer}, de::{Deserialize, Deserializer, Error, Visitor}}; + + impl Serialize for Asterisk { + fn serialize(&self, serializer: S) -> Result { + serializer.serialize_str("*") + } + } + + struct AsteriskVistor; + + impl<'a> Visitor<'a> for AsteriskVistor { + type Value = Asterisk; + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(formatter, "asterisk Uri") + } + + // This method should be the only one that needs to be implemented, since the + // other two methods (`visit_string` & `visit_borrowed_str`) have default implementations + // that just call this one. We don't benefit from taking ownership or borrowing from the + // deserializer, so this should be perfect. + fn visit_str(self, v: &str) -> Result { + if v == "*" { + Ok(Asterisk) + }else { + Err(E::custom(format!("`{}` is not a valid asterisk uri", v))) + } + } + } + + impl<'de> Deserialize<'de> for Asterisk { + fn deserialize>(deserializer: D) -> Result { + deserializer.deserialize_str(AsteriskVistor) + } + } +} diff --git a/core/http/src/uri/authority.rs b/core/http/src/uri/authority.rs index d6bbf2d7..b06a7fd6 100644 --- a/core/http/src/uri/authority.rs +++ b/core/http/src/uri/authority.rs @@ -20,6 +20,21 @@ use crate::uri::{as_utf8_unchecked, error::Error}; /// ``` /// /// Only the host part of the URI is required. +/// +/// ## Serde +/// +/// For convience, `Authority` implements `Serialize` and `Deserialize`. +/// Because `Authority` has a lifetime parameter, serde requires a borrow +/// attribute for the derive macro to work. If you want to own the Uri, +/// rather than borrow from the deserializer, use `'static`. +/// +/// ```ignore +/// #[derive(Deserialize)] +/// struct Uris<'a> { +/// #[serde(borrow)] +/// authority: Authority<'a>, +/// } +/// ``` #[derive(Debug, Clone)] pub struct Authority<'a> { pub(crate) source: Option>, @@ -109,6 +124,39 @@ impl<'a> Authority<'a> { crate::parse::uri::authority_from_str(string) } + /// Parses the string `string` into an `Authority`. Parsing will never allocate. + /// May allocate on error. + /// + /// This method should be used instead of [`Authority::parse()`] when + /// the source URI is already a `String`. Returns an `Error` if `string` is + /// not a valid authority URI. + /// + /// # Example + /// + /// ```rust + /// # extern crate rocket; + /// use rocket::http::uri::Authority; + /// + /// let source = format!("rocket.rs:8000"); + /// let uri = Authority::parse_owned(source).expect("valid URI"); + /// assert!(uri.user_info().is_none()); + /// assert_eq!(uri.host(), "rocket.rs"); + /// assert_eq!(uri.port(), Some(8000)); + /// ``` + pub fn parse_owned(string: String) -> Result, Error<'static>> { + let authority = Authority::parse(&string).map_err(|e| e.into_owned())?; + debug_assert!(authority.source.is_some(), "Origin source parsed w/o source"); + + let authority = Authority { + host: authority.host.into_owned(), + user_info: authority.user_info.into_owned(), + port: authority.port, + source: Some(Cow::Owned(string)), + }; + + Ok(authority) + } + /// Returns the user info part of the authority URI, if there is one. /// /// # Example @@ -207,3 +255,44 @@ impl<'a> TryFrom<&'a str> for Authority<'a> { Authority::parse(value) } } + +#[cfg(feature = "serde")] +mod serde { + use std::fmt; + + use super::Authority; + use _serde::{ser::{Serialize, Serializer}, de::{Deserialize, Deserializer, Error, Visitor}}; + + impl<'a> Serialize for Authority<'a> { + fn serialize(&self, serializer: S) -> Result { + serializer.serialize_str(&self.to_string()) + } + } + + struct AuthorityVistor; + + impl<'a> Visitor<'a> for AuthorityVistor { + type Value = Authority<'a>; + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(formatter, "authority Uri") + } + + fn visit_str(self, v: &str) -> Result { + Authority::parse_owned(v.to_string()).map_err(Error::custom) + } + + fn visit_string(self, v: String) -> Result { + Authority::parse_owned(v).map_err(Error::custom) + } + + fn visit_borrowed_str(self, v: &'a str) -> Result { + Authority::parse(v).map_err(Error::custom) + } + } + + impl<'a, 'de: 'a> Deserialize<'de> for Authority<'a> { + fn deserialize>(deserializer: D) -> Result { + deserializer.deserialize_str(AuthorityVistor) + } + } +} diff --git a/core/http/src/uri/origin.rs b/core/http/src/uri/origin.rs index 5abe9191..a76ffe57 100644 --- a/core/http/src/uri/origin.rs +++ b/core/http/src/uri/origin.rs @@ -85,6 +85,21 @@ use crate::{RawStr, RawStrBuf}; /// # assert_eq!(abnormal.into_normalized(), expected); /// # } /// ``` +/// +/// ## Serde +/// +/// For convience, `Origin` implements `Serialize` and `Deserialize`. +/// Because `Origin` has a lifetime parameter, serde requires a borrow +/// attribute for the derive macro to work. If you want to own the Uri, +/// rather than borrow from the deserializer, use `'static`. +/// +/// ```ignore +/// #[derive(Deserialize)] +/// struct Uris<'a> { +/// #[serde(borrow)] +/// origin: Origin<'a>, +/// } +/// ``` #[derive(Debug, Clone)] pub struct Origin<'a> { pub(crate) source: Option>, @@ -486,6 +501,47 @@ impl std::fmt::Display for Origin<'_> { } } +#[cfg(feature = "serde")] +mod serde { + use std::fmt; + + use super::Origin; + use _serde::{ser::{Serialize, Serializer}, de::{Deserialize, Deserializer, Error, Visitor}}; + + impl<'a> Serialize for Origin<'a> { + fn serialize(&self, serializer: S) -> Result { + serializer.serialize_str(&self.to_string()) + } + } + + struct OriginVistor; + + impl<'a> Visitor<'a> for OriginVistor { + type Value = Origin<'a>; + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(formatter, "origin Uri") + } + + fn visit_str(self, v: &str) -> Result { + Origin::parse_owned(v.to_string()).map_err(E::custom) + } + + fn visit_string(self, v: String) -> Result { + Origin::parse_owned(v).map_err(E::custom) + } + + fn visit_borrowed_str(self, v: &'a str) -> Result { + Origin::parse(v).map_err(E::custom) + } + } + + impl<'a, 'de: 'a> Deserialize<'de> for Origin<'a> { + fn deserialize>(deserializer: D) -> Result { + deserializer.deserialize_str(OriginVistor) + } + } +} + #[cfg(test)] mod tests { use super::Origin; diff --git a/core/http/src/uri/reference.rs b/core/http/src/uri/reference.rs index 76249662..204b6d31 100644 --- a/core/http/src/uri/reference.rs +++ b/core/http/src/uri/reference.rs @@ -45,6 +45,21 @@ use crate::parse::{Extent, IndexedStr}; /// Note that `uri!()` macro _always_ prefers the more specific URI variant to /// `Reference` when possible, as is demonstrated above for `absolute` and /// `origin`. +/// +/// ## Serde +/// +/// For convience, `Reference` implements `Serialize` and `Deserialize`. +/// Because `Reference` has a lifetime parameter, serde requires a borrow +/// attribute for the derive macro to work. If you want to own the Uri, +/// rather than borrow from the deserializer, use `'static`. +/// +/// ```ignore +/// #[derive(Deserialize)] +/// struct Uris<'a> { +/// #[serde(borrow)] +/// reference: Reference<'a>, +/// } +/// ``` #[derive(Debug, Clone)] pub struct Reference<'a> { source: Option>, @@ -151,6 +166,8 @@ impl<'a> Reference<'a> { /// Parses the string `string` into a `Reference`. Never allocates on /// success. May allocate on error. /// + /// TODO: Avoid allocation + /// /// This method should be used instead of [`Reference::parse()`] when the /// source URI is already a `String`. Returns an `Error` if `string` is not /// a valid URI reference. @@ -530,3 +547,44 @@ impl std::fmt::Display for Reference<'_> { Ok(()) } } + +#[cfg(feature = "serde")] +mod serde { + use std::fmt; + + use super::Reference; + use _serde::{ser::{Serialize, Serializer}, de::{Deserialize, Deserializer, Error, Visitor}}; + + impl<'a> Serialize for Reference<'a> { + fn serialize(&self, serializer: S) -> Result { + serializer.serialize_str(&self.to_string()) + } + } + + struct ReferenceVistor; + + impl<'a> Visitor<'a> for ReferenceVistor { + type Value = Reference<'a>; + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(formatter, "reference Uri") + } + + fn visit_str(self, v: &str) -> Result { + Reference::parse_owned(v.to_string()).map_err(Error::custom) + } + + fn visit_string(self, v: String) -> Result { + Reference::parse_owned(v).map_err(Error::custom) + } + + fn visit_borrowed_str(self, v: &'a str) -> Result { + Reference::parse(v).map_err(Error::custom) + } + } + + impl<'a, 'de: 'a> Deserialize<'de> for Reference<'a> { + fn deserialize>(deserializer: D) -> Result { + deserializer.deserialize_str(ReferenceVistor) + } + } +} diff --git a/core/http/src/uri/uri.rs b/core/http/src/uri/uri.rs index aeccf37f..f7aba737 100644 --- a/core/http/src/uri/uri.rs +++ b/core/http/src/uri/uri.rs @@ -33,6 +33,11 @@ use crate::uri::error::{Error, TryFromUriError}; /// methods of the internal structure. /// /// [RFC 7230]: https://tools.ietf.org/html/rfc7230 +/// +/// ## Serde +/// Parsing a string into a `Uri` is ambgious, so `Uri` does not implement `Serialize` +/// or `Deserialize`, although all of the variants do. See [`Uri::parse_any`] for more +/// information #[derive(Debug, PartialEq, Clone)] pub enum Uri<'a> { /// An asterisk: exactly `*`. diff --git a/core/lib/tests/http_uri_serde.rs b/core/lib/tests/http_uri_serde.rs new file mode 100644 index 00000000..0093a246 --- /dev/null +++ b/core/lib/tests/http_uri_serde.rs @@ -0,0 +1,101 @@ +use figment::{Figment, providers::Serialized}; +use rocket::{Config, uri}; +use rocket_http::uri::{Absolute, Asterisk, Authority, Origin, Reference}; +use serde::{Serialize, Deserialize}; +use pretty_assertions::assert_eq; + +#[derive(PartialEq, Debug, Serialize, Deserialize)] +struct UriContainer<'a> { + asterisk: Asterisk, + #[serde(borrow)] + origin: Origin<'a>, + #[serde(borrow)] + authority: Authority<'a>, + #[serde(borrow)] + absolute: Absolute<'a>, + #[serde(borrow)] + reference: Reference<'a>, +} + +#[derive(PartialEq, Debug, Serialize, Deserialize)] +struct UriContainerOwned { + asterisk: Asterisk, + #[serde(borrow)] + origin: Origin<'static>, + #[serde(borrow)] + authority: Authority<'static>, + #[serde(borrow)] + absolute: Absolute<'static>, + #[serde(borrow)] + reference: Reference<'static>, +} + +#[test] +fn uri_serde() { + figment::Jail::expect_with(|jail| { + jail.create_file("Rocket.toml", r#" + [default] + asterisk = "*" + origin = "/foo/bar?baz" + authority = "user:pass@rocket.rs:80" + absolute = "https://rocket.rs/foo/bar" + reference = "https://rocket.rs:8000/index.html" + "#)?; + + let uris: UriContainer<'_> = Config::figment().extract()?; + assert_eq!(uris, UriContainer { + asterisk: Asterisk, + origin: uri!("/foo/bar?baz"), + authority: uri!("user:pass@rocket.rs:80"), + absolute: uri!("https://rocket.rs/foo/bar"), + reference: uri!("https://rocket.rs:8000/index.html").into(), + }); + + Ok(()) + }); +} + +#[test] +fn uri_serde_owned() { + figment::Jail::expect_with(|jail| { + jail.create_file("Rocket.toml", r#" + [default] + asterisk = "*" + origin = "/foo/bar?baz" + authority = "user:pass@rocket.rs:80" + absolute = "https://rocket.rs/foo/bar" + reference = "https://rocket.rs:8000/index.html" + "#)?; + + let uris: UriContainerOwned = Config::figment().extract()?; + assert_eq!(uris, UriContainerOwned { + asterisk: Asterisk, + origin: uri!("/foo/bar?baz"), + authority: uri!("user:pass@rocket.rs:80"), + absolute: uri!("https://rocket.rs/foo/bar"), + reference: uri!("https://rocket.rs:8000/index.html").into(), + }); + + Ok(()) + }); +} + +#[test] +fn uri_serde_round_trip() { + let tmp = Figment::from(Serialized::defaults(UriContainer { + asterisk: Asterisk, + origin: uri!("/foo/bar?baz"), + authority: uri!("user:pass@rocket.rs:80"), + absolute: uri!("https://rocket.rs/foo/bar"), + reference: uri!("https://rocket.rs:8000/index.html").into(), + })); + + let uris: UriContainer<'_> = tmp.extract().expect("Parsing failed"); + assert_eq!(uris, UriContainer { + asterisk: Asterisk, + origin: uri!("/foo/bar?baz"), + authority: uri!("user:pass@rocket.rs:80"), + absolute: uri!("https://rocket.rs/foo/bar"), + reference: uri!("https://rocket.rs:8000/index.html").into(), + }); +}