summaryrefslogtreecommitdiffstats
path: root/third_party/rust/sfv/src/lib.rs
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rust/sfv/src/lib.rs')
-rw-r--r--third_party/rust/sfv/src/lib.rs440
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),
+ }
+ }
+}