summaryrefslogtreecommitdiffstats
path: root/third_party/rust/sfv/src/lib.rs
blob: f7aebfcdd227da82cd6a5bc807e9e7d4711aaf99 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
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),
        }
    }
}