From b9d5aeec2df70315ce7161765388fca120b869ec Mon Sep 17 00:00:00 2001 From: Dmitriy Pleshevskiy Date: Tue, 26 Jul 2022 18:14:26 +0300 Subject: [PATCH] core: add custom traits --- README.md | 11 ++--- examples/calc.rs | 2 +- examples/dotenv.rs | 2 +- src/core.rs | 93 ++++++++++++++++++++++++++++++++++++------ src/error.rs | 29 +++++++++---- src/lib.rs | 40 +++++++++++++++++- src/low/trim.rs | 17 +++----- src/std/bool.rs | 17 ++++---- src/std/number.rs | 17 ++++---- src/structs/pair.rs | 54 ++++++++---------------- src/structs/sep_vec.rs | 25 ++++++------ src/structs/trio.rs | 55 ++++++------------------- 12 files changed, 212 insertions(+), 150 deletions(-) diff --git a/README.md b/README.md index 80d990d..59d08c2 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ use estring::{SepVec, EString}; type PlusVec = SepVec; type MulVec = SepVec; -fn main() -> Result<(), estring::ParseError> { +fn main() -> estring::Result<()> { let res = EString::from("10+5*2+3") .parse::>>()? .iter() @@ -33,14 +33,15 @@ fn main() -> Result<(), estring::ParseError> { } ``` -You can use custom types as annotations! Just implement `TryFrom`! +You can use custom types as annotations! Just implement +`estring::ParseFragment`! ## Installation **The MSRV is 1.59.0** -Add `estring = { version = "0.1", features = ["vec", "number"] }` as a -dependency in `Cargo.toml`. +Add `estring = { version = "0.1", features = ["structs"] }` as a dependency in +`Cargo.toml`. `Cargo.toml` example: @@ -52,7 +53,7 @@ edition = "2021" authors = ["Me "] [dependencies] -estring = { version = "0.1", features = ["vec", "number"] } +estring = { version = "0.1", features = ["structs"] } ``` ## License diff --git a/examples/calc.rs b/examples/calc.rs index 27ea9e4..2799242 100644 --- a/examples/calc.rs +++ b/examples/calc.rs @@ -3,7 +3,7 @@ use estring::{EString, SepVec}; type PlusVec = SepVec; type MulVec = SepVec; -fn main() -> Result<(), estring::ParseError> { +fn main() -> estring::Result<()> { let res = EString::from("10+5*2+3") .parse::>>()? .iter() diff --git a/examples/dotenv.rs b/examples/dotenv.rs index a13d441..dee7a0a 100644 --- a/examples/dotenv.rs +++ b/examples/dotenv.rs @@ -5,7 +5,7 @@ DATABASE_URL=postgres://user:password@localhost:5432/recipes APP_HOST=http://localhost:3000 "; -fn main() -> Result<(), estring::ParseError> { +fn main() -> estring::Result<()> { EString::from(DOTENV_CONTENT) .parse::>>()? .iter() diff --git a/src/core.rs b/src/core.rs index f4bc8f9..ef570ab 100644 --- a/src/core.rs +++ b/src/core.rs @@ -2,8 +2,73 @@ //! string types //! -use crate::ParseError; -use std::convert::Infallible; +// TODO: add more info and examples. +/// Format a value fragment into a ``EString``. +pub trait FormatFragment { + /// Format this type and returns ``EString``. + fn fmt_frag(&self) -> EString; +} + +/// Parse a value fragment from a ``EString``. +/// +/// ``ParseFragment``’s `parse_frag` method is often used imlicitly, through ``EString``’s parse. +/// See [parse](EString::parse)’s documentation for examples. +/// +/// # Examples +/// +/// Basic implementation of ``ParseFragment`` on an example ``Point``. +/// +/// ```rust +/// use estring::{EString, ParseFragment, Reason}; +/// +/// #[derive(Debug, PartialEq)] +/// struct Point { +/// x: i32, +/// y: i32, +/// } +/// +/// impl ParseFragment for Point { +/// fn parse_frag(es: EString) -> estring::Result { +/// let orig = es.clone(); +/// let (x, y) = es +/// .trim_matches(|p| p == '(' || p == ')') +/// .split_once(',') +/// .ok_or(estring::Error(orig, Reason::Split))?; +/// +/// let (x, y) = (EString::from(x), EString::from(y)); +/// let x = x.clone().parse::() +/// .map_err(|_| estring::Error(x, Reason::Parse))?; +/// let y = y.clone().parse::() +/// .map_err(|_| estring::Error(y, Reason::Parse))?; +/// +/// Ok(Point { x, y }) +/// } +/// } +/// +/// let fragment = EString::from("(1,2)"); +/// let res = Point::parse_frag(fragment).unwrap(); +/// assert_eq!(res, Point { x: 1, y: 2 }) +/// ``` +/// +pub trait ParseFragment: Sized { + /// Parses a ``EString`` fragment `es` to return a value of this type. + /// + /// # Errors + /// + /// If parsing is not succeeds, returns ``Error`` inside ``Err`` with original fragment `es` + /// and reason ``Reason``. + /// + /// # Examples + /// + /// ```rust + /// use estring::{EString, ParseFragment}; + /// + /// let fragment = EString::from("5"); + /// let res = i32::parse_frag(fragment).unwrap(); + /// assert_eq!(res, 5); + /// ``` + fn parse_frag(es: EString) -> crate::Result; +} /// Wrapper under String type. #[derive(Debug, Default, PartialEq, Eq, Clone)] @@ -17,9 +82,8 @@ impl EString { /// Will return `Err` if estring cannot parse inner fragment /// #[inline] - pub fn parse>(self) -> Result { - let orig = self.0.clone(); - >::try_from(self).map_err(|_| ParseError(orig)) + pub fn parse(self) -> crate::Result { + T::parse_frag(self) } } @@ -42,20 +106,23 @@ impl std::ops::Deref for EString { } } -impl TryFrom for String { - type Error = Infallible; - +impl ParseFragment for EString { #[inline] - fn try_from(s: EString) -> Result { + fn parse_frag(value: EString) -> crate::Result { + Ok(value) + } +} + +impl ParseFragment for String { + #[inline] + fn parse_frag(s: EString) -> crate::Result { Ok(s.0) } } -impl TryFrom for &'static str { - type Error = Infallible; - +impl ParseFragment for &'static str { #[inline] - fn try_from(s: EString) -> Result { + fn parse_frag(s: EString) -> crate::Result { Ok(Box::leak(s.0.into_boxed_str())) } } diff --git a/src/error.rs b/src/error.rs index a484b68..3d99596 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,16 +1,31 @@ -/// Failed to parse the specified string. -#[derive(Debug)] -pub struct ParseError(pub String); +use crate::core::EString; -impl std::fmt::Display for ParseError { +/// The error type for operations interacting with ``EString``’s fragments. +#[derive(Debug)] +pub struct Error(pub EString, pub Reason); + +/// The reason for the failure to parse. +#[derive(Debug, PartialEq, Eq)] +pub enum Reason { + /// Cannot split fragment + Split, + /// Cannot parse fragment + Parse, +} + +impl std::fmt::Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, r#"Failed to parse: "{}""#, self.0) + write!( + f, + r#"Failed to parse "{:?}" with reason {:?}"#, + self.0, self.1 + ) } } -impl std::error::Error for ParseError {} +impl std::error::Error for Error {} -impl std::ops::Deref for ParseError { +impl std::ops::Deref for Error { type Target = String; fn deref(&self) -> &Self::Target { diff --git a/src/lib.rs b/src/lib.rs index c056c99..73d8637 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,7 +15,7 @@ //! type PlusVec = SepVec; //! type MulVec = SepVec; //! -//! fn main() -> Result<(), estring::ParseError> { +//! fn main() -> estring::Result<()> { //! let res = EString::from("10+5*2+3") //! .parse::>>()? //! .iter() @@ -32,6 +32,43 @@ #![warn(missing_docs)] mod error; +pub use error::{Error, Reason}; +/// The type returned by parser methods. +/// +/// # Examples +/// +/// ```rust +/// use estring::{EString, ParseFragment, Reason}; +/// +/// #[derive(Debug, PartialEq)] +/// struct Point { +/// x: i32, +/// y: i32, +/// } +/// +/// impl ParseFragment for Point { +/// fn parse_frag(es: EString) -> estring::Result { +/// let orig = es.clone(); +/// let (x, y) = es +/// .trim_matches(|p| p == '(' || p == ')') +/// .split_once(',') +/// .ok_or(estring::Error(orig, Reason::Split))?; +/// +/// let (x, y) = (EString::from(x), EString::from(y)); +/// let x = x.clone().parse::() +/// .map_err(|_| estring::Error(x, Reason::Parse))?; +/// let y = y.clone().parse::() +/// .map_err(|_| estring::Error(y, Reason::Parse))?; +/// +/// Ok(Point { x, y }) +/// } +/// } +/// +/// let fragment = EString::from("(1,2)"); +/// let res = Point::parse_frag(fragment).unwrap(); +/// assert_eq!(res, Point { x: 1, y: 2 }) +/// ``` +pub type Result = ::std::result::Result; pub mod core; pub mod std; @@ -51,4 +88,3 @@ pub mod structs; pub use structs::*; pub use crate::core::*; -pub use crate::error::ParseError; diff --git a/src/low/trim.rs b/src/low/trim.rs index f2b32e6..224826c 100644 --- a/src/low/trim.rs +++ b/src/low/trim.rs @@ -1,4 +1,4 @@ -use crate::core::EString; +use crate::{core::EString, ParseFragment}; /// Wrapper that allow to trim substring before continue /// @@ -9,7 +9,7 @@ use crate::core::EString; /// ```rust /// use estring::{EString, Trim}; /// -/// fn main() -> Result<(), estring::ParseError> { +/// fn main() -> estring::Result<()> { /// let res = EString::from(" 99 ").parse::>()?; /// assert_eq!(res, Trim(99)); /// Ok(()) @@ -33,16 +33,12 @@ impl std::fmt::Display for Trim { } } -impl TryFrom for Trim +impl ParseFragment for Trim where - T: TryFrom, + T: ParseFragment, { - type Error = (); - - fn try_from(value: EString) -> Result { - T::try_from(EString::from(value.trim())) - .map(Trim) - .map_err(|_| ()) + fn parse_frag(value: EString) -> crate::Result { + T::parse_frag(EString::from(value.trim())).map(Trim) } } @@ -60,7 +56,6 @@ mod tests { } } - #[cfg(feature = "number")] #[test] fn should_trim_and_convert_to_number() { let estr = EString::from(" 999 "); diff --git a/src/std/bool.rs b/src/std/bool.rs index c67be8e..0edf4ae 100644 --- a/src/std/bool.rs +++ b/src/std/bool.rs @@ -1,14 +1,13 @@ -use crate::core::EString; - -impl TryFrom for bool { - type Error = (); +use crate::core::{EString, ParseFragment}; +use crate::error::{Error, Reason}; +impl ParseFragment for bool { #[inline] - fn try_from(s: EString) -> Result { + fn parse_frag(s: EString) -> crate::Result { match s.to_lowercase().as_str() { "true" | "t" | "yes" | "y" | "on" | "1" => Ok(true), "false" | "f" | "no" | "n" | "off" | "0" | "" => Ok(false), - _ => Err(()), + _ => Err(Error(s, Reason::Parse)), } } } @@ -16,7 +15,6 @@ impl TryFrom for bool { #[cfg(test)] mod tests { use super::*; - use crate::ParseError; #[test] fn should_parse_bool_variable() { @@ -47,8 +45,9 @@ mod tests { fn should_throw_parse_error() { let estr = EString::from("something"); match estr.parse::() { - Err(ParseError(orig)) => { - assert_eq!(orig, String::from("something")); + Err(crate::Error(orig, reason)) => { + assert_eq!(orig, EString::from("something")); + assert_eq!(reason, crate::Reason::Parse); } _ => unreachable!(), }; diff --git a/src/std/number.rs b/src/std/number.rs index 0a3d034..555ebb8 100644 --- a/src/std/number.rs +++ b/src/std/number.rs @@ -1,15 +1,14 @@ -use crate::core::EString; +use crate::core::{EString, ParseFragment}; +use crate::error::{Error, Reason}; #[doc(hidden)] macro_rules! from_env_string_numbers_impl { ($($ty:ty),+$(,)?) => { $( - impl TryFrom for $ty { - type Error = <$ty as std::str::FromStr>::Err; - + impl ParseFragment for $ty { #[inline] - fn try_from(s: EString) -> Result { - s.0.parse::() + fn parse_frag(s: EString) -> crate::Result { + s.0.parse::().map_err(|_| Error(s, Reason::Parse)) } } )+ @@ -26,7 +25,6 @@ from_env_string_numbers_impl![ #[cfg(test)] mod tests { use super::*; - use crate::ParseError; #[test] fn should_parse_number() { @@ -51,8 +49,9 @@ mod tests { fn should_throw_parse_error() { let estr = EString::from("-10"); match estr.parse::() { - Err(ParseError(orig)) => { - assert_eq!(orig, String::from("-10")); + Err(Error(orig, reason)) => { + assert_eq!(orig, EString::from("-10")); + assert_eq!(reason, Reason::Parse); } _ => unreachable!(), }; diff --git a/src/structs/pair.rs b/src/structs/pair.rs index 5c70305..40911cc 100644 --- a/src/structs/pair.rs +++ b/src/structs/pair.rs @@ -1,34 +1,13 @@ //! Contains the implementations to pair tuple type //! -use crate::core::EString; +use crate::core::{EString, ParseFragment}; +use crate::{Error, Reason}; use std::fmt::Write; -/// The error type for operations interacting with parsing tuples. Possibly returned from -/// ``EString::parse`` -#[derive(Debug)] -pub enum Error { - /// The specified input string is not split. - Split, - - /// The specified substring of the split input string is not parsed - Parse(u8), -} - -impl std::fmt::Display for Error { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Error::Split => f.write_str("Cannot split input string"), - Error::Parse(n) => write!(f, "Cannot parse {} substring", n), - } - } -} - -impl std::error::Error for Error {} - /// Wrapper for pair (A, B) tuple to split string by a separator (`S1`). /// -/// **NOTE**: Required the enabling of the `tuple` feature. +/// **NOTE**: Required the enabling of the `structs` feature. /// /// # Examples /// @@ -37,7 +16,7 @@ impl std::error::Error for Error {} /// /// type EqPair = Pair; /// -/// fn main() -> Result<(), estring::ParseError> { +/// fn main() -> estring::Result<()> { /// let res = EString::from("1,2,3").parse::>()?; /// assert_eq!(*res, vec![1, 2, 3]); /// Ok(()) @@ -56,19 +56,17 @@ where } } -impl TryFrom for SepVec +impl ParseFragment for SepVec where - T: TryFrom + std::fmt::Display, + T: ParseFragment, { - type Error = T::Error; - - fn try_from(value: EString) -> Result { + fn parse_frag(value: EString) -> crate::Result { let inner = value .split(SEP) .map(str::trim) .map(EString::from) - .map(T::try_from) - .collect::, _>>()?; + .map(T::parse_frag) + .collect::>>()?; Ok(Self(inner)) } } @@ -76,7 +74,7 @@ where #[cfg(test)] mod tests { use super::*; - use crate::ParseError; + use crate::{Error, Reason}; const COMMA: char = ','; const SEMI: char = ';'; @@ -134,8 +132,9 @@ d,e"; fn should_throw_parse_vec_error() { let estr = EString::from("1,2,3,4,5"); match estr.parse::>() { - Err(ParseError(orig)) => { - assert_eq!(orig, String::from("1,2,3,4,5")); + Err(Error(orig, reason)) => { + assert_eq!(orig, EString::from("1,2,3,4,5")); + assert_eq!(reason, Reason::Parse); } _ => unreachable!(), }; diff --git a/src/structs/trio.rs b/src/structs/trio.rs index 65aa650..5a8681a 100644 --- a/src/structs/trio.rs +++ b/src/structs/trio.rs @@ -1,44 +1,21 @@ //! Contains the implementations to parse triple-tuple type //! -use crate::core::EString; +use super::Pair; +use crate::core::{EString, ParseFragment}; use std::fmt::Write; -/// The error type for operations interacting with parsing tuples. Possibly returned from -/// ``EString::parse`` -#[derive(Debug)] -pub enum Error { - /// The specified input string is not split. - Split, - - /// The specified substring of the split input string is not parsed - Parse(u8), -} - -impl std::fmt::Display for Error { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Error::Split => f.write_str("Cannot split input string"), - Error::Parse(n) => write!(f, "Cannot parse {} substring", n), - } - } -} - -impl std::error::Error for Error {} - /// Wrapper for trio (A, B, C) tuple to split string by separators (`S1` and `S2`). /// -/// **NOTE**: Required the enabling of the `tuple` feature. +/// **NOTE**: Required the enabling of the `structs` feature. /// /// # Examples /// /// ```rust /// use estring::{Trio, EString}; /// -/// type EqTrio = Trio; -/// -/// fn main() -> Result<(), estring::ParseError> { -/// let res = EString::from("one=two=free").parse::>()?; +/// fn main() -> estring::Result<()> { +/// let res = EString::from("one+two=free").parse::