From 3feb0892e3b5d26344075092449b7b97be12f5cb Mon Sep 17 00:00:00 2001 From: Maximilian Hristache Date: Fri, 27 Jan 2017 01:15:41 +0100 Subject: [PATCH] Add url_serde crate with ser/de support for Url type --- Cargo.toml | 2 +- Makefile | 1 + url_serde/Cargo.toml | 22 +++++ url_serde/README.md | 9 ++ url_serde/src/lib.rs | 215 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 248 insertions(+), 1 deletion(-) create mode 100644 url_serde/Cargo.toml create mode 100644 url_serde/README.md create mode 100644 url_serde/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 3c28394d7..f42873563 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ keywords = ["url", "parser"] license = "MIT/Apache-2.0" [workspace] -members = [".", "idna"] +members = [".", "idna", "url_serde"] [[test]] name = "unit" diff --git a/Makefile b/Makefile index 2042654f4..7a8dc270d 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,6 @@ test: cargo test --features "query_encoding serde rustc-serialize heapsize" (cd idna && cargo test) + (cd url_serde && cargo test) .PHONY: test diff --git a/url_serde/Cargo.toml b/url_serde/Cargo.toml new file mode 100644 index 000000000..1485d38b8 --- /dev/null +++ b/url_serde/Cargo.toml @@ -0,0 +1,22 @@ +[package] + +name = "url_serde" +version = "0.1.0" +authors = ["The rust-url developers"] + +description = "Serde support for URL types" +documentation = "https://docs.rs/url_serde/" +repository = "https://github.com/servo/rust-url" +readme = "README.md" +keywords = ["url", "serde"] +license = "MIT/Apache-2.0" + +[dependencies] +serde = "0.9.0" +url = "1.0.0" + +[dev-dependencies] +serde_json = "0.9.0" + +[lib] +doctest = false diff --git a/url_serde/README.md b/url_serde/README.md new file mode 100644 index 000000000..e1dbe86dd --- /dev/null +++ b/url_serde/README.md @@ -0,0 +1,9 @@ +Serde support for rust-url types +================================ + +This crate provides wrappers and convenience functions to make `rust-url` and `serde` +work hand in hand. + +This crate supports `serde 0.9.0` or newer. Older versions of `serde` are natively supported by `rust-url` crate directly. + +For more details, see the crate [documentation](https://docs.rs/url_serde/). \ No newline at end of file diff --git a/url_serde/src/lib.rs b/url_serde/src/lib.rs new file mode 100644 index 000000000..bbd31acd3 --- /dev/null +++ b/url_serde/src/lib.rs @@ -0,0 +1,215 @@ +/*! + +This crate provides wrappers and convenience functions to make rust-url +and Serde work hand in hand. + +The supported types are: + +* `url::Url` + +# How do I use a data type with a `Url` member with Serde? + +Use the serde attributes `deserialize_with` and `serialize_with`. + +``` +#[derive(serde::Serialize, serde::Deserialize)] +struct MyStruct { + #[serde(deserialize_with = "url_serde::deserialize", + serialize_with = "url_serde::serialize")] + url: Url, +} +``` + +# How do I encode a `Url` value with `serde_json::to_string`? + +Use the `Ser` wrapper. + +``` +serde_json::to_string(&Ser::new(&url)) +``` + +# How do I decode a `Url` value with `serde_json::parse`? + +Use the `De` wrapper. + +``` +serde_json::from_str(r"http:://www.rust-lang.org").map(De::into_inner) +``` + +# How do I send `Url` values as part of an IPC channel? + +Use the `Serde` wrapper. It implements `Deref` and `DerefMut` for convenience. + +``` +ipc::channel::>() +``` +*/ + +#![deny(missing_docs)] +#![deny(unsafe_code)] + +extern crate serde; +extern crate url; + +use std::cmp::PartialEq; +use std::fmt; +use std::ops::{Deref, DerefMut}; +use std::error::Error; +use serde::{Deserialize, Serialize, Serializer, Deserializer}; +use url::{Url}; + + +/// Serialises `value` with a given serializer. +/// +/// This is useful to serialize `rust-url` types used in structure fields or +/// tuple members with `#[serde(serialize_with = "url_serde::serialize")]`. +pub fn serialize(value: &T, serializer: S) -> Result + where S: Serializer, for<'a> Ser<'a, T>: Serialize +{ + Ser::new(value).serialize(serializer) +} + +/// A wrapper to serialize `rust-url` types. +/// +/// This is useful with functions such as `serde_json::to_string`. +/// +/// Values of this type can only be passed to the `serde::Serialize` trait. +#[derive(Debug)] +pub struct Ser<'a, T: 'a>(&'a T); + +impl<'a, T> Ser<'a, T> where Ser<'a, T>: Serialize { + /// Returns a new `Ser` wrapper. + #[inline(always)] + pub fn new(value: &'a T) -> Self { + Ser(value) + } +} + + +/// Serializes this URL into a `serde` stream. +impl<'a> Serialize for Ser<'a, Url> { + fn serialize(&self, serializer: S) -> Result where S: Serializer { + serializer.serialize_str(self.0.as_str()) + } +} + + +/// Deserialises a `T` value with a given deserializer. +/// +/// This is useful to deserialize Url types used in structure fields or +/// tuple members with `#[serde(deserialize_with = "url_serde::deserialize")]`. +pub fn deserialize(deserializer: D) -> Result + where D: Deserializer, De: Deserialize +{ + De::deserialize(deserializer).map(De::into_inner) +} + + +/// A wrapper to deserialize `rust-url` types. +/// +/// This is useful with functions such as `serde_json::from_str`. +/// +/// Values of this type can only be obtained through +/// the `serde::Deserialize` trait. +#[derive(Debug)] +pub struct De(T); + +impl De where De: serde::Deserialize { + /// Consumes this wrapper, returning the deserialized value. + #[inline(always)] + pub fn into_inner(self) -> T { + self.0 + } +} + + +/// Deserializes this URL from a `serde` stream. +impl Deserialize for De { + fn deserialize(deserializer: D) -> Result, D::Error> where D: Deserializer { + let string_representation: String = Deserialize::deserialize(deserializer)?; + Url::parse(&string_representation).map(De).map_err(|err| { + serde::de::Error::custom(err.description()) + }) + } +} + + +/// A convenience wrapper to be used as a type parameter, for example when +/// a `Vec` need to be passed to serde. +#[derive(Clone, PartialEq)] +pub struct Serde(pub T) + where De: Deserialize, for<'a> Ser<'a, T>: Serialize; + +impl Serde +where De: Deserialize, for<'a> Ser<'a, T>: Serialize +{ + /// Consumes this wrapper, returning the inner value. + #[inline(always)] + pub fn into_inner(self) -> T { + self.0 + } +} + +impl fmt::Debug for Serde +where T: fmt::Debug, De: Deserialize, for<'a> Ser<'a, T>: Serialize +{ + fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> { + self.0.fmt(formatter) + } +} + +impl Deref for Serde +where De: Deserialize, for<'a> Ser<'a, T>: Serialize +{ + type Target = T; + + fn deref(&self) -> &T { + &self.0 + } +} + +impl DerefMut for Serde +where De: Deserialize, for<'a> Ser<'a, T>: Serialize +{ + fn deref_mut(&mut self) -> &mut T { + &mut self.0 + } +} + +impl PartialEq for Serde +where De: Deserialize, for<'a> Ser<'a, T>: Serialize +{ + fn eq(&self, other: &T) -> bool { + self.0 == *other + } +} + +impl Deserialize for Serde +where De: Deserialize, for<'a> Ser<'a, T>: Serialize +{ + fn deserialize(deserializer: D) -> Result + where D: Deserializer + { + De::deserialize(deserializer).map(De::into_inner).map(Serde) + } +} + +impl Serialize for Serde +where De: Deserialize, for<'a> Ser<'a, T>: Serialize +{ + fn serialize(&self, serializer: S) -> Result + where S: Serializer + { + Ser(&self.0).serialize(serializer) + } +} + + +#[test] +fn test_ser_de_url() { + extern crate serde_json; + let url = Url::parse("http://www.test.com/foo/bar?$param=bazz").unwrap(); + let s = serde_json::to_string(&Ser::new(&url)).unwrap(); + let new_url: Url = serde_json::from_str(&s).map(De::into_inner).unwrap(); + assert_eq!(url, new_url); +}