From 22fb8b23f718e454a63c9fc17137f7f5c0188611 Mon Sep 17 00:00:00 2001 From: Daniel Tashjian Date: Fri, 8 Oct 2021 19:45:52 -0400 Subject: [PATCH] Added support for serde. --- .editorconfig | 11 ++++- rust-lib/Cargo.lock | 6 +++ rust-lib/Cargo.toml | 3 ++ rust-lib/src/email_address.rs | 63 +++++++++++++++++++++++++++ rust-lib/tests/email_address_tests.rs | 37 ++++++++++++++++ 5 files changed, 118 insertions(+), 2 deletions(-) diff --git a/.editorconfig b/.editorconfig index c3d036c..9f1107e 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,9 +1,16 @@ root = true -[*] +[*.{js,json,ts}] indent_style = space indent_size = 2 charset = utf-8 trim_trailing_whitespace = false insert_final_newline = false -quote_type = single \ No newline at end of file +quote_type = single + +[*.rs] +indent_style = space +indent_size = 4 +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true \ No newline at end of file diff --git a/rust-lib/Cargo.lock b/rust-lib/Cargo.lock index 9aec458..980cfdb 100644 --- a/rust-lib/Cargo.lock +++ b/rust-lib/Cargo.lock @@ -238,9 +238,12 @@ version = "1.0.3" dependencies = [ "console_error_panic_hook", "criterion", + "email-address-parser", "pest", "pest_derive", "quick-xml", + "serde", + "serde_json", "wasm-bindgen", "wasm-bindgen-test", ] @@ -565,6 +568,9 @@ name = "serde" version = "1.0.115" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e54c9a88f2da7238af84b5101443f0c0d0a3bbdc455e34a5c9497b1903ed55d5" +dependencies = [ + "serde_derive", +] [[package]] name = "serde_cbor" diff --git a/rust-lib/Cargo.toml b/rust-lib/Cargo.toml index c3bd073..bb69ec9 100644 --- a/rust-lib/Cargo.toml +++ b/rust-lib/Cargo.toml @@ -22,13 +22,16 @@ pest = "^2.1.3" pest_derive = "^2.1.0" wasm-bindgen = "^0.2.67" console_error_panic_hook = "^0.1.6" +serde = { version = "1.0.115", features = ["derive"], optional = true } [build-dependencies] quick-xml = "^0.18.1" [dev-dependencies] +email-address-parser = { path = ".", features = ["serde"] } wasm-bindgen-test = "^0.3.17" criterion = "^0.3.3" +serde_json = "1.0.57" [package.metadata.wasm-pack.profile.release] wasm-opt = ["-Oz", "--enable-mutable-globals"] diff --git a/rust-lib/src/email_address.rs b/rust-lib/src/email_address.rs index 3d60287..01ee689 100644 --- a/rust-lib/src/email_address.rs +++ b/rust-lib/src/email_address.rs @@ -6,6 +6,9 @@ use std::fmt; use std::hash::Hash; use wasm_bindgen::prelude::*; +#[cfg(feature = "serde")] +use serde::Serialize; + /// Options for parsing. /// /// The is only one available option so far `is_lax` which can be set to @@ -51,6 +54,7 @@ struct RFC5322; /// ``` #[wasm_bindgen] #[derive(Clone, Debug, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize))] pub struct EmailAddress { local_part: String, domain: String, @@ -317,6 +321,65 @@ impl fmt::Display for EmailAddress { } } +#[cfg(feature = "serde")] +impl<'de> serde::Deserialize<'de> for EmailAddress { + fn deserialize>(deserializer: D) -> Result { + deserializer.deserialize_any(EmailAddressVisitor) + } +} + +#[cfg(feature = "serde")] +struct EmailAddressVisitor; + +#[cfg(feature = "serde")] +impl<'de> serde::de::Visitor<'de> for EmailAddressVisitor { + type Value = EmailAddress; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + write!(formatter, "a local_part and domain representing an email address") + } + + fn visit_map>(self, mut map: A) -> Result { + use serde::de::Unexpected; + + let entry_1 = map.next_entry::()?; + let entry_2 = map.next_entry::()?; + let entry_3 = map.next_entry::()?; + + if entry_3.is_some() { + return Err(serde::de::Error::invalid_length(2, &self)); + } + + fn get<'a>( + key: &str, + a: &'a Option<(String, String)>, + b: &'a Option<(String, String)>, + ) -> Result<&'a str, ()> { + if let Some(a_value) = a { + if let Some(b_value) = b { + if a_value.0 == key { + return Ok(&a_value.1); + } else if b_value.0 == key { + return Ok(&b_value.1); + } + } + } + + Err(()) + } + + let local_part = get("local_part", &entry_1, &entry_2) + .map_err(|_| serde::de::Error::invalid_length(2, &self))?; + + let domain = get("domain", &entry_1, &entry_2) + .map_err(|_| serde::de::Error::invalid_length(2, &self))?; + + EmailAddress::new(local_part, domain, None).map_err(|error| { + serde::de::Error::invalid_value(Unexpected::Other(&error), &self) + }) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/rust-lib/tests/email_address_tests.rs b/rust-lib/tests/email_address_tests.rs index 4c5292a..46a3ad2 100644 --- a/rust-lib/tests/email_address_tests.rs +++ b/rust-lib/tests/email_address_tests.rs @@ -1,3 +1,5 @@ +use serde_json::{Error, json}; + use email_address_parser::EmailAddress; #[test] @@ -16,3 +18,38 @@ fn test_clone() { assert_eq!("foo", actual.get_local_part()); assert_eq!("bar.com", actual.get_domain()); } + +#[test] +fn test_serialize() { + let actual = serde_json::to_value(EmailAddress::new("foo", "bar.baz", None).unwrap()).unwrap(); + + assert_eq!(json!({"local_part":"foo","domain":"bar.baz"}), actual); +} + +#[test] +fn test_deserialize_valid() { + let actual: EmailAddress = serde_json::from_value(json!({"local_part":"foo","domain":"bar.baz"})).unwrap(); + + assert_eq!(EmailAddress::new("foo", "bar.baz", None).unwrap(), actual); +} + +#[test] +fn test_deserialize_invalid() { + let error_1: Result = serde_json::from_value(json!({"local_part":"foo","domain":""})); + let error_2: Result = serde_json::from_value(json!({"local_part":"","domain":"bar.baz"})); + let error_3: Result = serde_json::from_value(json!({"localpart":"foo","domain":"bar.baz"})); + let error_4: Result = serde_json::from_value(json!({"local_part":"foo","domaine":"bar.baz"})); + let error_5: Result = serde_json::from_value(json!({"local_part":"foo","domain":"bar.baz","ques":"que.se"})); + let error_6: Result = serde_json::from_value(json!({"local_part":"foo"})); + let error_7: Result = serde_json::from_value(json!({"domain":"bar.baz"})); + let error_8: Result = serde_json::from_value(json!({})); + + assert!(error_1.is_err()); + assert!(error_2.is_err()); + assert!(error_3.is_err()); + assert!(error_4.is_err()); + assert!(error_5.is_err()); + assert!(error_6.is_err()); + assert!(error_7.is_err()); + assert!(error_8.is_err()); +}