/*! `sfv` crate is an implementation of *Structured Field Values for HTTP* as specified in [RFC 8941](https://httpwg.org/specs/rfc8941.html) for parsing and serializing HTTP field values. It also exposes a set of types that might be useful for defining new structured fields. # Data Structures There are three types of structured fields: - `Item` - can be an `Integer`, `Decimal`, `String`, `Token`, `Byte Sequence`, or `Boolean`. It can have associated `Parameters`. - `List` - array of zero or more members, each of which can be an `Item` or an `InnerList`, both of which can be `Parameterized`. - `Dictionary` - ordered map of name-value pairs, where the names are short textual strings and the values are `Items` or arrays of `Items` (represented with `InnerList`), both of which can be `Parameterized`. There can be zero or more members, and their names are unique in the scope of the `Dictionary` they occur within. There's also a few primitive types used to construct structured field values: - `BareItem` used as `Item`'s value or as a parameter value in `Parameters`. - `Parameters` are an ordered map of key-value pairs that are associated with an `Item` or `InnerList`. The keys are unique within the scope the `Parameters` they occur within, and the values are `BareItem`. - `InnerList` is an array of zero or more `Items`. Can have `Parameters`. - `ListEntry` represents either `Item` or `InnerList` as a member of `List` or as member-value in `Dictionary`. # Examples ### Parsing ``` use sfv::Parser; // Parsing structured field value of Item type. let item_header_input = "12.445;foo=bar"; let item = Parser::parse_item(item_header_input.as_bytes()); assert!(item.is_ok()); println!("{:#?}", item); // Parsing structured field value of List type. let list_header_input = "1;a=tok, (\"foo\" \"bar\");baz, ()"; let list = Parser::parse_list(list_header_input.as_bytes()); assert!(list.is_ok()); println!("{:#?}", list); // Parsing structured field value of Dictionary type. let dict_header_input = "a=?0, b, c; foo=bar, rating=1.5, fruits=(apple pear)"; let dict = Parser::parse_dictionary(dict_header_input.as_bytes()); assert!(dict.is_ok()); println!("{:#?}", dict); ``` ### Getting Parsed Value Members ``` use sfv::*; let dict_header = "u=2, n=(* foo 2)"; let dict = Parser::parse_dictionary(dict_header.as_bytes()).unwrap(); // Case 1 - handling value if it's an Item of Integer type let u_val = match dict.get("u") { Some(ListEntry::Item(item)) => item.bare_item.as_int(), _ => None, }; if let Some(u_val) = u_val { println!("{}", u_val); } // Case 2 - matching on all possible types match dict.get("u") { Some(ListEntry::Item(item)) => match &item.bare_item { BareItem::Token(val) => { // do something if it's a Token println!("{}", val); } BareItem::Integer(val) => { // do something if it's an Integer println!("{}", val); } BareItem::Boolean(val) => { // do something if it's a Boolean println!("{}", val); } BareItem::Decimal(val) => { // do something if it's a Decimal println!("{}", val); } BareItem::String(val) => { // do something if it's a String println!("{}", val); } BareItem::ByteSeq(val) => { // do something if it's a ByteSeq println!("{:?}", val); } }, Some(ListEntry::InnerList(inner_list)) => { // do something if it's an InnerList println!("{:?}", inner_list.items); } None => panic!("key not found"), } ``` ### Structured Field Value Construction and Serialization Creates `Item` with empty parameters: ``` use sfv::{Item, BareItem, SerializeValue}; let str_item = Item::new(BareItem::String(String::from("foo"))); assert_eq!(str_item.serialize_value().unwrap(), "\"foo\""); ``` Creates `Item` field value with parameters: ``` use sfv::{Item, BareItem, SerializeValue, Parameters, Decimal, FromPrimitive}; 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); assert_eq!(int_item.serialize_value().unwrap(), "99;key=13.457"); ``` 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()); // Creates Item. let str_item = Item::new(BareItem::String(String::from("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); // Creates InnerList. let mut inner_list_params = Parameters::new(); inner_list_params.insert("bar".into(), BareItem::Boolean(true)); let inner_list = InnerList::with_params(vec![int_item, str_item], inner_list_params); let list: List = vec![Item::new(tok_item).into(), inner_list.into()]; assert_eq!( list.serialize_value().unwrap(), "tok, (99;key=?0 \"foo\");bar" ); ``` 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)); let mut dict = Dictionary::new(); dict.insert("key1".into(), member_value1.into()); dict.insert("key2".into(), member_value2.into()); dict.insert("key3".into(), member_value3.into()); assert_eq!( dict.serialize_value().unwrap(), "key1=\"apple\", key2, key3=?0" ); ``` */ mod parser; mod ref_serializer; mod serializer; mod utils; #[cfg(test)] mod test_parser; #[cfg(test)] mod test_serializer; use indexmap::IndexMap; pub use rust_decimal::{ prelude::{FromPrimitive, FromStr}, Decimal, }; pub use parser::{ParseMore, ParseValue, Parser}; pub use ref_serializer::{RefDictSerializer, RefItemSerializer, RefListSerializer}; pub use serializer::SerializeValue; type SFVResult = std::result::Result; /// Represents `Item` type structured field value. /// Can be used as a member of `List` or `Dictionary`. // sf-item = bare-item parameters // bare-item = sf-integer / sf-decimal / sf-string / sf-token // / sf-binary / sf-boolean #[derive(Debug, PartialEq, Clone)] pub struct Item { /// Value of `Item`. pub bare_item: BareItem, /// `Item`'s associated parameters. Can be empty. pub params: Parameters, } impl Item { /// Returns new `Item` with empty `Parameters`. pub fn new(bare_item: BareItem) -> Item { Item { bare_item, params: Parameters::new(), } } /// Returns new `Item` with specified `Parameters`. pub fn with_params(bare_item: BareItem, params: Parameters) -> Item { Item { bare_item, params } } } /// Represents `Dictionary` type structured field value. // sf-dictionary = dict-member *( OWS "," OWS dict-member ) // dict-member = member-name [ "=" member-value ] // member-name = key // member-value = sf-item / inner-list pub type Dictionary = IndexMap; /// Represents `List` type structured field value. // sf-list = list-member *( OWS "," OWS list-member ) // list-member = sf-item / inner-list pub type List = Vec; /// Parameters of `Item` or `InnerList`. // parameters = *( ";" *SP parameter ) // parameter = param-name [ "=" param-value ] // param-name = key // key = ( lcalpha / "*" ) // *( lcalpha / DIGIT / "_" / "-" / "." / "*" ) // lcalpha = %x61-7A ; a-z // param-value = bare-item pub type Parameters = IndexMap; /// Represents a member of `List` or `Dictionary` structured field value. #[derive(Debug, PartialEq, Clone)] pub enum ListEntry { /// Member of `Item` type. Item(Item), /// Member of `InnerList` (array of `Items`) type. InnerList(InnerList), } impl From for ListEntry { fn from(item: Item) -> Self { ListEntry::Item(item) } } impl From for ListEntry { fn from(item: InnerList) -> Self { ListEntry::InnerList(item) } } /// Array of `Items` with associated `Parameters`. // inner-list = "(" *SP [ sf-item *( 1*SP sf-item ) *SP ] ")" // parameters #[derive(Debug, PartialEq, Clone)] pub struct InnerList { /// `Items` that `InnerList` contains. Can be empty. pub items: Vec, /// `InnerList`'s associated parameters. Can be empty. pub params: Parameters, } impl InnerList { /// Returns new `InnerList` with empty `Parameters`. pub fn new(items: Vec) -> InnerList { InnerList { items, params: Parameters::new(), } } /// Returns new `InnerList` with specified `Parameters`. pub fn with_params(items: Vec, params: Parameters) -> InnerList { InnerList { items, params } } } /// `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), Integer(i64), } /// Similar to `BareItem`, but used to serialize values via `RefItemSerializer`, `RefListSerializer`, `RefDictSerializer`. #[derive(Debug, PartialEq, Clone)] pub enum RefBareItem<'a> { Integer(i64), Decimal(Decimal), String(&'a str), ByteSeq(&'a [u8]), Boolean(bool), Token(&'a str), } 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::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), } } }