From c4ecf683088a22875a80153b7b4e8b6124e05626 Mon Sep 17 00:00:00 2001 From: Tpt Date: Sat, 26 Mar 2022 18:49:31 +0100 Subject: [PATCH] Makes Iri and IriRef implement Serde (De)Serialize traits --- .github/workflows/build.yml | 8 ++++--- Cargo.toml | 7 ++++++ README.md | 2 ++ src/lib.rs | 35 +++++++++++++++++++++++++++ tests/lib.rs | 48 +++++++++++++++++++++++++++++++++++++ 5 files changed, 97 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 223ba6b..d8e4061 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -19,15 +19,17 @@ jobs: - uses: actions/checkout@v1 - run: rustup update - run: rustup component add clippy - - run: cargo clippy --all --all-targets + - run: cargo clippy --all-targets + - run: cargo clippy --all-targets --all-features test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 - run: rustup update - - run: cargo build - - run: cargo test --verbose --all --all-targets + - run: cargo build --all-features + - run: cargo test --verbose + - run: cargo test --verbose --all-features env: RUST_BACKTRACE: 1 diff --git a/Cargo.toml b/Cargo.toml index f083dd2..6608084 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,9 +11,16 @@ Simple and fast implementation of IRI validation and relative IRI resolution """ edition = "2018" +[dependencies] +serde = { version = "1", optional = true } + [dev-dependencies] criterion = "0.3" +serde_test = "1" [[bench]] name = "lib" harness = false + +[package.metadata.docs.rs] +all-features = true diff --git a/README.md b/README.md index a10b08d..a00113d 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,8 @@ assert_eq!(iri.query(), None); assert_eq!(iri.fragment(), Some("foo")); ``` +If [`serde`](https://serde.rs/) is available, `Iri` and `IriRef` implement the `Serialize` and `Deserialize` traits and encode the IRI as a string. + ## License diff --git a/src/lib.rs b/src/lib.rs index cd33e59..ca82d0b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,8 @@ #![doc = include_str!("../README.md")] #![deny(unsafe_code)] +#[cfg(feature = "serde")] +use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::borrow::{Borrow, Cow}; use std::cmp::Ordering; use std::convert::{TryFrom, TryInto}; @@ -443,6 +445,22 @@ impl<'a> From<&'a IriRef>> for IriRef<&'a str> { } } +#[cfg(feature = "serde")] +impl Serialize for IriRef { + fn serialize(&self, serializer: S) -> Result { + self.iri.serialize(serializer) + } +} + +#[cfg(feature = "serde")] +impl<'de, T: Deref + Deserialize<'de>> Deserialize<'de> for IriRef { + fn deserialize>(deserializer: D) -> Result { + use serde::de::Error; + + Self::parse(T::deserialize(deserializer)?).map_err(D::Error::custom) + } +} + /// A [RFC 3987](https://www.ietf.org/rfc/rfc3987.html) IRI. /// /// Instances of this type are guaranteed to be absolute, @@ -861,6 +879,23 @@ impl> TryFrom> for Iri { } } +#[cfg(feature = "serde")] +impl Serialize for Iri { + fn serialize(&self, serializer: S) -> Result { + self.0.serialize(serializer) + } +} + +#[cfg(feature = "serde")] +impl<'de, T: Deref + Deserialize<'de>> Deserialize<'de> for Iri { + fn deserialize>(deserializer: D) -> Result { + use serde::de::Error; + IriRef::deserialize(deserializer)? + .try_into() + .map_err(D::Error::custom) + } +} + /// An error raised during [`Iri`](struct.Iri.html) validation. #[derive(Debug)] pub struct IriParseError { diff --git a/tests/lib.rs b/tests/lib.rs index 948c865..301b7ac 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -1,5 +1,7 @@ #![allow(clippy::eq_op)] use oxiri::{Iri, IriRef}; +#[cfg(feature = "serde")] +use serde_test::{assert_de_tokens, assert_de_tokens_error, assert_tokens, Token}; use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; @@ -556,3 +558,49 @@ fn test_str() { let iri = Iri::parse("http://example.com").unwrap(); assert!(iri.starts_with("http://")); } + +#[cfg(feature = "serde")] +#[test] +fn test_iriref_serde_impl() { + assert_tokens( + &IriRef::parse("//example.com").unwrap(), + &[Token::BorrowedStr("//example.com")], + ); + assert_tokens( + &IriRef::parse("//example.com".to_string()).unwrap(), + &[Token::String("//example.com")], + ); + assert_de_tokens( + &IriRef::parse("//example.com".to_string()).unwrap(), + &[Token::BorrowedStr("//example.com")], + ); + assert_de_tokens_error::>( + &[Token::String(":")], + "No scheme found in an absolute IRI", + ); +} + +#[cfg(feature = "serde")] +#[test] +fn test_iri_serde_impl() { + assert_tokens( + &Iri::parse("http://example.com").unwrap(), + &[Token::BorrowedStr("http://example.com")], + ); + assert_tokens( + &Iri::parse("http://example.com".to_string()).unwrap(), + &[Token::String("http://example.com")], + ); + assert_de_tokens( + &Iri::parse("http://example.com".to_string()).unwrap(), + &[Token::BorrowedStr("http://example.com")], + ); + assert_de_tokens_error::>( + &[Token::String(":")], + "No scheme found in an absolute IRI", + ); + assert_de_tokens_error::>( + &[Token::String("//example.com")], + "No scheme found in an absolute IRI", + ); +}