From 7ea839f897908fe051fe1309d67d78ec153ee5f4 Mon Sep 17 00:00:00 2001 From: Peter Pentchev Date: Sun, 22 May 2022 11:36:06 +0300 Subject: [PATCH 01/11] Handle regex groups that were not even captured. Closes: #1 --- src/de.rs | 9 ++++----- src/lib.rs | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/src/de.rs b/src/de.rs index f5a1e17..fe8c71c 100644 --- a/src/de.rs +++ b/src/de.rs @@ -32,10 +32,9 @@ impl<'de, 'a> serde::Deserializer<'de> for &'a mut Deserializer<'de> { let caps = self.regex.captures(&self.input).ok_or_else(Error::NoMatch)?; let items = self.regex.capture_names().filter_map(|n| { - n.map(|name| { - let value = &caps[name]; - (name.to_owned(), Value { name: name.to_owned(), value: value.to_owned() }) - }) + n.and_then(|name| caps.name(name).map(|value| { + (name.to_owned(), Value { name: name.to_owned(), value: value.as_str().to_owned() }) + })) }); let ms = MapDeserializer::new(items); @@ -160,4 +159,4 @@ impl<'de> serde::Deserializer<'de> for Value { unit seq bytes byte_buf map unit_struct tuple_struct tuple ignored_any struct } -} \ No newline at end of file +} diff --git a/src/lib.rs b/src/lib.rs index 7a51762..d099bd6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -273,6 +273,24 @@ mod test { assert_eq!(output, Test3 { foo: None, bar: None }); } + #[test] + fn test_option_present() { + let regex = r"^(?P\d*)(?:,(?P-?\d*))?$"; + let input = "1,-2"; + let output: Test3 = from_str(input, regex).unwrap(); + + assert_eq!(output, Test3 { foo: Some(1), bar: Some(-2) }); + } + + #[test] + fn test_option_missing() { + let regex = r"^(?P\d*)(?:,(?P-?\d*))?$"; + let input = "1"; + let output: Test3 = from_str(input, regex).unwrap(); + + assert_eq!(output, Test3 { foo: Some(1), bar: None }); + } + #[test] fn test_bool() { #[derive(Deserialize)] From eaf0ea70281eac4c75743819294c2ee4114d6c82 Mon Sep 17 00:00:00 2001 From: Peter Pentchev Date: Sun, 22 May 2022 12:04:57 +0300 Subject: [PATCH 02/11] Reformat the source code using `cargo fmt`. --- src/de.rs | 122 +++++++++++++++++++++++++++++++++++++++------------ src/error.rs | 9 +++- src/lib.rs | 85 +++++++++++++++++++++++++---------- 3 files changed, 162 insertions(+), 54 deletions(-) diff --git a/src/de.rs b/src/de.rs index fe8c71c..b815693 100644 --- a/src/de.rs +++ b/src/de.rs @@ -1,7 +1,7 @@ use std::str::FromStr; -use serde::de::{Visitor, IntoDeserializer}; use serde::de::value::MapDeserializer; +use serde::de::{IntoDeserializer, Visitor}; use regex::Regex; @@ -14,27 +14,41 @@ pub(crate) struct Deserializer<'de> { impl<'de> Deserializer<'de> { pub fn new(input: &'de str, regex: Regex) -> Deserializer { - Deserializer { - input, - regex, - } + Deserializer { input, regex } } } impl<'de, 'a> serde::Deserializer<'de> for &'a mut Deserializer<'de> { type Error = Error; - fn deserialize_any(self, visitor: V) -> Result where V: Visitor<'de> { + fn deserialize_any(self, visitor: V) -> Result + where + V: Visitor<'de>, + { self.deserialize_map(visitor) } - fn deserialize_map(self, visitor: V) -> Result where V: Visitor<'de> { - let caps = self.regex.captures(&self.input).ok_or_else(Error::NoMatch)?; + fn deserialize_map(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + let caps = self + .regex + .captures(&self.input) + .ok_or_else(Error::NoMatch)?; let items = self.regex.capture_names().filter_map(|n| { - n.and_then(|name| caps.name(name).map(|value| { - (name.to_owned(), Value { name: name.to_owned(), value: value.as_str().to_owned() }) - })) + n.and_then(|name| { + caps.name(name).map(|value| { + ( + name.to_owned(), + Value { + name: name.to_owned(), + value: value.as_str().to_owned(), + }, + ) + }) + }) }); let ms = MapDeserializer::new(items); @@ -53,14 +67,16 @@ impl<'de, 'a> serde::Deserializer<'de> for &'a mut Deserializer<'de> { } } - struct Value { name: String, value: String, } impl Value { - fn parse(&self) -> Result where T: FromStr { + fn parse(&self) -> Result + where + T: FromStr, + { self.value.parse().map_err(|_| self.get_parse_error()) } @@ -83,11 +99,17 @@ impl<'de> IntoDeserializer<'de, Error> for Value { impl<'de> serde::Deserializer<'de> for Value { type Error = Error; - fn deserialize_any(self, visitor: V) -> Result where V: Visitor<'de> { + fn deserialize_any(self, visitor: V) -> Result + where + V: Visitor<'de>, + { self.value.into_deserializer().deserialize_any(visitor) } - fn deserialize_bool(self, visitor: V) -> Result where V: Visitor<'de> { + fn deserialize_bool(self, visitor: V) -> Result + where + V: Visitor<'de>, + { if self.value.eq_ignore_ascii_case("true") { visitor.visit_bool(true) } else if self.value.eq_ignore_ascii_case("false") { @@ -97,47 +119,80 @@ impl<'de> serde::Deserializer<'de> for Value { } } - fn deserialize_i8(self, visitor: V) -> Result where V: Visitor<'de> { + fn deserialize_i8(self, visitor: V) -> Result + where + V: Visitor<'de>, + { visitor.visit_i8(self.parse()?) } - fn deserialize_i16(self, visitor: V) -> Result where V: Visitor<'de> { + fn deserialize_i16(self, visitor: V) -> Result + where + V: Visitor<'de>, + { visitor.visit_i16(self.parse()?) } - fn deserialize_i32(self, visitor: V) -> Result where V: Visitor<'de> { + fn deserialize_i32(self, visitor: V) -> Result + where + V: Visitor<'de>, + { visitor.visit_i32(self.parse()?) } - fn deserialize_i64(self, visitor: V) -> Result where V: Visitor<'de> { + fn deserialize_i64(self, visitor: V) -> Result + where + V: Visitor<'de>, + { visitor.visit_i64(self.parse()?) } - fn deserialize_u8(self, visitor: V) -> Result where V: Visitor<'de> { + fn deserialize_u8(self, visitor: V) -> Result + where + V: Visitor<'de>, + { visitor.visit_u8(self.parse()?) } - fn deserialize_u16(self, visitor: V) -> Result where V: Visitor<'de> { + fn deserialize_u16(self, visitor: V) -> Result + where + V: Visitor<'de>, + { visitor.visit_u16(self.parse()?) } - fn deserialize_u32(self, visitor: V) -> Result where V: Visitor<'de> { + fn deserialize_u32(self, visitor: V) -> Result + where + V: Visitor<'de>, + { visitor.visit_u32(self.parse()?) } - fn deserialize_u64(self, visitor: V) -> Result where V: Visitor<'de> { + fn deserialize_u64(self, visitor: V) -> Result + where + V: Visitor<'de>, + { visitor.visit_u64(self.parse()?) } - fn deserialize_f32(self, visitor: V) -> Result where V: Visitor<'de> { + fn deserialize_f32(self, visitor: V) -> Result + where + V: Visitor<'de>, + { visitor.visit_f32(self.parse()?) } - fn deserialize_f64(self, visitor: V) -> Result where V: Visitor<'de> { + fn deserialize_f64(self, visitor: V) -> Result + where + V: Visitor<'de>, + { visitor.visit_f64(self.parse()?) } - fn deserialize_option(self, visitor: V) -> Result where V: Visitor<'de> { + fn deserialize_option(self, visitor: V) -> Result + where + V: Visitor<'de>, + { if self.value.is_empty() { visitor.visit_none() } else { @@ -145,11 +200,22 @@ impl<'de> serde::Deserializer<'de> for Value { } } - fn deserialize_newtype_struct(self, _: &'static str, visitor: V) -> Result where V: Visitor<'de> { + fn deserialize_newtype_struct(self, _: &'static str, visitor: V) -> Result + where + V: Visitor<'de>, + { visitor.visit_newtype_struct(self) } - fn deserialize_enum(self, _name: &'static str, _variants: &'static [&'static str], visitor: V) -> Result where V: Visitor<'de> { + fn deserialize_enum( + self, + _name: &'static str, + _variants: &'static [&'static str], + visitor: V, + ) -> Result + where + V: Visitor<'de>, + { visitor.visit_enum(self.value.into_deserializer()) } diff --git a/src/error.rs b/src/error.rs index b44f44d..c8036bf 100644 --- a/src/error.rs +++ b/src/error.rs @@ -23,7 +23,10 @@ pub enum Error { } impl serde::de::Error for Error { - fn custom(msg: T) -> Self where T: Display { + fn custom(msg: T) -> Self + where + T: Display, + { Self::Custom(msg.to_string()) } } @@ -36,7 +39,9 @@ impl Display for Error { match self { BadRegex(err) => err.fmt(f), NoMatch() => write!(f, "String doesn't match pattern"), - BadValue { name, value } => write!(f, "Unable to convert value for group {}: {}", name, value), + BadValue { name, value } => { + write!(f, "Unable to convert value for group {}: {}", name, value) + } Custom(err) => write!(f, "{}", err), } } diff --git a/src/lib.rs b/src/lib.rs index d099bd6..edb4fe2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -89,13 +89,13 @@ If your regular expression looks like a behemoth no mere mortal will ever unders * */ -mod error; mod de; +mod error; pub use error::Error; -use serde::Deserialize; use regex::Regex; +use serde::Deserialize; /// Deserialize an input string into a struct. /// @@ -120,7 +120,10 @@ use regex::Regex; /// # Ok(()) /// # } /// ``` -pub fn from_str<'a, T>(input: &'a str, regex: &str) -> std::result::Result where T: Deserialize<'a> { +pub fn from_str<'a, T>(input: &'a str, regex: &str) -> std::result::Result +where + T: Deserialize<'a>, +{ let regex = Regex::new(®ex).map_err(Error::BadRegex)?; from_str_regex(input, regex) } @@ -149,15 +152,18 @@ pub fn from_str<'a, T>(input: &'a str, regex: &str) -> std::result::Result(input: &'a str, regex: Regex) -> std::result::Result where T: Deserialize<'a> { +pub fn from_str_regex<'a, T>(input: &'a str, regex: Regex) -> std::result::Result +where + T: Deserialize<'a>, +{ let mut deserializer = de::Deserializer::new(input, regex); T::deserialize(&mut deserializer) } #[cfg(test)] mod test { - use super::*; use super::error::Result; + use super::*; #[derive(Deserialize, PartialEq, Debug)] struct Test { @@ -233,20 +239,23 @@ mod test { let input = "true,1,2,3,4,-1,-2,-3,-4,1.0,-1.0,foobar"; let output: Test2 = from_str(input, TEST2_PATTERN).unwrap(); - assert_eq!(output, Test2 { - f_bool: true, - f_u8: 1, - f_u16: 2, - f_u32: 3, - f_u64: 4, - f_i8: -1, - f_i16: -2, - f_i32: -3, - f_i64: -4, - f_f32: 1.0, - f_f64: -1.0, - f_str: "foobar".to_owned(), - }); + assert_eq!( + output, + Test2 { + f_bool: true, + f_u8: 1, + f_u16: 2, + f_u32: 3, + f_u64: 4, + f_i8: -1, + f_i16: -2, + f_i32: -3, + f_i64: -4, + f_f32: 1.0, + f_f64: -1.0, + f_str: "foobar".to_owned(), + } + ); } #[derive(Deserialize, PartialEq, Debug)] @@ -261,7 +270,13 @@ mod test { let input = "1,-2"; let output: Test3 = from_str(input, regex).unwrap(); - assert_eq!(output, Test3 { foo: Some(1), bar: Some(-2) }); + assert_eq!( + output, + Test3 { + foo: Some(1), + bar: Some(-2) + } + ); } #[test] @@ -270,7 +285,13 @@ mod test { let input = ","; let output: Test3 = from_str(input, regex).unwrap(); - assert_eq!(output, Test3 { foo: None, bar: None }); + assert_eq!( + output, + Test3 { + foo: None, + bar: None + } + ); } #[test] @@ -279,7 +300,13 @@ mod test { let input = "1,-2"; let output: Test3 = from_str(input, regex).unwrap(); - assert_eq!(output, Test3 { foo: Some(1), bar: Some(-2) }); + assert_eq!( + output, + Test3 { + foo: Some(1), + bar: Some(-2) + } + ); } #[test] @@ -288,7 +315,13 @@ mod test { let input = "1"; let output: Test3 = from_str(input, regex).unwrap(); - assert_eq!(output, Test3 { foo: Some(1), bar: None }); + assert_eq!( + output, + Test3 { + foo: Some(1), + bar: None + } + ); } #[test] @@ -365,7 +398,11 @@ mod test { let input = "aaa1,-2"; let output: Result = from_str(input, regex); - assert!(matches!(output, Err(Error::BadValue{..})), "Expected Error::BadValue got {:?}", output); + assert!( + matches!(output, Err(Error::BadValue { .. })), + "Expected Error::BadValue got {:?}", + output + ); } #[test] From 643a78cc98e6e88f5f5e667487a1aed8b2d60481 Mon Sep 17 00:00:00 2001 From: Peter Pentchev Date: Sun, 22 May 2022 12:06:49 +0300 Subject: [PATCH 03/11] Fix clippy::needless_borrow. --- src/de.rs | 5 +---- src/lib.rs | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/de.rs b/src/de.rs index b815693..ee6d28b 100644 --- a/src/de.rs +++ b/src/de.rs @@ -32,10 +32,7 @@ impl<'de, 'a> serde::Deserializer<'de> for &'a mut Deserializer<'de> { where V: Visitor<'de>, { - let caps = self - .regex - .captures(&self.input) - .ok_or_else(Error::NoMatch)?; + let caps = self.regex.captures(self.input).ok_or_else(Error::NoMatch)?; let items = self.regex.capture_names().filter_map(|n| { n.and_then(|name| { diff --git a/src/lib.rs b/src/lib.rs index edb4fe2..92c5a54 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -124,7 +124,7 @@ pub fn from_str<'a, T>(input: &'a str, regex: &str) -> std::result::Result, { - let regex = Regex::new(®ex).map_err(Error::BadRegex)?; + let regex = Regex::new(regex).map_err(Error::BadRegex)?; from_str_regex(input, regex) } From d70aee6c6301e09e04ec1ef2a894fd68b0a9bf3f Mon Sep 17 00:00:00 2001 From: Peter Pentchev Date: Sun, 22 May 2022 12:12:34 +0300 Subject: [PATCH 04/11] Fix clippy::pattern_type_mismatch. --- src/error.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/error.rs b/src/error.rs index c8036bf..9db5690 100644 --- a/src/error.rs +++ b/src/error.rs @@ -36,13 +36,16 @@ impl std::error::Error for Error {} impl Display for Error { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { use Error::*; - match self { - BadRegex(err) => err.fmt(f), + match *self { + BadRegex(ref err) => err.fmt(f), NoMatch() => write!(f, "String doesn't match pattern"), - BadValue { name, value } => { + BadValue { + ref name, + ref value, + } => { write!(f, "Unable to convert value for group {}: {}", name, value) } - Custom(err) => write!(f, "{}", err), + Custom(ref err) => write!(f, "{}", err), } } } From 066c93f25c03a91aac9d50ad26040dc6a5ea6b24 Mon Sep 17 00:00:00 2001 From: Peter Pentchev Date: Sun, 22 May 2022 12:16:19 +0300 Subject: [PATCH 05/11] Fix clippy::single_char_lifetime_names. --- src/de.rs | 2 +- src/lib.rs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/de.rs b/src/de.rs index ee6d28b..04e9dc1 100644 --- a/src/de.rs +++ b/src/de.rs @@ -18,7 +18,7 @@ impl<'de> Deserializer<'de> { } } -impl<'de, 'a> serde::Deserializer<'de> for &'a mut Deserializer<'de> { +impl<'de> serde::Deserializer<'de> for &'_ mut Deserializer<'de> { type Error = Error; fn deserialize_any(self, visitor: V) -> Result diff --git a/src/lib.rs b/src/lib.rs index 92c5a54..391a955 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -120,9 +120,9 @@ use serde::Deserialize; /// # Ok(()) /// # } /// ``` -pub fn from_str<'a, T>(input: &'a str, regex: &str) -> std::result::Result +pub fn from_str<'input, T>(input: &'input str, regex: &str) -> std::result::Result where - T: Deserialize<'a>, + T: Deserialize<'input>, { let regex = Regex::new(regex).map_err(Error::BadRegex)?; from_str_regex(input, regex) @@ -152,9 +152,9 @@ where /// # Ok(()) /// # } /// ``` -pub fn from_str_regex<'a, T>(input: &'a str, regex: Regex) -> std::result::Result +pub fn from_str_regex<'input, T>(input: &'input str, regex: Regex) -> std::result::Result where - T: Deserialize<'a>, + T: Deserialize<'input>, { let mut deserializer = de::Deserializer::new(input, regex); T::deserialize(&mut deserializer) From a546c2535a17cd250d370a3d4bcf5ae0125972b7 Mon Sep 17 00:00:00 2001 From: Peter Pentchev Date: Sun, 22 May 2022 12:17:25 +0300 Subject: [PATCH 06/11] Fix clippy::shadow_reuse. --- src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 391a955..1d8792e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -124,8 +124,8 @@ pub fn from_str<'input, T>(input: &'input str, regex: &str) -> std::result::Resu where T: Deserialize<'input>, { - let regex = Regex::new(regex).map_err(Error::BadRegex)?; - from_str_regex(input, regex) + let rex = Regex::new(regex).map_err(Error::BadRegex)?; + from_str_regex(input, rex) } /// Deserialize an input string into a struct. From 84812d8eb9fad78f2261d4e59d1d2a8da540a135 Mon Sep 17 00:00:00 2001 From: Peter Pentchev Date: Sun, 22 May 2022 12:18:50 +0300 Subject: [PATCH 07/11] Mark the Error enum as non-exhaustive. --- src/error.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/error.rs b/src/error.rs index 9db5690..9e1338a 100644 --- a/src/error.rs +++ b/src/error.rs @@ -2,6 +2,7 @@ use std::fmt::{Display, Formatter}; /// An error that occurred during deserialization. #[derive(Debug)] +#[non_exhaustive] pub enum Error { /// An error occurred while parsing the regular expression BadRegex(regex::Error), From 727ddb05e8ec316cae9523ceb48d620b7edd53a9 Mon Sep 17 00:00:00 2001 From: Peter Pentchev Date: Sun, 22 May 2022 12:22:35 +0300 Subject: [PATCH 08/11] Explicitly allow clippy::map_err_ignore as needed. --- src/de.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/de.rs b/src/de.rs index 04e9dc1..f6adf83 100644 --- a/src/de.rs +++ b/src/de.rs @@ -70,6 +70,7 @@ struct Value { } impl Value { + #[allow(clippy::map_err_ignore)] fn parse(&self) -> Result where T: FromStr, From 992896026c1cc7f8325ba50350301aefd6d34d03 Mon Sep 17 00:00:00 2001 From: Peter Pentchev Date: Sun, 22 May 2022 12:28:16 +0300 Subject: [PATCH 09/11] Fix clippy::redundant_pub_crate. --- src/de.rs | 2 +- src/error.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/de.rs b/src/de.rs index f6adf83..a3fdf2b 100644 --- a/src/de.rs +++ b/src/de.rs @@ -7,7 +7,7 @@ use regex::Regex; use crate::error::*; -pub(crate) struct Deserializer<'de> { +pub struct Deserializer<'de> { input: &'de str, regex: Regex, } diff --git a/src/error.rs b/src/error.rs index 9e1338a..c6f0c18 100644 --- a/src/error.rs +++ b/src/error.rs @@ -53,4 +53,4 @@ impl Display for Error { // Do not use this alias in public parts of the crate because // it would hide the direct link to the actual error type in rustdoc. -pub(crate) type Result = std::result::Result; +pub type Result = std::result::Result; From 6b4b6f1af9ded2e137cc6fce6b64414efb408803 Mon Sep 17 00:00:00 2001 From: Peter Pentchev Date: Sun, 22 May 2022 12:34:13 +0300 Subject: [PATCH 10/11] Mark the de::Deserializer::new() function as const. --- src/de.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/de.rs b/src/de.rs index a3fdf2b..04221e1 100644 --- a/src/de.rs +++ b/src/de.rs @@ -13,7 +13,7 @@ pub struct Deserializer<'de> { } impl<'de> Deserializer<'de> { - pub fn new(input: &'de str, regex: Regex) -> Deserializer { + pub const fn new(input: &'de str, regex: Regex) -> Deserializer { Deserializer { input, regex } } } From cf890d2762d7dcd494bee3b6c6c736145341f4b0 Mon Sep 17 00:00:00 2001 From: Peter Pentchev Date: Sun, 22 May 2022 12:38:39 +0300 Subject: [PATCH 11/11] Mark most of the exported functions as inline. --- src/de.rs | 1 + src/error.rs | 2 ++ src/lib.rs | 2 ++ 3 files changed, 5 insertions(+) diff --git a/src/de.rs b/src/de.rs index 04221e1..0bece7a 100644 --- a/src/de.rs +++ b/src/de.rs @@ -21,6 +21,7 @@ impl<'de> Deserializer<'de> { impl<'de> serde::Deserializer<'de> for &'_ mut Deserializer<'de> { type Error = Error; + #[inline] fn deserialize_any(self, visitor: V) -> Result where V: Visitor<'de>, diff --git a/src/error.rs b/src/error.rs index c6f0c18..81011a1 100644 --- a/src/error.rs +++ b/src/error.rs @@ -24,6 +24,7 @@ pub enum Error { } impl serde::de::Error for Error { + #[inline] fn custom(msg: T) -> Self where T: Display, @@ -35,6 +36,7 @@ impl serde::de::Error for Error { impl std::error::Error for Error {} impl Display for Error { + #[inline] fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { use Error::*; match *self { diff --git a/src/lib.rs b/src/lib.rs index 1d8792e..8e6f2ed 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -120,6 +120,7 @@ use serde::Deserialize; /// # Ok(()) /// # } /// ``` +#[inline] pub fn from_str<'input, T>(input: &'input str, regex: &str) -> std::result::Result where T: Deserialize<'input>, @@ -152,6 +153,7 @@ where /// # Ok(()) /// # } /// ``` +#[inline] pub fn from_str_regex<'input, T>(input: &'input str, regex: Regex) -> std::result::Result where T: Deserialize<'input>,