diff options
Diffstat (limited to 'third_party/rust/sfv/src/lib.rs')
-rw-r--r-- | third_party/rust/sfv/src/lib.rs | 440 |
1 files changed, 440 insertions, 0 deletions
diff --git a/third_party/rust/sfv/src/lib.rs b/third_party/rust/sfv/src/lib.rs new file mode 100644 index 0000000000..f7aebfcdd2 --- /dev/null +++ b/third_party/rust/sfv/src/lib.rs @@ -0,0 +1,440 @@ +/*! +`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<T> = std::result::Result<T, &'static str>; + +/// 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<String, ListEntry>; + +/// Represents `List` type structured field value. +// sf-list = list-member *( OWS "," OWS list-member ) +// list-member = sf-item / inner-list +pub type List = Vec<ListEntry>; + +/// 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<String, BareItem>; + +/// 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<Item> for ListEntry { + fn from(item: Item) -> Self { + ListEntry::Item(item) + } +} + +impl From<InnerList> 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<Item>, + /// `InnerList`'s associated parameters. Can be empty. + pub params: Parameters, +} + +impl InnerList { + /// Returns new `InnerList` with empty `Parameters`. + pub fn new(items: Vec<Item>) -> InnerList { + InnerList { + items, + params: Parameters::new(), + } + } + + /// Returns new `InnerList` with specified `Parameters`. + pub fn with_params(items: Vec<Item>, 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<u8>), + // 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<Decimal> { + 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<i64> { + 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<u8>`, 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<u8>> { + 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<bool> { + 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<i64> 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<Decimal> 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), + } + } +} |