diff --git a/benches/bench.rs b/benches/bench.rs index 3e924c1..9b6588d 100644 --- a/benches/bench.rs +++ b/benches/bench.rs @@ -3,7 +3,8 @@ extern crate criterion; use criterion::{BenchmarkId, Criterion}; use rust_decimal::prelude::FromPrimitive; -use sfv::{BareItem, Decimal, Parser, SerializeValue}; +use rust_decimal::Decimal; +use sfv::{Parser, SerializeValue}; use sfv::{RefBareItem, RefDictSerializer, RefItemSerializer, RefListSerializer}; criterion_main!(parsing, serializing, ref_serializing); diff --git a/src/bare_item.rs b/src/bare_item.rs new file mode 100644 index 0000000..90bf3b3 --- /dev/null +++ b/src/bare_item.rs @@ -0,0 +1,626 @@ +use crate::{utils, SFVResult}; +use rust_decimal::prelude::{FromPrimitive, ToPrimitive}; +use std::{ + convert::{TryFrom, TryInto}, + fmt, + ops::Deref, +}; + +/// `BareItem` type is used to construct `Items` or `Parameters` values. +#[derive(Debug, PartialEq, Clone)] +pub enum BareItem { + /// Decimal number + // sf-decimal = ["-"] 1*12DIGIT "." 1*3DIGIT + Decimal(BareItemDecimal), + /// Integer number + // sf-integer = ["-"] 1*15DIGIT + Integer(BareItemInteger), + // sf-string = DQUOTE *chr DQUOTE + // chr = unescaped / escaped + // unescaped = %x20-21 / %x23-5B / %x5D-7E + // escaped = "\" ( DQUOTE / "\" ) + String(BareItemString), + // ":" *(base64) ":" + // base64 = ALPHA / DIGIT / "+" / "/" / "=" + ByteSeq(BareItemByteSeq), + // sf-boolean = "?" boolean + // boolean = "0" / "1" + Boolean(BareItemBoolean), + // sf-token = ( ALPHA / "*" ) *( tchar / ":" / "/" ) + Token(BareItemToken), +} + +impl BareItem { + /// Creates a `BareItem::Decimal` from an `f64` input. + /// ``` + /// # use sfv::BareItem; + /// # fn main() -> Result<(), &'static str> { + /// let value = BareItem::new_decimal_from_f64(13.37)?; + /// # Ok(()) + /// # } + /// ``` + pub fn new_decimal_from_f64(value: f64) -> SFVResult { + let decimal = rust_decimal::Decimal::from_f64(value) + .ok_or("validate_decimal: value can not represent decimal")?; + + Self::new_decimal(decimal) + } + + /// Creates a `BareItem::Decimal` from a `rust_decimal::Decimal` input. + /// ``` + /// # use sfv::BareItem; + /// # use crate::sfv::FromPrimitive; + /// # fn main() -> Result<(), &'static str> { + /// let decimal = rust_decimal::Decimal::from_f64(13.37).unwrap(); + /// let value = BareItem::new_decimal(decimal); + /// # Ok(()) + /// # } + /// ``` + pub fn new_decimal(value: rust_decimal::Decimal) -> SFVResult { + let value: BareItemDecimal = value.try_into()?; + Ok(BareItem::Decimal(value)) + } + + /// Creates a `BareItem::Decimal` from a `rust_decimal::Decimal` input. + /// ``` + /// # use sfv::BareItem; + /// # fn main() -> Result<(), &'static str> { + /// let value = BareItem::new_integer(42)?; + /// # Ok(()) + /// # } + /// ``` + pub fn new_integer(value: i64) -> SFVResult { + let value: BareItemInteger = value.try_into()?; + Ok(BareItem::Integer(value)) + } + + /// Creates a `BareItem::String` from a `&str` input. + /// ``` + /// # use sfv::BareItem; + /// # fn main() -> Result<(), &'static str> { + /// let value = BareItem::new_string("foo")?; + /// # Ok(()) + /// # } + /// ``` + pub fn new_string(value: &str) -> SFVResult { + let value: BareItemString = value.try_into()?; + Ok(BareItem::String(value)) + } + + /// Creates a `BareItem::ByteSeq` from a byte slice input. + /// ``` + /// # use sfv::BareItem; + /// # fn main() -> Result<(), &'static str> { + /// let value = BareItem::new_byte_seq("hello".as_bytes())?; + /// # Ok(()) + /// # } + /// ``` + pub fn new_byte_seq(value: &[u8]) -> SFVResult { + let value: BareItemByteSeq = value.into(); + Ok(BareItem::ByteSeq(value)) + } + + /// Creates a `BareItem::Boolean` from a `bool` input. + /// ``` + /// # use sfv::BareItem; + /// # fn main() -> Result<(), &'static str> { + /// let value = BareItem::new_boolean(true)?; + /// # Ok(()) + /// # } + /// ``` + pub fn new_boolean(value: bool) -> SFVResult { + let value: BareItemBoolean = value.into(); + Ok(BareItem::Boolean(value)) + } + + /// Creates a `BareItem::Token` from a `&str` input. + /// ``` + /// # use sfv::BareItem; + /// # fn main() -> Result<(), &'static str> { + /// let value = BareItem::new_boolean(true)?; + /// # Ok(()) + /// # } + /// ``` + pub fn new_token(value: &str) -> SFVResult { + let value: BareItemToken = value.try_into()?; + Ok(BareItem::Token(value)) + } +} + +impl BareItem { + /// If `BareItem` is a decimal, returns `Decimal`, otherwise returns `None`. + /// ``` + /// # use sfv::{BareItem, FromPrimitive}; + /// use rust_decimal::Decimal; + /// # use std::convert::TryInto; + /// # fn main() -> Result<(), &'static str> { + /// let decimal_number = Decimal::from_f64(415.566).unwrap(); + /// let bare_item: BareItem = decimal_number.try_into()?; + /// assert_eq!(bare_item.as_decimal().unwrap(), decimal_number); + /// # Ok(()) + /// # } + /// ``` + pub fn as_decimal(&self) -> Option { + match self { + BareItem::Decimal(val) => Some(val.0), + _ => None, + } + } + /// If `BareItem` is an integer, returns `i64`, otherwise returns `None`. + /// ``` + /// # use sfv::BareItem; + /// # use std::convert::TryInto; + /// # fn main() -> Result<(), &'static str> { + /// let bare_item: BareItem = 100_i64.try_into()?; + /// assert_eq!(bare_item.as_int().unwrap(), 100); + /// # Ok(()) + /// # } + /// ``` + pub fn as_int(&self) -> Option { + match &self { + BareItem::Integer(val) => Some(**val), + _ => None, + } + } + /// If `BareItem` is `String`, returns `&str`, otherwise returns `None`. + /// ``` + /// # use sfv::BareItem; + /// # use std::convert::TryInto; + /// # fn main() -> Result<(), &'static str> { + /// let bare_item = BareItem::String("foo".to_owned().try_into()?); + /// assert_eq!(bare_item.as_str().unwrap(), "foo"); + /// Ok(()) + /// # } + /// ``` + pub fn as_str(&self) -> Option<&str> { + match *self { + BareItem::String(ref val) => Some(val), + _ => None, + } + } + /// If `BareItem` is a `ByteSeq`, returns `&Vec`, otherwise returns `None`. + /// ``` + /// # use sfv::BareItem; + /// let bare_item = BareItem::ByteSeq("foo".to_owned().into_bytes().into()); + /// assert_eq!(bare_item.as_byte_seq().unwrap().as_slice(), "foo".as_bytes()); + /// ``` + pub fn as_byte_seq(&self) -> Option<&Vec> { + match *self { + BareItem::ByteSeq(ref val) => Some(&val.0), + _ => None, + } + } + /// If `BareItem` is a `Boolean`, returns `bool`, otherwise returns `None`. + /// ``` + /// # use sfv::{BareItem, FromPrimitive}; + /// let bare_item = BareItem::Boolean(true.into()); + /// assert_eq!(bare_item.as_bool().unwrap(), true); + /// ``` + pub fn as_bool(&self) -> Option { + match self { + BareItem::Boolean(val) => Some(val.0), + _ => None, + } + } + /// If `BareItem` is a `Token`, returns `&str`, otherwise returns `None`. + /// ``` + /// use sfv::BareItem; + /// # use std::convert::TryInto; + /// # fn main() -> Result<(), &'static str> { + /// + /// let bare_item = BareItem::Token("*bar".try_into()?); + /// assert_eq!(bare_item.as_token().unwrap(), "*bar"); + /// # Ok(()) + /// # } + /// ``` + pub fn as_token(&self) -> Option<&str> { + match *self { + BareItem::Token(ref val) => Some(val), + _ => None, + } + } +} + +impl TryFrom for BareItem { + type Error = &'static str; + /// Converts `i64` into `BareItem::Integer`. + /// ``` + /// # use sfv::BareItem; + /// # use std::convert::TryInto; + /// # fn main() -> Result<(), &'static str> { + /// let bare_item: BareItem = 456_i64.try_into()?; + /// assert_eq!(bare_item.as_int().unwrap(), 456); + /// # Ok(()) + /// # } + /// ``` + fn try_from(item: i64) -> Result { + Self::new_integer(item) + } +} + +impl TryFrom for BareItem { + type Error = &'static str; + /// Converts `rust_decimal::Decimal` into `BareItem::Decimal`. + /// ``` + /// # use sfv::{BareItem, FromPrimitive}; + /// # use std::convert::TryInto; + /// use rust_decimal::Decimal; + /// # fn main() -> Result<(), &'static str> { + /// let decimal_number = Decimal::from_f64(48.01).unwrap(); + /// let bare_item: BareItem = decimal_number.try_into()?; + /// assert_eq!(bare_item.as_decimal().unwrap(), decimal_number); + /// # Ok(()) + /// # } + /// ``` + fn try_from(item: rust_decimal::Decimal) -> Result { + Self::new_decimal(item) + } +} + +impl TryFrom for BareItem { + type Error = &'static str; + + /// Converts `f64` into `BareItem::Decimal`. + /// ``` + /// # use sfv::{BareItem, FromPrimitive}; + /// # use std::convert::TryInto; + /// # use rust_decimal::prelude::ToPrimitive; + /// # fn main() -> Result<(), &'static str> { + /// let decimal_number = 48.01; + /// let bare_item: BareItem = decimal_number.try_into()?; + /// assert_eq!(bare_item.as_decimal().unwrap().to_f64().unwrap(), decimal_number); + /// # Ok(()) + /// # } + /// ``` + fn try_from(value: f64) -> Result { + Self::new_decimal_from_f64(value) + } +} + +impl TryFrom<&[u8]> for BareItem { + type Error = &'static str; + + /// Converts a byte slice into `BareItem::ByteSeq`. + /// ``` + /// # use sfv::{BareItem, FromPrimitive}; + /// # use std::convert::TryInto; + /// # fn main() -> Result<(), &'static str> { + /// let byte_slice = "hello".as_bytes(); + /// let bare_item: BareItem = byte_slice.try_into()?; + /// assert_eq!(bare_item.as_byte_seq().unwrap(), byte_slice); + /// # Ok(()) + /// # } + /// ``` + fn try_from(value: &[u8]) -> Result { + Self::new_byte_seq(value) + } +} + +impl TryFrom for BareItem { + type Error = &'static str; + + /// Converts a `bool` into `BareItem::Boolean`. + /// ``` + /// # use sfv::{BareItem, FromPrimitive}; + /// # use std::convert::TryInto; + /// # fn main() -> Result<(), &'static str> { + /// let boolean = true; + /// let bare_item: BareItem = boolean.try_into()?; + /// assert_eq!(bare_item.as_bool().unwrap(), boolean); + /// # Ok(()) + /// # } + /// ``` + fn try_from(value: bool) -> Result { + Self::new_boolean(value) + } +} + +/// Decimals are numbers with an integer and a fractional component. The integer component has at most 12 digits; the fractional component has at most three digits. +/// +/// The ABNF for decimals is: +/// ```abnf,ignore,no_run +/// sf-decimal = ["-"] 1*12DIGIT "." 1*3DIGIT +/// ``` +#[derive(Debug, PartialEq, Clone)] +pub struct BareItemDecimal(pub(crate) rust_decimal::Decimal); + +impl TryFrom for BareItemDecimal { + type Error = &'static str; + fn try_from(value: rust_decimal::Decimal) -> Result { + let validated = Self::validate(value)?; + Ok(BareItemDecimal(validated)) + } +} + +impl ValidateValue<'_, rust_decimal::Decimal> for BareItemDecimal { + fn validate(value: rust_decimal::Decimal) -> SFVResult { + let fraction_length = 3; + + let decimal = value.round_dp(fraction_length); + let int_comp = decimal.trunc(); + let int_comp = int_comp + .abs() + .to_u64() + .ok_or("serialize_decimal: integer component > 12 digits")?; + + if int_comp > 999_999_999_999_u64 { + return Err("serialize_decimal: integer component > 12 digits"); + } + + Ok(decimal) + } +} + +/// Validates a bare item value and returns a new sanitized value +/// or passes back ownership of the existing value in case the input needs no change. +pub trait ValidateValue<'a, T> { + fn validate(value: T) -> SFVResult; +} + +impl Deref for BareItemDecimal { + type Target = rust_decimal::Decimal; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl fmt::Display for BareItemDecimal { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +/// Integers have a range of -999,999,999,999,999 to 999,999,999,999,999 inclusive (i.e., up to fifteen digits, signed), for IEEE 754 compatibility. +/// +/// The ABNF for Integers is: +/// ```abnf,ignore,no_run +/// sf-integer = ["-"] 1*15DIGIT +/// ``` +#[derive(Debug, PartialEq, Clone)] +pub struct BareItemInteger(pub(crate) i64); + +impl Deref for BareItemInteger { + type Target = i64; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl TryFrom for BareItemInteger { + type Error = &'static str; + + fn try_from(value: i64) -> Result { + let value = Self::validate(value)?; + Ok(BareItemInteger(value)) + } +} + +impl ValidateValue<'_, i64> for BareItemInteger { + fn validate(value: i64) -> SFVResult { + let (min_int, max_int) = (-999_999_999_999_999_i64, 999_999_999_999_999_i64); + + if !(min_int <= value && value <= max_int) { + return Err("serialize_integer: integer is out of range"); + } + + Ok(value) + } +} + +impl fmt::Display for BareItemInteger { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +/// Strings are zero or more printable ASCII (RFC0020) characters (i.e., the range %x20 to %x7E). Note that this excludes tabs, newlines, carriage returns, etc. +/// +/// The ABNF for Strings is: +/// ```abnf,ignore,no_run +/// sf-string = DQUOTE *chr DQUOTE +/// chr = unescaped / escaped +/// unescaped = %x20-21 / %x23-5B / %x5D-7E +/// escaped = "\" ( DQUOTE / "\" ) +/// ``` +#[derive(Debug, PartialEq, Clone)] +pub struct BareItemString(pub(crate) std::string::String); + +impl Deref for BareItemString { + type Target = String; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl TryFrom for BareItemString { + type Error = &'static str; + fn try_from(value: String) -> Result { + let value = Self::validate(&value)?; + Ok(BareItemString(value.to_owned())) + } +} + +impl TryFrom<&str> for BareItemString { + type Error = &'static str; + fn try_from(value: &str) -> Result { + let value = Self::validate(value)?; + Ok(BareItemString(value.to_owned())) + } +} + +impl<'a> ValidateValue<'a, &'a str> for BareItemString { + fn validate(value: &'a str) -> SFVResult<&'a str> { + if !value.is_ascii() { + return Err("serialize_string: non-ascii character"); + } + + let vchar_or_sp = |char| char == '\x7f' || ('\x00'..='\x1f').contains(&char); + if value.chars().any(vchar_or_sp) { + return Err("serialize_string: not a visible character"); + } + + Ok(value) + } +} + +impl fmt::Display for BareItemString { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +/// Byte Sequences can be conveyed in Structured Fields. +/// +/// The ABNF for a Byte Sequence is: +/// ```abnf,ignore,no_run +/// sf-binary = ":" *(base64) ":" +/// base64 = ALPHA / DIGIT / "+" / "/" / "=" +/// ``` +#[derive(Debug, PartialEq, Clone)] +pub struct BareItemByteSeq(pub(crate) Vec); + +impl From<&[u8]> for BareItemByteSeq { + fn from(value: &[u8]) -> Self { + BareItemByteSeq(value.to_vec()) + } +} + +impl From> for BareItemByteSeq { + fn from(value: Vec) -> Self { + BareItemByteSeq(value) + } +} + +impl Deref for BareItemByteSeq { + type Target = [u8]; + fn deref(&self) -> &Self::Target { + self.0.as_slice() + } +} + +/// Boolean values can be conveyed in Structured Fields. +/// +/// The ABNF for a Boolean is: +/// ```abnf,ignore,no_run +/// sf-boolean = "?" boolean +/// boolean = "0" / "1" +/// ``` +#[derive(Debug, PartialEq, Clone)] +pub struct BareItemBoolean(pub(crate) bool); + +impl From for BareItemBoolean { + fn from(value: bool) -> Self { + BareItemBoolean(value) + } +} + +impl Deref for BareItemBoolean { + type Target = bool; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl fmt::Display for BareItemBoolean { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +/// Tokens are short textual words; their abstract model is identical to their expression in the HTTP field value serialization. +/// +/// The ABNF for Tokens is: +/// ```abnf,ignore,no_run +/// sf-token = ( ALPHA / "*" ) *( tchar / ":" / "/" ) +/// ``` +#[derive(Debug, PartialEq, Clone)] +pub struct BareItemToken(pub(crate) String); + +impl Deref for BareItemToken { + type Target = String; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl TryFrom for BareItemToken { + type Error = &'static str; + fn try_from(value: String) -> Result { + let value = Self::validate(&value)?; + Ok(BareItemToken(value.to_owned())) + } +} + +impl TryFrom<&str> for BareItemToken { + type Error = &'static str; + fn try_from(value: &str) -> Result { + let value = Self::validate(value)?; + Ok(BareItemToken(value.to_owned())) + } +} + +impl<'a> ValidateValue<'a, &'a str> for BareItemToken { + fn validate(value: &'a str) -> SFVResult<&'a str> { + if !value.is_ascii() { + return Err("serialize_string: non-ascii character"); + } + + let mut chars = value.chars(); + if let Some(char) = chars.next() { + if !(char.is_ascii_alphabetic() || char == '*') { + return Err("serialise_token: first character is not ALPHA or '*'"); + } + } + + if chars + .clone() + .any(|c| !(utils::is_tchar(c) || c == ':' || c == '/')) + { + return Err("serialise_token: disallowed character"); + } + + Ok(value) + } +} + +impl fmt::Display for BareItemToken { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +#[cfg(test)] +mod tests { + use std::convert::TryInto; + use std::error::Error; + use std::str::FromStr; + + use super::*; + + #[test] + fn create_non_ascii_string_errors() -> Result<(), Box> { + let disallowed_value: Result = + "non-ascii text 🐹".to_owned().try_into(); + + assert_eq!( + Err("serialize_string: non-ascii character"), + disallowed_value + ); + + Ok(()) + } + + #[test] + fn create_too_long_decimal_errors() -> Result<(), Box> { + let disallowed_value: Result = + rust_decimal::Decimal::from_str("12345678912345.123")?.try_into(); + assert_eq!( + Err("serialize_decimal: integer component > 12 digits"), + disallowed_value + ); + + Ok(()) + } +} diff --git a/src/lib.rs b/src/lib.rs index f7aebfc..c5b3696 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -99,40 +99,51 @@ let dict_header = "u=2, n=(* foo 2)"; Creates `Item` with empty parameters: ``` use sfv::{Item, BareItem, SerializeValue}; - -let str_item = Item::new(BareItem::String(String::from("foo"))); +# use std::convert::TryInto; +# fn main() -> Result<(), &'static str> { +let str_item = Item::new(BareItem::new_string("foo")?); assert_eq!(str_item.serialize_value().unwrap(), "\"foo\""); +# Ok(()) +# } ``` Creates `Item` field value with parameters: ``` -use sfv::{Item, BareItem, SerializeValue, Parameters, Decimal, FromPrimitive}; +use sfv::{Item, BareItem, SerializeValue, Parameters, FromPrimitive}; +use rust_decimal::Decimal; +# use std::convert::TryInto; +# fn main() -> Result<(), &'static str> { let mut params = Parameters::new(); let decimal = Decimal::from_f64(13.45655).unwrap(); -params.insert("key".into(), BareItem::Decimal(decimal)); -let int_item = Item::with_params(BareItem::Integer(99), params); +params.insert("key".into(), BareItem::new_decimal(decimal)?); +let int_item = Item::with_params(BareItem::new_integer(99_i64)?, params); assert_eq!(int_item.serialize_value().unwrap(), "99;key=13.457"); +# Ok(()) +# } ``` Creates `List` field value with `Item` and parametrized `InnerList` as members: ``` use sfv::{Item, BareItem, InnerList, List, SerializeValue, Parameters}; -let tok_item = BareItem::Token("tok".into()); +# use std::convert::TryInto; +# fn main() -> Result<(), &'static str> { + +let tok_item = BareItem::new_token("tok")?; // Creates Item. -let str_item = Item::new(BareItem::String(String::from("foo"))); +let str_item = Item::new(BareItem::new_string("foo")?); // Creates InnerList members. let mut int_item_params = Parameters::new(); -int_item_params.insert("key".into(), BareItem::Boolean(false)); -let int_item = Item::with_params(BareItem::Integer(99), int_item_params); +int_item_params.insert("key".into(), BareItem::new_boolean(false)?); +let int_item = Item::with_params(BareItem::new_integer(99_i64)?, int_item_params); // Creates InnerList. let mut inner_list_params = Parameters::new(); -inner_list_params.insert("bar".into(), BareItem::Boolean(true)); +inner_list_params.insert("bar".into(), BareItem::new_boolean(true)?); let inner_list = InnerList::with_params(vec![int_item, str_item], inner_list_params); @@ -141,15 +152,21 @@ assert_eq!( list.serialize_value().unwrap(), "tok, (99;key=?0 \"foo\");bar" ); + +# Ok(()) +# } ``` Creates `Dictionary` field value: ``` use sfv::{Parser, Item, BareItem, SerializeValue, ParseValue, Dictionary}; -let member_value1 = Item::new(BareItem::String(String::from("apple"))); -let member_value2 = Item::new(BareItem::Boolean(true)); -let member_value3 = Item::new(BareItem::Boolean(false)); +# use std::convert::TryInto; +# fn main() -> Result<(), &'static str> { + +let member_value1 = Item::new(BareItem::new_string("apple")?); +let member_value2 = Item::new(BareItem::new_boolean(true)?); +let member_value3 = Item::new(BareItem::new_boolean(false)?); let mut dict = Dictionary::new(); dict.insert("key1".into(), member_value1.into()); @@ -161,9 +178,13 @@ assert_eq!( "key1=\"apple\", key2, key3=?0" ); +# Ok(()) +# } + ``` */ +mod bare_item; mod parser; mod ref_serializer; mod serializer; @@ -173,17 +194,20 @@ mod utils; mod test_parser; #[cfg(test)] mod test_serializer; + use indexmap::IndexMap; -pub use rust_decimal::{ - prelude::{FromPrimitive, FromStr}, - Decimal, -}; +pub use rust_decimal::prelude::{FromPrimitive, FromStr}; pub use parser::{ParseMore, ParseValue, Parser}; pub use ref_serializer::{RefDictSerializer, RefItemSerializer, RefListSerializer}; pub use serializer::SerializeValue; +pub use bare_item::{ + BareItem, BareItemBoolean, BareItemByteSeq, BareItemDecimal, BareItemInteger, BareItemString, + BareItemToken, +}; + type SFVResult = std::result::Result; /// Represents `Item` type structured field value. @@ -282,135 +306,9 @@ impl InnerList { } } -/// `BareItem` type is used to construct `Items` or `Parameters` values. -#[derive(Debug, PartialEq, Clone)] -pub enum BareItem { - /// Decimal number - // sf-decimal = ["-"] 1*12DIGIT "." 1*3DIGIT - Decimal(Decimal), - /// Integer number - // sf-integer = ["-"] 1*15DIGIT - Integer(i64), - // sf-string = DQUOTE *chr DQUOTE - // chr = unescaped / escaped - // unescaped = %x20-21 / %x23-5B / %x5D-7E - // escaped = "\" ( DQUOTE / "\" ) - String(String), - // ":" *(base64) ":" - // base64 = ALPHA / DIGIT / "+" / "/" / "=" - ByteSeq(Vec), - // sf-boolean = "?" boolean - // boolean = "0" / "1" - Boolean(bool), - // sf-token = ( ALPHA / "*" ) *( tchar / ":" / "/" ) - Token(String), -} - -impl BareItem { - /// If `BareItem` is a decimal, returns `Decimal`, otherwise returns `None`. - /// ``` - /// # use sfv::{BareItem, Decimal, FromPrimitive}; - /// let decimal_number = Decimal::from_f64(415.566).unwrap(); - /// let bare_item: BareItem = decimal_number.into(); - /// assert_eq!(bare_item.as_decimal().unwrap(), decimal_number); - /// ``` - pub fn as_decimal(&self) -> Option { - match *self { - BareItem::Decimal(val) => Some(val), - _ => None, - } - } - /// If `BareItem` is an integer, returns `i64`, otherwise returns `None`. - /// ``` - /// # use sfv::BareItem; - /// let bare_item: BareItem = 100.into(); - /// assert_eq!(bare_item.as_int().unwrap(), 100); - /// ``` - pub fn as_int(&self) -> Option { - match *self { - BareItem::Integer(val) => Some(val), - _ => None, - } - } - /// If `BareItem` is `String`, returns `&str`, otherwise returns `None`. - /// ``` - /// # use sfv::BareItem; - /// let bare_item = BareItem::String("foo".into()); - /// assert_eq!(bare_item.as_str().unwrap(), "foo"); - /// ``` - pub fn as_str(&self) -> Option<&str> { - match *self { - BareItem::String(ref val) => Some(val), - _ => None, - } - } - /// If `BareItem` is a `ByteSeq`, returns `&Vec`, otherwise returns `None`. - /// ``` - /// # use sfv::BareItem; - /// let bare_item = BareItem::ByteSeq("foo".to_owned().into_bytes()); - /// assert_eq!(bare_item.as_byte_seq().unwrap().as_slice(), "foo".as_bytes()); - /// ``` - pub fn as_byte_seq(&self) -> Option<&Vec> { - match *self { - BareItem::ByteSeq(ref val) => Some(val), - _ => None, - } - } - /// If `BareItem` is a `Boolean`, returns `bool`, otherwise returns `None`. - /// ``` - /// # use sfv::{BareItem, Decimal, FromPrimitive}; - /// let bare_item = BareItem::Boolean(true); - /// assert_eq!(bare_item.as_bool().unwrap(), true); - /// ``` - pub fn as_bool(&self) -> Option { - match *self { - BareItem::Boolean(val) => Some(val), - _ => None, - } - } - /// If `BareItem` is a `Token`, returns `&str`, otherwise returns `None`. - /// ``` - /// use sfv::BareItem; - /// - /// let bare_item = BareItem::Token("*bar".into()); - /// assert_eq!(bare_item.as_token().unwrap(), "*bar"); - /// ``` - pub fn as_token(&self) -> Option<&str> { - match *self { - BareItem::Token(ref val) => Some(val), - _ => None, - } - } -} - -impl From for BareItem { - /// Converts `i64` into `BareItem::Integer`. - /// ``` - /// # use sfv::BareItem; - /// let bare_item: BareItem = 456.into(); - /// assert_eq!(bare_item.as_int().unwrap(), 456); - /// ``` - fn from(item: i64) -> Self { - BareItem::Integer(item) - } -} - -impl From for BareItem { - /// Converts `Decimal` into `BareItem::Decimal`. - /// ``` - /// # use sfv::{BareItem, Decimal, FromPrimitive}; - /// let decimal_number = Decimal::from_f64(48.01).unwrap(); - /// let bare_item: BareItem = decimal_number.into(); - /// assert_eq!(bare_item.as_decimal().unwrap(), decimal_number); - /// ``` - fn from(item: Decimal) -> Self { - BareItem::Decimal(item) - } -} - #[derive(Debug, PartialEq)] pub(crate) enum Num { - Decimal(Decimal), + Decimal(BareItemDecimal), Integer(i64), } @@ -418,7 +316,7 @@ pub(crate) enum Num { #[derive(Debug, PartialEq, Clone)] pub enum RefBareItem<'a> { Integer(i64), - Decimal(Decimal), + Decimal(rust_decimal::Decimal), String(&'a str), ByteSeq(&'a [u8]), Boolean(bool), @@ -429,12 +327,12 @@ impl BareItem { /// Converts `BareItem` into `RefBareItem`. fn to_ref_bare_item(&self) -> RefBareItem { match self { - BareItem::Integer(val) => RefBareItem::Integer(*val), - BareItem::Decimal(val) => RefBareItem::Decimal(*val), + BareItem::Integer(val) => RefBareItem::Integer(**val), + BareItem::Decimal(val) => RefBareItem::Decimal(**val), BareItem::String(val) => RefBareItem::String(val), - BareItem::ByteSeq(val) => RefBareItem::ByteSeq(val.as_slice()), - BareItem::Boolean(val) => RefBareItem::Boolean(*val), - BareItem::Token(val) => RefBareItem::Token(val), + BareItem::ByteSeq(val) => RefBareItem::ByteSeq(val), + BareItem::Boolean(val) => RefBareItem::Boolean(**val), + BareItem::Token(val) => RefBareItem::Token(&val), } } } diff --git a/src/parser.rs b/src/parser.rs index 943380f..19e4f7e 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,7 +1,7 @@ -use crate::utils; +use crate::{bare_item, utils}; use crate::{ - BareItem, Decimal, Dictionary, FromStr, InnerList, Item, List, ListEntry, Num, Parameters, - SFVResult, + BareItem, BareItemBoolean, BareItemByteSeq, BareItemDecimal, BareItemToken, Dictionary, + FromStr, InnerList, Item, List, ListEntry, Num, Parameters, SFVResult, }; use std::iter::Peekable; use std::str::{from_utf8, Chars}; @@ -91,7 +91,7 @@ impl ParseValue for Dictionary { let value = true; let params = Parser::parse_parameters(input_chars)?; let member = Item { - bare_item: BareItem::Boolean(value), + bare_item: BareItem::Boolean(value.into()), params, }; dict.insert(this_key, member.into()); @@ -234,15 +234,21 @@ impl Parser { } match input_chars.peek() { - Some(&'?') => Ok(BareItem::Boolean(Self::parse_bool(input_chars)?)), - Some(&'"') => Ok(BareItem::String(Self::parse_string(input_chars)?)), - Some(&':') => Ok(BareItem::ByteSeq(Self::parse_byte_sequence(input_chars)?)), - Some(&c) if c == '*' || c.is_ascii_alphabetic() => { - Ok(BareItem::Token(Self::parse_token(input_chars)?)) - } + Some(&'?') => Ok(BareItem::Boolean(BareItemBoolean(Self::parse_bool( + input_chars, + )?))), + Some(&'"') => Ok(BareItem::String(bare_item::BareItemString( + Self::parse_string(input_chars)?, + ))), + Some(&':') => Ok(BareItem::ByteSeq(BareItemByteSeq( + Self::parse_byte_sequence(input_chars)?, + ))), + Some(&c) if c == '*' || c.is_ascii_alphabetic() => Ok(BareItem::Token(BareItemToken( + Self::parse_token(input_chars)?, + ))), Some(&c) if c == '-' || c.is_ascii_digit() => match Self::parse_number(input_chars)? { Num::Decimal(val) => Ok(BareItem::Decimal(val)), - Num::Integer(val) => Ok(BareItem::Integer(val)), + Num::Integer(val) => Ok(BareItem::Integer(bare_item::BareItemInteger(val))), }, _ => Err("parse_bare_item: item type can't be identified"), } @@ -376,14 +382,14 @@ impl Parser { match chars_after_dot { Some(0) => Err("parse_number: decimal ends with '.'"), Some(1..=3) => { - let mut output_number = Decimal::from_str(&input_number) + let mut output_number = rust_decimal::Decimal::from_str(&input_number) .map_err(|_err| "parse_number: parsing f64 failed")?; if sign == -1 { output_number.set_sign_negative(true) } - Ok(Num::Decimal(output_number)) + Ok(Num::Decimal(BareItemDecimal(output_number))) } _ => Err("parse_number: invalid decimal fraction length"), } @@ -440,7 +446,7 @@ impl Parser { input_chars.next(); Self::parse_bare_item(input_chars)? } - _ => BareItem::Boolean(true), + _ => BareItem::Boolean(true.into()), }; params.insert(param_name, param_value); } diff --git a/src/ref_serializer.rs b/src/ref_serializer.rs index 4bcd29f..a318683 100644 --- a/src/ref_serializer.rs +++ b/src/ref_serializer.rs @@ -115,7 +115,8 @@ impl<'a> RefListSerializer<'a> { /// Serializes `Dictionary` field value components incrementally. /// ``` -/// use sfv::{RefBareItem, RefDictSerializer, Decimal, FromPrimitive}; +/// use sfv::{RefBareItem, RefDictSerializer, FromPrimitive}; +/// use rust_decimal::Decimal; /// /// let mut serialized_item = String::new(); /// let serializer = RefDictSerializer::new(&mut serialized_item); @@ -245,7 +246,7 @@ impl<'a> Container<'a> for RefDictSerializer<'a> { #[cfg(test)] mod alternative_serializer_tests { use super::*; - use crate::{Decimal, FromPrimitive}; + use crate::FromPrimitive; #[test] fn test_fast_serialize_item() -> SFVResult<()> { @@ -287,7 +288,7 @@ mod alternative_serializer_tests { .bare_item_member("member2", &RefBareItem::Boolean(true))? .parameter( "key3", - &RefBareItem::Decimal(Decimal::from_f64(45.4586).unwrap()), + &RefBareItem::Decimal(rust_decimal::Decimal::from_f64(45.4586).unwrap()), )? .parameter("key4", &RefBareItem::String("str"))? .open_inner_list("key5")? diff --git a/src/serializer.rs b/src/serializer.rs index b0ed021..8f23efc 100644 --- a/src/serializer.rs +++ b/src/serializer.rs @@ -1,8 +1,8 @@ -use crate::utils; +use crate::bare_item::ValidateValue; use crate::{ - BareItem, Decimal, Dictionary, InnerList, Item, List, ListEntry, Parameters, RefBareItem, - SFVResult, + BareItem, Dictionary, InnerList, Item, List, ListEntry, Parameters, RefBareItem, SFVResult, }; +use crate::{BareItemDecimal, BareItemInteger, BareItemString, BareItemToken}; use data_encoding::BASE64; /// Serializes structured field value into String. @@ -99,7 +99,7 @@ impl Serializer { ListEntry::Item(ref item) => { // If dict member is boolean true, no need to serialize it: only its params must be serialized // Otherwise serialize entire item with its params - if item.bare_item == BareItem::Boolean(true) { + if item.bare_item == BareItem::Boolean(true.into()) { Self::serialize_parameters(&item.params, output)?; } else { output.push('='); @@ -214,35 +214,21 @@ impl Serializer { } pub(crate) fn serialize_integer(value: i64, output: &mut String) -> SFVResult<()> { - //https://httpwg.org/specs/rfc8941.html#ser-integer - - let (min_int, max_int) = (-999_999_999_999_999_i64, 999_999_999_999_999_i64); - if !(min_int <= value && value <= max_int) { - return Err("serialize_integer: integer is out of range"); - } + // https://httpwg.org/specs/rfc8941.html#ser-integer + let value = BareItemInteger::validate(value)?; output.push_str(&value.to_string()); Ok(()) } - pub(crate) fn serialize_decimal(value: Decimal, output: &mut String) -> SFVResult<()> { + pub(crate) fn serialize_decimal( + value: rust_decimal::Decimal, + output: &mut String, + ) -> SFVResult<()> { // https://httpwg.org/specs/rfc8941.html#ser-decimal + let decimal = BareItemDecimal::validate(value)?; - let integer_comp_length = 12; - let fraction_length = 3; - - let decimal = value.round_dp(fraction_length); - let int_comp = decimal.trunc(); - let fract_comp = decimal.fract(); - - // TODO: Replace with > 999_999_999_999_u64 - if int_comp.abs().to_string().len() > integer_comp_length { - return Err("serialize_decimal: integer component > 12 digits"); - } - - if fract_comp.is_zero() { - output.push_str(&int_comp.to_string()); - output.push('.'); - output.push('0'); + if decimal.fract().is_zero() { + output.push_str(&format!("{:.1}", &decimal)); } else { output.push_str(&decimal.to_string()); } @@ -252,15 +238,7 @@ impl Serializer { pub(crate) fn serialize_string(value: &str, output: &mut String) -> SFVResult<()> { // https://httpwg.org/specs/rfc8941.html#ser-integer - - if !value.is_ascii() { - return Err("serialize_string: non-ascii character"); - } - - let vchar_or_sp = |char| char == '\x7f' || ('\x00'..='\x1f').contains(&char); - if value.chars().any(vchar_or_sp) { - return Err("serialize_string: not a visible character"); - } + let value = BareItemString::validate(value)?; output.push('\"'); for char in value.chars() { @@ -276,24 +254,7 @@ impl Serializer { pub(crate) fn serialize_token(value: &str, output: &mut String) -> SFVResult<()> { // https://httpwg.org/specs/rfc8941.html#ser-token - - if !value.is_ascii() { - return Err("serialize_string: non-ascii character"); - } - - let mut chars = value.chars(); - if let Some(char) = chars.next() { - if !(char.is_ascii_alphabetic() || char == '*') { - return Err("serialise_token: first character is not ALPHA or '*'"); - } - } - - if chars - .clone() - .any(|c| !(utils::is_tchar(c) || c == ':' || c == '/')) - { - return Err("serialise_token: disallowed character"); - } + let value = BareItemToken::validate(value)?; output.push_str(value); Ok(()) diff --git a/src/test_parser.rs b/src/test_parser.rs index 97404eb..c727a6f 100644 --- a/src/test_parser.rs +++ b/src/test_parser.rs @@ -1,19 +1,21 @@ use crate::FromStr; -use crate::{BareItem, Decimal, Dictionary, InnerList, Item, List, Num, Parameters}; +use crate::{BareItem, Dictionary, InnerList, Item, List, Num, Parameters}; use crate::{ParseMore, ParseValue, Parser}; +use rust_decimal::Decimal; +use std::convert::TryInto; use std::error::Error; use std::iter::FromIterator; #[test] fn parse() -> Result<(), Box> { let input = "\"some_value\"".as_bytes(); - let parsed_item = Item::new(BareItem::String("some_value".to_owned())); + let parsed_item = Item::new(BareItem::String("some_value".to_owned().try_into()?)); let expected = parsed_item; assert_eq!(expected, Parser::parse_item(input)?); let input = "12.35;a ".as_bytes(); - let params = Parameters::from_iter(vec![("a".to_owned(), BareItem::Boolean(true))]); - let expected = Item::with_params(Decimal::from_str("12.35")?.into(), params); + let params = Parameters::from_iter(vec![("a".to_owned(), BareItem::Boolean(true.into()))]); + let expected = Item::with_params(Decimal::from_str("12.35")?.try_into()?, params); assert_eq!(expected, Parser::parse_item(input)?); Ok(()) @@ -41,8 +43,8 @@ fn parse_errors() -> Result<(), Box> { #[test] fn parse_list_of_numbers() -> Result<(), Box> { let mut input = "1,42".chars().peekable(); - let item1 = Item::new(1.into()); - let item2 = Item::new(42.into()); + let item1 = Item::new(1.try_into()?); + let item2 = Item::new(42.try_into()?); let expected_list: List = vec![item1.into(), item2.into()]; assert_eq!(expected_list, List::parse(&mut input)?); Ok(()) @@ -51,8 +53,8 @@ fn parse_list_of_numbers() -> Result<(), Box> { #[test] fn parse_list_with_multiple_spaces() -> Result<(), Box> { let mut input = "1 , 42".chars().peekable(); - let item1 = Item::new(1.into()); - let item2 = Item::new(42.into()); + let item1 = Item::new(1.try_into()?); + let item2 = Item::new(42.try_into()?); let expected_list: List = vec![item1.into(), item2.into()]; assert_eq!(expected_list, List::parse(&mut input)?); Ok(()) @@ -61,10 +63,10 @@ fn parse_list_with_multiple_spaces() -> Result<(), Box> { #[test] fn parse_list_of_lists() -> Result<(), Box> { let mut input = "(1 2), (42 43)".chars().peekable(); - let item1 = Item::new(1.into()); - let item2 = Item::new(2.into()); - let item3 = Item::new(42.into()); - let item4 = Item::new(43.into()); + let item1 = Item::new(1.try_into()?); + let item2 = Item::new(2.try_into()?); + let item3 = Item::new(42.try_into()?); + let item4 = Item::new(43.try_into()?); let inner_list_1 = InnerList::new(vec![item1, item2]); let inner_list_2 = InnerList::new(vec![item3, item4]); let expected_list: List = vec![inner_list_1.into(), inner_list_2.into()]; @@ -92,10 +94,12 @@ fn parse_list_empty() -> Result<(), Box> { #[test] fn parse_list_of_lists_with_param_and_spaces() -> Result<(), Box> { let mut input = "( 1 42 ); k=*".chars().peekable(); - let item1 = Item::new(1.into()); - let item2 = Item::new(42.into()); - let inner_list_param = - Parameters::from_iter(vec![("k".to_owned(), BareItem::Token("*".to_owned()))]); + let item1 = Item::new(1.try_into()?); + let item2 = Item::new(42.try_into()?); + let inner_list_param = Parameters::from_iter(vec![( + "k".to_owned(), + BareItem::Token("*".to_owned().try_into()?), + )]); let inner_list = InnerList::with_params(vec![item1, item2], inner_list_param); let expected_list: List = vec![inner_list.into()]; assert_eq!(expected_list, List::parse(&mut input)?); @@ -107,13 +111,13 @@ fn parse_list_of_items_and_lists_with_param() -> Result<(), Box> { let mut input = "12, 14, (a b); param=\"param_value_1\", ()" .chars() .peekable(); - let item1 = Item::new(12.into()); - let item2 = Item::new(14.into()); - let item3 = Item::new(BareItem::Token("a".to_owned())); - let item4 = Item::new(BareItem::Token("b".to_owned())); + let item1 = Item::new(12.try_into()?); + let item2 = Item::new(14.try_into()?); + let item3 = Item::new(BareItem::Token("a".to_owned().try_into()?)); + let item4 = Item::new(BareItem::Token("b".to_owned().try_into()?)); let inner_list_param = Parameters::from_iter(vec![( "param".to_owned(), - BareItem::String("param_value_1".to_owned()), + BareItem::String("param_value_1".to_owned().try_into()?), )]); let inner_list = InnerList::with_params(vec![item3, item4], inner_list_param); let empty_inner_list = InnerList::new(vec![]); @@ -178,10 +182,10 @@ fn parse_inner_list_errors() -> Result<(), Box> { #[test] fn parse_inner_list_with_param_and_spaces() -> Result<(), Box> { let mut input = "(c b); a=1".chars().peekable(); - let inner_list_param = Parameters::from_iter(vec![("a".to_owned(), 1.into())]); + let inner_list_param = Parameters::from_iter(vec![("a".to_owned(), 1.try_into()?)]); - let item1 = Item::new(BareItem::Token("c".to_owned())); - let item2 = Item::new(BareItem::Token("b".to_owned())); + let item1 = Item::new(BareItem::Token("c".to_owned().try_into()?)); + let item2 = Item::new(BareItem::Token("b".to_owned().try_into()?)); let expected = InnerList::with_params(vec![item1, item2], inner_list_param); assert_eq!(expected, Parser::parse_inner_list(&mut input)?); Ok(()) @@ -190,16 +194,16 @@ fn parse_inner_list_with_param_and_spaces() -> Result<(), Box> { #[test] fn parse_item_int_with_space() -> Result<(), Box> { let mut input = "12 ".chars().peekable(); - assert_eq!(Item::new(12.into()), Item::parse(&mut input)?); + assert_eq!(Item::new(12.try_into()?), Item::parse(&mut input)?); Ok(()) } #[test] fn parse_item_decimal_with_bool_param_and_space() -> Result<(), Box> { let mut input = "12.35;a ".chars().peekable(); - let param = Parameters::from_iter(vec![("a".to_owned(), BareItem::Boolean(true))]); + let param = Parameters::from_iter(vec![("a".to_owned(), BareItem::Boolean(true.into()))]); assert_eq!( - Item::with_params(Decimal::from_str("12.35")?.into(), param), + Item::with_params(Decimal::from_str("12.35")?.try_into()?, param), Item::parse(&mut input)? ); Ok(()) @@ -207,9 +211,12 @@ fn parse_item_decimal_with_bool_param_and_space() -> Result<(), Box> #[test] fn parse_item_number_with_param() -> Result<(), Box> { - let param = Parameters::from_iter(vec![("a1".to_owned(), BareItem::Token("*".to_owned()))]); + let param = Parameters::from_iter(vec![( + "a1".to_owned(), + BareItem::Token("*".to_owned().try_into()?), + )]); assert_eq!( - Item::with_params(BareItem::String("12.35".to_owned()), param), + Item::with_params(BareItem::String("12.35".to_owned().try_into()?), param), Item::parse(&mut "\"12.35\";a1=*".chars().peekable())? ); Ok(()) @@ -253,16 +260,21 @@ fn parse_dict_with_spaces_and_params() -> Result<(), Box> { let mut input = "abc=123;a=1;b=2, def=456, ghi=789;q=9;r=\"+w\"" .chars() .peekable(); - let item1_params = - Parameters::from_iter(vec![("a".to_owned(), 1.into()), ("b".to_owned(), 2.into())]); + let item1_params = Parameters::from_iter(vec![ + ("a".to_owned(), 1.try_into()?), + ("b".to_owned(), 2.try_into()?), + ]); let item3_params = Parameters::from_iter(vec![ - ("q".to_owned(), 9.into()), - ("r".to_owned(), BareItem::String("+w".to_owned())), + ("q".to_owned(), 9.try_into()?), + ( + "r".to_owned(), + BareItem::String("+w".to_owned().try_into()?), + ), ]); - let item1 = Item::with_params(123.into(), item1_params); - let item2 = Item::new(456.into()); - let item3 = Item::with_params(789.into(), item3_params); + let item1 = Item::with_params(123.try_into()?, item1_params); + let item2 = Item::new(456.try_into()?); + let item3 = Item::with_params(789.try_into()?, item3_params); let expected_dict = Dictionary::from_iter(vec![ ("abc".to_owned(), item1.into()), @@ -286,11 +298,13 @@ fn parse_dict_empty_value() -> Result<(), Box> { #[test] fn parse_dict_with_token_param() -> Result<(), Box> { let mut input = "a=1, b;foo=*, c=3".chars().peekable(); - let item2_params = - Parameters::from_iter(vec![("foo".to_owned(), BareItem::Token("*".to_owned()))]); - let item1 = Item::new(1.into()); - let item2 = Item::with_params(BareItem::Boolean(true), item2_params); - let item3 = Item::new(3.into()); + let item2_params = Parameters::from_iter(vec![( + "foo".to_owned(), + BareItem::Token("*".to_owned().try_into()?), + )]); + let item1 = Item::new(1.try_into()?); + let item2 = Item::with_params(BareItem::Boolean(true.into()), item2_params); + let item3 = Item::new(3.try_into()?); let expected_dict = Dictionary::from_iter(vec![ ("a".to_owned(), item1.into()), ("b".to_owned(), item2.into()), @@ -303,8 +317,8 @@ fn parse_dict_with_token_param() -> Result<(), Box> { #[test] fn parse_dict_multiple_spaces() -> Result<(), Box> { // input1, input2, input3 must be parsed into the same structure - let item1 = Item::new(1.into()); - let item2 = Item::new(2.into()); + let item1 = Item::new(1.try_into()?); + let item2 = Item::new(2.try_into()?); let expected_dict = Dictionary::from_iter(vec![ ("a".to_owned(), item1.into()), ("b".to_owned(), item2.into()), @@ -323,23 +337,23 @@ fn parse_dict_multiple_spaces() -> Result<(), Box> { #[test] fn parse_bare_item() -> Result<(), Box> { assert_eq!( - BareItem::Boolean(false), + BareItem::Boolean(false.into()), Parser::parse_bare_item(&mut "?0".chars().peekable())? ); assert_eq!( - BareItem::String("test string".to_owned()), + BareItem::String("test string".to_owned().try_into()?), Parser::parse_bare_item(&mut "\"test string\"".chars().peekable())? ); assert_eq!( - BareItem::Token("*token".to_owned()), + BareItem::Token("*token".to_owned().try_into()?), Parser::parse_bare_item(&mut "*token".chars().peekable())? ); assert_eq!( - BareItem::ByteSeq("base_64 encoding test".to_owned().into_bytes()), + BareItem::ByteSeq("base_64 encoding test".to_owned().into_bytes().into()), Parser::parse_bare_item(&mut ":YmFzZV82NCBlbmNvZGluZyB0ZXN0:".chars().peekable())? ); assert_eq!( - BareItem::Decimal(Decimal::from_str("-3.55")?), + BareItem::Decimal(Decimal::from_str("-3.55")?.try_into()?), Parser::parse_bare_item(&mut "-3.55".chars().peekable())? ); Ok(()) @@ -600,37 +614,37 @@ fn parse_number_int() -> Result<(), Box> { fn parse_number_decimal() -> Result<(), Box> { let mut input = "00.42 test string".chars().peekable(); assert_eq!( - Num::Decimal(Decimal::from_str("0.42")?), + Num::Decimal(Decimal::from_str("0.42")?.try_into()?), Parser::parse_number(&mut input)? ); assert_eq!(" test string", input.collect::()); assert_eq!( - Num::Decimal(Decimal::from_str("1.5")?), + Num::Decimal(Decimal::from_str("1.5")?.try_into()?), Parser::parse_number(&mut "1.5.4.".chars().peekable())? ); assert_eq!( - Num::Decimal(Decimal::from_str("1.8")?), + Num::Decimal(Decimal::from_str("1.8")?.try_into()?), Parser::parse_number(&mut "1.8.".chars().peekable())? ); assert_eq!( - Num::Decimal(Decimal::from_str("1.7")?), + Num::Decimal(Decimal::from_str("1.7")?.try_into()?), Parser::parse_number(&mut "1.7.0".chars().peekable())? ); assert_eq!( - Num::Decimal(Decimal::from_str("3.14")?), + Num::Decimal(Decimal::from_str("3.14")?.try_into()?), Parser::parse_number(&mut "3.14".chars().peekable())? ); assert_eq!( - Num::Decimal(Decimal::from_str("-3.14")?), + Num::Decimal(Decimal::from_str("-3.14")?.try_into()?), Parser::parse_number(&mut "-3.14".chars().peekable())? ); assert_eq!( - Num::Decimal(Decimal::from_str("123456789012.1")?), + Num::Decimal(Decimal::from_str("123456789012.1")?.try_into()?), Parser::parse_number(&mut "123456789012.1".chars().peekable())? ); assert_eq!( - Num::Decimal(Decimal::from_str("1234567890.112")?), + Num::Decimal(Decimal::from_str("1234567890.112")?.try_into()?), Parser::parse_number(&mut "1234567890.112".chars().peekable())? ); @@ -714,7 +728,7 @@ fn parse_params_string() -> Result<(), Box> { let mut input = ";b=\"param_val\"".chars().peekable(); let expected = Parameters::from_iter(vec![( "b".to_owned(), - BareItem::String("param_val".to_owned()), + BareItem::String("param_val".to_owned().try_into()?), )]); assert_eq!(expected, Parser::parse_parameters(&mut input)?); Ok(()) @@ -724,8 +738,8 @@ fn parse_params_string() -> Result<(), Box> { fn parse_params_bool() -> Result<(), Box> { let mut input = ";b;a".chars().peekable(); let expected = Parameters::from_iter(vec![ - ("b".to_owned(), BareItem::Boolean(true)), - ("a".to_owned(), BareItem::Boolean(true)), + ("b".to_owned(), BareItem::Boolean(true.into())), + ("a".to_owned(), BareItem::Boolean(true.into())), ]); assert_eq!(expected, Parser::parse_parameters(&mut input)?); Ok(()) @@ -735,8 +749,8 @@ fn parse_params_bool() -> Result<(), Box> { fn parse_params_mixed_types() -> Result<(), Box> { let mut input = ";key1=?0;key2=746.15".chars().peekable(); let expected = Parameters::from_iter(vec![ - ("key1".to_owned(), BareItem::Boolean(false)), - ("key2".to_owned(), Decimal::from_str("746.15")?.into()), + ("key1".to_owned(), BareItem::Boolean(false.into())), + ("key2".to_owned(), Decimal::from_str("746.15")?.try_into()?), ]); assert_eq!(expected, Parser::parse_parameters(&mut input)?); Ok(()) @@ -746,8 +760,8 @@ fn parse_params_mixed_types() -> Result<(), Box> { fn parse_params_with_spaces() -> Result<(), Box> { let mut input = "; key1=?0; key2=11111".chars().peekable(); let expected = Parameters::from_iter(vec![ - ("key1".to_owned(), BareItem::Boolean(false)), - ("key2".to_owned(), 11111.into()), + ("key1".to_owned(), BareItem::Boolean(false.into())), + ("key2".to_owned(), 11111.try_into()?), ]); assert_eq!(expected, Parser::parse_parameters(&mut input)?); Ok(()) @@ -806,9 +820,9 @@ fn parse_key_errors() -> Result<(), Box> { #[test] fn parse_more_list() -> Result<(), Box> { - let item1 = Item::new(1.into()); - let item2 = Item::new(2.into()); - let item3 = Item::new(42.into()); + let item1 = Item::new(1.try_into()?); + let item2 = Item::new(2.try_into()?); + let item3 = Item::new(42.try_into()?); let inner_list_1 = InnerList::new(vec![item1, item2]); let expected_list: List = vec![inner_list_1.into(), item3.into()]; @@ -820,11 +834,13 @@ fn parse_more_list() -> Result<(), Box> { #[test] fn parse_more_dict() -> Result<(), Box> { - let item2_params = - Parameters::from_iter(vec![("foo".to_owned(), BareItem::Token("*".to_owned()))]); - let item1 = Item::new(1.into()); - let item2 = Item::with_params(BareItem::Boolean(true), item2_params); - let item3 = Item::new(3.into()); + let item2_params = Parameters::from_iter(vec![( + "foo".to_owned(), + BareItem::Token("*".to_owned().try_into()?), + )]); + let item1 = Item::new(1.try_into()?); + let item2 = Item::with_params(BareItem::Boolean(true.into()), item2_params); + let item3 = Item::new(3.try_into()?); let expected_dict = Dictionary::from_iter(vec![ ("a".to_owned(), item1.into()), ("b".to_owned(), item2.into()), diff --git a/src/test_serializer.rs b/src/test_serializer.rs index edcc79c..ebb2be7 100644 --- a/src/test_serializer.rs +++ b/src/test_serializer.rs @@ -1,7 +1,9 @@ use crate::serializer::Serializer; use crate::FromStr; use crate::SerializeValue; -use crate::{BareItem, Decimal, Dictionary, InnerList, Item, List, Parameters}; +use crate::{BareItem, Dictionary, InnerList, Item, List, Parameters}; +use rust_decimal::Decimal; +use std::convert::TryInto; use std::error::Error; use std::iter::FromIterator; @@ -27,23 +29,28 @@ fn serialize_value_empty_list() -> Result<(), Box> { #[test] fn serialize_value_list_mixed_members_with_params() -> Result<(), Box> { - let item1 = Item::new(Decimal::from_str("42.4568")?.into()); - let item2_param = Parameters::from_iter(vec![("itm2_p".to_owned(), BareItem::Boolean(true))]); - let item2 = Item::with_params(17.into(), item2_param); + let item1 = Item::new(Decimal::from_str("42.4568")?.try_into()?); + let item2_param = + Parameters::from_iter(vec![("itm2_p".to_owned(), BareItem::Boolean(true.into()))]); + let item2 = Item::with_params(17.try_into()?, item2_param); let inner_list_item1_param = - Parameters::from_iter(vec![("in1_p".to_owned(), BareItem::Boolean(false))]); - let inner_list_item1 = - Item::with_params(BareItem::String("str1".to_owned()), inner_list_item1_param); + Parameters::from_iter(vec![("in1_p".to_owned(), BareItem::Boolean(false.into()))]); + let inner_list_item1 = Item::with_params( + BareItem::String("str1".to_owned().try_into()?), + inner_list_item1_param, + ); let inner_list_item2_param = Parameters::from_iter(vec![( "in2_p".to_owned(), - BareItem::String("valu\\e".to_owned()), + BareItem::String("valu\\e".to_owned().try_into()?), )]); - let inner_list_item2 = - Item::with_params(BareItem::Token("str2".to_owned()), inner_list_item2_param); + let inner_list_item2 = Item::with_params( + BareItem::Token("str2".to_owned().try_into()?), + inner_list_item2_param, + ); let inner_list_param = Parameters::from_iter(vec![( "inner_list_param".to_owned(), - BareItem::ByteSeq("weather".as_bytes().to_vec()), + BareItem::ByteSeq("weather".as_bytes().into()), )]); let inner_list = InnerList::with_params(vec![inner_list_item1, inner_list_item2], inner_list_param); @@ -56,20 +63,9 @@ fn serialize_value_list_mixed_members_with_params() -> Result<(), Box #[test] fn serialize_value_errors() -> Result<(), Box> { - let disallowed_item = Item::new(BareItem::String("non-ascii text 🐹".into())); - assert_eq!( - Err("serialize_string: non-ascii character"), - disallowed_item.serialize_value() - ); - - let disallowed_item = Item::new(Decimal::from_str("12345678912345.123")?.into()); - assert_eq!( - Err("serialize_decimal: integer component > 12 digits"), - disallowed_item.serialize_value() - ); - - let param_with_disallowed_key = Parameters::from_iter(vec![("_key".to_owned(), 13.into())]); - let disallowed_item = Item::with_params(12.into(), param_with_disallowed_key); + let param_with_disallowed_key = + Parameters::from_iter(vec![("_key".to_owned(), 13.try_into()?)]); + let disallowed_item = Item::with_params(12.try_into()?, param_with_disallowed_key); assert_eq!( Err("serialize_key: first character is not lcalpha or '*'"), disallowed_item.serialize_value() @@ -81,9 +77,9 @@ fn serialize_value_errors() -> Result<(), Box> { fn serialize_item_byteseq_with_param() -> Result<(), Box> { let mut buf = String::new(); - let item_param = ("a".to_owned(), BareItem::Token("*ab_1".into())); + let item_param = ("a".to_owned(), BareItem::Token("*ab_1".try_into()?)); let item_param = Parameters::from_iter(vec![item_param]); - let item = Item::with_params(BareItem::ByteSeq("parser".as_bytes().to_vec()), item_param); + let item = Item::with_params(BareItem::ByteSeq("parser".as_bytes().into()), item_param); Serializer::serialize_item(&item, &mut buf)?; assert_eq!(":cGFyc2Vy:;a=*ab_1", &buf); Ok(()) @@ -92,7 +88,7 @@ fn serialize_item_byteseq_with_param() -> Result<(), Box> { #[test] fn serialize_item_without_params() -> Result<(), Box> { let mut buf = String::new(); - let item = Item::new(1.into()); + let item = Item::new(1.try_into()?); Serializer::serialize_item(&item, &mut buf)?; assert_eq!("1", &buf); Ok(()) @@ -101,8 +97,8 @@ fn serialize_item_without_params() -> Result<(), Box> { #[test] fn serialize_item_with_bool_true_param() -> Result<(), Box> { let mut buf = String::new(); - let param = Parameters::from_iter(vec![("a".to_owned(), BareItem::Boolean(true))]); - let item = Item::with_params(Decimal::from_str("12.35")?.into(), param); + let param = Parameters::from_iter(vec![("a".to_owned(), BareItem::Boolean(true.into()))]); + let item = Item::with_params(Decimal::from_str("12.35")?.try_into()?, param); Serializer::serialize_item(&item, &mut buf)?; assert_eq!("12.35;a", &buf); Ok(()) @@ -111,8 +107,11 @@ fn serialize_item_with_bool_true_param() -> Result<(), Box> { #[test] fn serialize_item_with_token_param() -> Result<(), Box> { let mut buf = String::new(); - let param = Parameters::from_iter(vec![("a1".to_owned(), BareItem::Token("*tok".to_owned()))]); - let item = Item::with_params(BareItem::String("12.35".to_owned()), param); + let param = Parameters::from_iter(vec![( + "a1".to_owned(), + BareItem::Token("*tok".to_owned().try_into()?), + )]); + let item = Item::with_params(BareItem::String("12.35".to_owned().try_into()?), param); Serializer::serialize_item(&item, &mut buf)?; assert_eq!("\"12.35\";a1=*tok", &buf); Ok(()) @@ -348,8 +347,8 @@ fn serialize_params_bool() -> Result<(), Box> { let mut buf = String::new(); let input = Parameters::from_iter(vec![ - ("*b".to_owned(), BareItem::Boolean(true)), - ("a.a".to_owned(), BareItem::Boolean(true)), + ("*b".to_owned(), BareItem::Boolean(true.into())), + ("a.a".to_owned(), BareItem::Boolean(true.into())), ]); Serializer::serialize_parameters(&input, &mut buf)?; @@ -363,7 +362,7 @@ fn serialize_params_string() -> Result<(), Box> { let input = Parameters::from_iter(vec![( "b".to_owned(), - BareItem::String("param_val".to_owned()), + BareItem::String("param_val".to_owned().try_into()?), )]); Serializer::serialize_parameters(&input, &mut buf)?; assert_eq!(";b=\"param_val\"", &buf); @@ -375,8 +374,8 @@ fn serialize_params_numbers() -> Result<(), Box> { let mut buf = String::new(); let input = Parameters::from_iter(vec![ - ("key1".to_owned(), Decimal::from_str("746.15")?.into()), - ("key2".to_owned(), 11111.into()), + ("key1".to_owned(), Decimal::from_str("746.15")?.try_into()?), + ("key2".to_owned(), 11111.try_into()?), ]); Serializer::serialize_parameters(&input, &mut buf)?; assert_eq!(";key1=746.15;key2=11111", &buf); @@ -388,8 +387,11 @@ fn serialize_params_mixed_types() -> Result<(), Box> { let mut buf = String::new(); let input = Parameters::from_iter(vec![ - ("key1".to_owned(), BareItem::Boolean(false)), - ("key2".to_owned(), Decimal::from_str("1354.091878")?.into()), + ("key1".to_owned(), BareItem::Boolean(false.into())), + ( + "key2".to_owned(), + Decimal::from_str("1354.091878")?.try_into()?, + ), ]); Serializer::serialize_parameters(&input, &mut buf)?; assert_eq!(";key1=?0;key2=1354.092", &buf); @@ -440,13 +442,13 @@ fn serialize_key_erros() -> Result<(), Box> { fn serialize_list_of_items_and_inner_list() -> Result<(), Box> { let mut buf = String::new(); - let item1 = Item::new(12.into()); - let item2 = Item::new(14.into()); - let item3 = Item::new(BareItem::Token("a".to_owned())); - let item4 = Item::new(BareItem::Token("b".to_owned())); + let item1 = Item::new(12.try_into()?); + let item2 = Item::new(14.try_into()?); + let item3 = Item::new(BareItem::Token("a".to_owned().try_into()?)); + let item4 = Item::new(BareItem::Token("b".to_owned().try_into()?)); let inner_list_param = Parameters::from_iter(vec![( "param".to_owned(), - BareItem::String("param_value_1".to_owned()), + BareItem::String("param_value_1".to_owned().try_into()?), )]); let inner_list = InnerList::with_params(vec![item3, item4], inner_list_param); let input: List = vec![item1.into(), item2.into(), inner_list.into()]; @@ -460,10 +462,10 @@ fn serialize_list_of_items_and_inner_list() -> Result<(), Box> { fn serialize_list_of_lists() -> Result<(), Box> { let mut buf = String::new(); - let item1 = Item::new(1.into()); - let item2 = Item::new(2.into()); - let item3 = Item::new(42.into()); - let item4 = Item::new(43.into()); + let item1 = Item::new(1.try_into()?); + let item2 = Item::new(2.try_into()?); + let item3 = Item::new(42.try_into()?); + let item4 = Item::new(43.try_into()?); let inner_list_1 = InnerList::new(vec![item1, item2]); let inner_list_2 = InnerList::new(vec![item3, item4]); let input: List = vec![inner_list_1.into(), inner_list_2.into()]; @@ -478,11 +480,11 @@ fn serialize_list_with_bool_item_and_bool_params() -> Result<(), Box> let mut buf = String::new(); let item1_params = Parameters::from_iter(vec![ - ("a".to_owned(), BareItem::Boolean(true)), - ("b".to_owned(), BareItem::Boolean(false)), + ("a".to_owned(), BareItem::Boolean(true.into())), + ("b".to_owned(), BareItem::Boolean(false.into())), ]); - let item1 = Item::with_params(BareItem::Boolean(false), item1_params); - let item2 = Item::new(BareItem::Token("cde_456".to_owned())); + let item1 = Item::with_params(BareItem::Boolean(false.into()), item1_params); + let item2 = Item::new(BareItem::Token("cde_456".to_owned().try_into()?)); let input: List = vec![item1.into(), item2.into()]; Serializer::serialize_list(&input, &mut buf)?; @@ -495,18 +497,21 @@ fn serialize_dictionary_with_params() -> Result<(), Box> { let mut buf = String::new(); let item1_params = Parameters::from_iter(vec![ - ("a".to_owned(), 1.into()), - ("b".to_owned(), BareItem::Boolean(true)), + ("a".to_owned(), 1.try_into()?), + ("b".to_owned(), BareItem::Boolean(true.into())), ]); let item2_params = Parameters::new(); let item3_params = Parameters::from_iter(vec![ - ("q".to_owned(), BareItem::Boolean(false)), - ("r".to_owned(), BareItem::String("+w".to_owned())), + ("q".to_owned(), BareItem::Boolean(false.into())), + ( + "r".to_owned(), + BareItem::String("+w".to_owned().try_into()?), + ), ]); - let item1 = Item::with_params(123.into(), item1_params); - let item2 = Item::with_params(456.into(), item2_params); - let item3 = Item::with_params(789.into(), item3_params); + let item1 = Item::with_params(123.try_into()?, item1_params); + let item2 = Item::with_params(456.try_into()?, item2_params); + let item3 = Item::with_params(789.try_into()?, item3_params); let input = Dictionary::from_iter(vec![ ("abc".to_owned(), item1.into()), diff --git a/tests/specification_tests.rs b/tests/specification_tests.rs index 4400c02..75a2f30 100644 --- a/tests/specification_tests.rs +++ b/tests/specification_tests.rs @@ -4,7 +4,8 @@ use serde_json::Value; use sfv::FromStr; use sfv::Parser; use sfv::SerializeValue; -use sfv::{BareItem, Decimal, Dictionary, InnerList, Item, List, ListEntry, Parameters}; +use sfv::{BareItem, Dictionary, InnerList, Item, List, ListEntry, Parameters}; +use std::convert::TryInto; use std::error::Error; use std::path::PathBuf; use std::{env, fs}; @@ -89,14 +90,22 @@ fn run_test_case(test_case: &TestData) -> Result<(), Box> { } fn run_test_case_serialization_only(test_case: &TestData) -> Result<(), Box> { - let expected_field_value = build_expected_field_value(test_case)?; - let actual_result = expected_field_value.serialize(); + let expected_field_value = build_expected_field_value(test_case); + // TODO: must_fail has to always fail on creation + // As not all cases are moved yet, we take a two-step approach here: + // Either creation fails or serialization fails if let Some(true) = test_case.must_fail { + if expected_field_value.is_err() { + return Ok(()); + } + let actual_result = expected_field_value?.serialize(); assert!(actual_result.is_err()); return Ok(()); } + let actual_result = expected_field_value?.serialize(); + // Test serialization if let Some(canonical_val) = &test_case.canonical { if canonical_val.is_empty() { @@ -234,31 +243,36 @@ fn build_bare_item(bare_item_value: &Value) -> Result> bare_item if bare_item.is_i64() => Ok(BareItem::Integer( bare_item .as_i64() - .ok_or("build_bare_item: bare_item value is not an i64")?, + .ok_or("build_bare_item: bare_item value is not an i64")? + .try_into()?, )), bare_item if bare_item.is_f64() => { - let decimal = Decimal::from_str(&serde_json::to_string(bare_item)?)?; - Ok(BareItem::Decimal(decimal)) + let decimal = rust_decimal::Decimal::from_str(&serde_json::to_string(bare_item)?)?; + Ok(BareItem::Decimal(decimal.try_into()?)) } bare_item if bare_item.is_boolean() => Ok(BareItem::Boolean( bare_item .as_bool() - .ok_or("build_bare_item: bare_item value is not a bool")?, + .ok_or("build_bare_item: bare_item value is not a bool")? + .into(), )), - bare_item if bare_item.is_string() => Ok(BareItem::String( - bare_item + bare_item if bare_item.is_string() => { + let converted = bare_item .as_str() .ok_or("build_bare_item: bare_item value is not a str")? .clone() - .to_owned(), - )), + .to_owned() + .try_into(); + Ok(BareItem::String(converted?)) + } bare_item if (bare_item.is_object() && bare_item["__type"] == "token") => { Ok(BareItem::Token( bare_item["value"] .as_str() .ok_or("build_bare_item: bare_item value is not a str")? .clone() - .to_owned(), + .to_owned() + .try_into()?, )) } bare_item if (bare_item.is_object() && bare_item["__type"] == "binary") => { @@ -266,7 +280,7 @@ fn build_bare_item(bare_item_value: &Value) -> Result> .as_str() .ok_or("build_bare_item: bare_item value is not a str")? .clone(); - Ok(BareItem::ByteSeq(BASE32.decode(str_val.as_bytes())?)) + Ok(BareItem::ByteSeq(BASE32.decode(str_val.as_bytes())?.into())) } _ => Err("build_bare_item: unknown bare_item value".into()), }