Skip to main content

sfv/
lib.rs

1/*!
2`sfv` is an implementation of *Structured Field Values for HTTP*, as specified in [RFC 9651](https://httpwg.org/specs/rfc9651.html) for parsing and serializing HTTP field values.
3It also exposes a set of types that might be useful for defining new structured fields.
4
5# Data Structures
6
7There are three types of structured fields:
8
9- `Item` -- an `Integer`, `Decimal`, `String`, `Token`, `Byte Sequence`, `Boolean`, `Date`, or `Display String`. It can have associated `Parameters`.
10- `List` -- an array of zero or more members, each of which can be an `Item` or an `InnerList`, both of which can have `Parameters`.
11- `Dictionary` -- an ordered map of name-value pairs, where the names are short textual strings and the values are `Item`s or arrays of `Items` (represented with `InnerList`), both of which can have associated parameters. There can be zero or more members, and their names are unique in the scope of the `Dictionary` they occur within.
12
13There are also a few lower-level types used to construct structured field values:
14- `BareItem` is used as `Item`'s value or as a parameter value in `Parameters`.
15- `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`.
16- `InnerList` is an array of zero or more `Items`. Can have associated `Parameters`.
17- `ListEntry` represents either `Item` or `InnerList` as a member of `List` or as member-value in `Dictionary`.
18
19# Examples
20
21*/
22#![cfg_attr(
23    feature = "parsed-types",
24    doc = r##"
25### Parsing
26
27```
28# use sfv::{Dictionary, Item, List, Parser};
29# fn main() -> Result<(), sfv::Error> {
30// Parsing a structured field value of Item type.
31let input = "12.445;foo=bar";
32let item: Item = Parser::new(input).parse()?;
33println!("{:#?}", item);
34
35// Parsing a structured field value of List type.
36let input = r#"1;a=tok, ("foo" "bar");baz, ()"#;
37let list: List = Parser::new(input).parse()?;
38println!("{:#?}", list);
39
40// Parsing a structured field value of Dictionary type.
41let input = "a=?0, b, c; foo=bar, rating=1.5, fruits=(apple pear)";
42let dict: Dictionary = Parser::new(input).parse()?;
43println!("{:#?}", dict);
44# Ok(())
45# }
46```
47
48### Getting Parsed Value Members
49```
50# use sfv::*;
51# fn main() -> Result<(), sfv::Error> {
52let input = "u=2, n=(* foo 2)";
53let dict: Dictionary = Parser::new(input).parse()?;
54
55match dict.get("u") {
56    Some(ListEntry::Item(item)) => match &item.bare_item {
57        BareItem::Token(val) => { /* ... */ }
58        BareItem::Integer(val) => { /* ... */ }
59        BareItem::Boolean(val) => { /* ... */ }
60        BareItem::Decimal(val) => { /* ... */ }
61        BareItem::String(val) => { /* ... */ }
62        BareItem::ByteSequence(val) => { /* ... */ }
63        BareItem::Date(val) => { /* ... */ }
64        BareItem::DisplayString(val) => { /* ... */ }
65    },
66    Some(ListEntry::InnerList(inner_list)) => { /* ... */ }
67    None => { /* ... */ }
68}
69# Ok(())
70# }
71```
72"##
73)]
74/*!
75### Serialization
76Serializes an `Item`:
77```
78use sfv::{Decimal, ItemSerializer, KeyRef, StringRef};
79
80# fn main() -> Result<(), sfv::Error> {
81let serialized_item = ItemSerializer::new()
82    .bare_item(StringRef::from_str("foo")?)
83    .parameter(KeyRef::from_str("key")?, Decimal::try_from(13.45655)?)
84    .finish();
85
86assert_eq!(serialized_item, r#""foo";key=13.457"#);
87# Ok(())
88# }
89```
90
91Serializes a `List`:
92```
93use sfv::{KeyRef, ListSerializer, StringRef, TokenRef};
94
95# fn main() -> Result<(), sfv::Error> {
96let mut ser = ListSerializer::new();
97
98ser.bare_item(TokenRef::from_str("tok")?);
99
100{
101    let mut ser = ser.inner_list();
102
103    ser.bare_item(99).parameter(KeyRef::from_str("key")?, false);
104
105    ser.bare_item(StringRef::from_str("foo")?);
106
107    ser.finish().parameter(KeyRef::from_str("bar")?, true);
108}
109
110assert_eq!(
111    ser.finish().as_deref(),
112    Some(r#"tok, (99;key=?0 "foo");bar"#),
113);
114# Ok(())
115# }
116```
117
118Serializes a `Dictionary`:
119```
120use sfv::{DictSerializer, KeyRef, StringRef};
121
122# fn main() -> Result<(), sfv::Error> {
123let mut ser = DictSerializer::new();
124
125ser.bare_item(KeyRef::from_str("key1")?, StringRef::from_str("apple")?);
126
127ser.bare_item(KeyRef::from_str("key2")?, true);
128
129ser.bare_item(KeyRef::from_str("key3")?, false);
130
131assert_eq!(
132    ser.finish().as_deref(),
133    Some(r#"key1="apple", key2, key3=?0"#),
134);
135# Ok(())
136# }
137```
138
139# Crate features
140
141- `parsed-types` (enabled by default) -- When enabled, exposes fully owned types
142  [`Item`], [`Dictionary`], [`List`], and their components, which can be obtained from
143  [`Parser::parse`], etc. These types are implemented using the
144  [`indexmap`](https://crates.io/crates/indexmap) crate, so disabling this
145  feature can avoid that dependency if parsing using a visitor
146  ([`Parser::parse_item_with_visitor`], etc.) is sufficient.
147
148- `arbitrary` -- Implements the
149  [`Arbitrary`](https://docs.rs/arbitrary/1.4.1/arbitrary/trait.Arbitrary.html)
150  trait for this crate's types, making them easier to use with fuzzing.
151*/
152
153#![deny(missing_docs)]
154#![deny(missing_debug_implementations)]
155
156mod date;
157mod decimal;
158mod error;
159mod integer;
160mod key;
161#[cfg(feature = "parsed-types")]
162mod parsed;
163mod parser;
164mod ref_serializer;
165mod serializer;
166mod string;
167mod token;
168mod utils;
169pub mod visitor;
170
171#[cfg(test)]
172mod test_decimal;
173#[cfg(test)]
174mod test_integer;
175#[cfg(test)]
176mod test_key;
177#[cfg(test)]
178mod test_parser;
179#[cfg(test)]
180mod test_ref_serializer;
181#[cfg(test)]
182mod test_serializer;
183#[cfg(test)]
184mod test_string;
185#[cfg(test)]
186mod test_token;
187
188use std::borrow::{Borrow, Cow};
189use std::fmt;
190use std::string::String as StdString;
191
192pub use date::Date;
193pub use decimal::Decimal;
194pub use error::Error;
195pub use integer::{integer, Integer};
196pub use key::{key_ref, Key, KeyRef};
197#[cfg(feature = "parsed-types")]
198pub use parsed::{Dictionary, FieldType, InnerList, Item, List, ListEntry, Parameters};
199pub use parser::Parser;
200pub use ref_serializer::{
201    DictSerializer, InnerListSerializer, ItemSerializer, ListSerializer, ParameterSerializer,
202};
203pub use string::{string_ref, String, StringRef};
204pub use token::{token_ref, Token, TokenRef};
205
206type SFVResult<T> = std::result::Result<T, Error>;
207
208/// An abstraction over multiple kinds of ownership of a [bare item].
209///
210/// In general most users will be interested in:
211/// - [`BareItem`], for completely owned data
212/// - [`RefBareItem`], for completely borrowed data
213/// - [`BareItemFromInput`], for data borrowed from input when possible
214///
215/// [bare item]: <https://httpwg.org/specs/rfc9651.html#item>
216#[derive(Debug, Clone, Copy)]
217#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
218pub enum GenericBareItem<S, B, T, D> {
219    /// A [decimal](https://httpwg.org/specs/rfc9651.html#decimal).
220    // sf-decimal  = ["-"] 1*12DIGIT "." 1*3DIGIT
221    Decimal(Decimal),
222    /// An [integer](https://httpwg.org/specs/rfc9651.html#integer).
223    // sf-integer = ["-"] 1*15DIGIT
224    Integer(Integer),
225    /// A [string](https://httpwg.org/specs/rfc9651.html#string).
226    // sf-string = DQUOTE *chr DQUOTE
227    // chr       = unescaped / escaped
228    // unescaped = %x20-21 / %x23-5B / %x5D-7E
229    // escaped   = "\" ( DQUOTE / "\" )
230    String(S),
231    /// A [byte sequence](https://httpwg.org/specs/rfc9651.html#binary).
232    // ":" *(base64) ":"
233    // base64    = ALPHA / DIGIT / "+" / "/" / "="
234    ByteSequence(B),
235    /// A [boolean](https://httpwg.org/specs/rfc9651.html#boolean).
236    // sf-boolean = "?" boolean
237    // boolean    = "0" / "1"
238    Boolean(bool),
239    /// A [token](https://httpwg.org/specs/rfc9651.html#token).
240    // sf-token = ( ALPHA / "*" ) *( tchar / ":" / "/" )
241    Token(T),
242    /// A [date](https://httpwg.org/specs/rfc9651.html#date).
243    ///
244    /// [`Parser`] will never produce this variant when used with
245    /// [`Version::Rfc8941`].
246    // sf-date = "@" sf-integer
247    Date(Date),
248    /// A [display string](https://httpwg.org/specs/rfc9651.html#displaystring).
249    ///
250    /// Display Strings are similar to [`String`]s, in that they consist of zero
251    /// or more characters, but they allow Unicode scalar values (i.e., all
252    /// Unicode code points except for surrogates), unlike [`String`]s.
253    ///
254    /// [`Parser`] will never produce this variant when used with
255    /// [`Version::Rfc8941`].
256    ///
257    /// [display string]: <https://httpwg.org/specs/rfc9651.html#displaystring>
258    // sf-displaystring = "%" DQUOTE *( unescaped / "\" / pct-encoded ) DQUOTE
259    // pct-encoded      = "%" lc-hexdig lc-hexdig
260    // lc-hexdig        = DIGIT / %x61-66 ; 0-9, a-f
261    DisplayString(D),
262}
263
264impl<S, B, T, D> GenericBareItem<S, B, T, D> {
265    /// If the bare item is a decimal, returns it; otherwise returns `None`.
266    #[must_use]
267    pub fn as_decimal(&self) -> Option<Decimal> {
268        match *self {
269            Self::Decimal(val) => Some(val),
270            _ => None,
271        }
272    }
273
274    /// If the bare item is an integer, returns it; otherwise returns `None`.
275    #[must_use]
276    pub fn as_integer(&self) -> Option<Integer> {
277        match *self {
278            Self::Integer(val) => Some(val),
279            _ => None,
280        }
281    }
282
283    /// If the bare item is a string, returns a reference to it; otherwise returns `None`.
284    #[must_use]
285    pub fn as_string(&self) -> Option<&StringRef>
286    where
287        S: Borrow<StringRef>,
288    {
289        match *self {
290            Self::String(ref val) => Some(val.borrow()),
291            _ => None,
292        }
293    }
294
295    /// If the bare item is a byte sequence, returns a reference to it; otherwise returns `None`.
296    #[must_use]
297    pub fn as_byte_sequence(&self) -> Option<&[u8]>
298    where
299        B: Borrow<[u8]>,
300    {
301        match *self {
302            Self::ByteSequence(ref val) => Some(val.borrow()),
303            _ => None,
304        }
305    }
306
307    /// If the bare item is a boolean, returns it; otherwise returns `None`.
308    #[must_use]
309    pub fn as_boolean(&self) -> Option<bool> {
310        match *self {
311            Self::Boolean(val) => Some(val),
312            _ => None,
313        }
314    }
315
316    /// If the bare item is a token, returns a reference to it; otherwise returns `None`.
317    #[must_use]
318    pub fn as_token(&self) -> Option<&TokenRef>
319    where
320        T: Borrow<TokenRef>,
321    {
322        match *self {
323            Self::Token(ref val) => Some(val.borrow()),
324            _ => None,
325        }
326    }
327
328    /// If the bare item is a date, returns it; otherwise returns `None`.
329    #[must_use]
330    pub fn as_date(&self) -> Option<Date> {
331        match *self {
332            Self::Date(val) => Some(val),
333            _ => None,
334        }
335    }
336
337    /// If the bare item is a display string, returns a reference to it; otherwise returns `None`.
338    #[must_use]
339    pub fn as_display_string(&self) -> Option<&D> {
340        match *self {
341            Self::DisplayString(ref val) => Some(val),
342            _ => None,
343        }
344    }
345}
346
347impl<S, B, T, D> From<Integer> for GenericBareItem<S, B, T, D> {
348    fn from(val: Integer) -> Self {
349        Self::Integer(val)
350    }
351}
352
353impl<S, B, T, D> From<bool> for GenericBareItem<S, B, T, D> {
354    fn from(val: bool) -> Self {
355        Self::Boolean(val)
356    }
357}
358
359impl<S, B, T, D> From<Decimal> for GenericBareItem<S, B, T, D> {
360    fn from(val: Decimal) -> Self {
361        Self::Decimal(val)
362    }
363}
364
365impl<S, B, T, D> From<Date> for GenericBareItem<S, B, T, D> {
366    fn from(val: Date) -> Self {
367        Self::Date(val)
368    }
369}
370
371impl<S, B, T, D> TryFrom<f32> for GenericBareItem<S, B, T, D> {
372    type Error = Error;
373
374    fn try_from(val: f32) -> Result<Self, Error> {
375        Decimal::try_from(val).map(Self::Decimal)
376    }
377}
378
379impl<S, B, T, D> TryFrom<f64> for GenericBareItem<S, B, T, D> {
380    type Error = Error;
381
382    fn try_from(val: f64) -> Result<Self, Error> {
383        Decimal::try_from(val).map(Self::Decimal)
384    }
385}
386
387impl<S, T, D> From<Vec<u8>> for GenericBareItem<S, Vec<u8>, T, D> {
388    fn from(val: Vec<u8>) -> Self {
389        Self::ByteSequence(val)
390    }
391}
392
393impl<S, B, D> From<Token> for GenericBareItem<S, B, Token, D> {
394    fn from(val: Token) -> Self {
395        Self::Token(val)
396    }
397}
398
399impl<B, T, D> From<String> for GenericBareItem<String, B, T, D> {
400    fn from(val: String) -> Self {
401        Self::String(val)
402    }
403}
404
405impl<'a, S, T, D> From<&'a [u8]> for GenericBareItem<S, Vec<u8>, T, D> {
406    fn from(val: &'a [u8]) -> Self {
407        Self::ByteSequence(val.to_owned())
408    }
409}
410
411impl<'a, S, B, D> From<&'a TokenRef> for GenericBareItem<S, B, Token, D> {
412    fn from(val: &'a TokenRef) -> Self {
413        Self::Token(val.to_owned())
414    }
415}
416
417impl<'a, B, T, D> From<&'a StringRef> for GenericBareItem<String, B, T, D> {
418    fn from(val: &'a StringRef) -> Self {
419        Self::String(val.to_owned())
420    }
421}
422
423#[derive(Debug, PartialEq)]
424pub(crate) enum Num {
425    Decimal(Decimal),
426    Integer(Integer),
427}
428
429/// A [bare item] that owns its data.
430///
431/// [bare item]: <https://httpwg.org/specs/rfc9651.html#item>
432#[cfg_attr(
433    feature = "parsed-types",
434    doc = "Used to construct an [`Item`] or [`Parameters`] values."
435)]
436///
437/// Note: This type deliberately does not implement `From<StdString>` as a
438/// shorthand for [`BareItem::DisplayString`] because it is too easy to confuse
439/// with conversions from [`String`]:
440///
441/// ```compile_fail
442/// # use sfv::BareItem;
443/// let _: BareItem = "x".to_owned().into();
444/// ```
445///
446/// Instead, use:
447///
448/// ```
449/// # use sfv::BareItem;
450/// let _ = BareItem::DisplayString("x".to_owned());
451/// ```
452pub type BareItem = GenericBareItem<String, Vec<u8>, Token, StdString>;
453
454/// A [bare item] that borrows its data.
455///
456/// Used to serialize values via [`ItemSerializer`], [`ListSerializer`], and [`DictSerializer`].
457///
458/// [bare item]: <https://httpwg.org/specs/rfc9651.html#item>
459///
460/// Note: This type deliberately does not implement `From<&str>` as a shorthand
461/// for [`RefBareItem::DisplayString`] because it is too easy to confuse with
462/// conversions from [`StringRef`]:
463///
464/// ```compile_fail
465/// # use sfv::RefBareItem;
466/// let _: RefBareItem = "x".into();
467/// ```
468///
469/// Instead, use:
470///
471/// ```
472/// # use sfv::RefBareItem;
473/// let _ = RefBareItem::DisplayString("x");
474/// ```
475pub type RefBareItem<'a> = GenericBareItem<&'a StringRef, &'a [u8], &'a TokenRef, &'a str>;
476
477/// A [bare item] that borrows data from input when possible.
478///
479/// Used to parse input incrementally in the [`visitor`] module.
480///
481/// [bare item]: <https://httpwg.org/specs/rfc9651.html#item>
482///
483/// Note: This type deliberately does not implement `From<Cow<str>>` as a
484/// shorthand for [`BareItemFromInput::DisplayString`] because it is too easy to
485/// confuse with conversions from [`Cow<StringRef>`]:
486///
487/// ```compile_fail
488/// # use sfv::BareItemFromInput;
489/// # use std::borrow::Cow;
490/// let _: BareItemFromInput = "x".to_owned().into();
491/// ```
492///
493/// Instead, use:
494///
495/// ```
496/// # use sfv::BareItemFromInput;
497/// # use std::borrow::Cow;
498/// let _ = BareItemFromInput::DisplayString(Cow::Borrowed("x"));
499/// ```
500pub type BareItemFromInput<'a> =
501    GenericBareItem<Cow<'a, StringRef>, Vec<u8>, &'a TokenRef, Cow<'a, str>>;
502
503impl<'a, S, B, T, D> From<&'a GenericBareItem<S, B, T, D>> for RefBareItem<'a>
504where
505    S: Borrow<StringRef>,
506    B: Borrow<[u8]>,
507    T: Borrow<TokenRef>,
508    D: Borrow<str>,
509{
510    fn from(val: &'a GenericBareItem<S, B, T, D>) -> RefBareItem<'a> {
511        match val {
512            GenericBareItem::Integer(val) => RefBareItem::Integer(*val),
513            GenericBareItem::Decimal(val) => RefBareItem::Decimal(*val),
514            GenericBareItem::String(val) => RefBareItem::String(val.borrow()),
515            GenericBareItem::ByteSequence(val) => RefBareItem::ByteSequence(val.borrow()),
516            GenericBareItem::Boolean(val) => RefBareItem::Boolean(*val),
517            GenericBareItem::Token(val) => RefBareItem::Token(val.borrow()),
518            GenericBareItem::Date(val) => RefBareItem::Date(*val),
519            GenericBareItem::DisplayString(val) => RefBareItem::DisplayString(val.borrow()),
520        }
521    }
522}
523
524impl<'a> From<BareItemFromInput<'a>> for BareItem {
525    fn from(val: BareItemFromInput<'a>) -> BareItem {
526        match val {
527            BareItemFromInput::Integer(val) => BareItem::Integer(val),
528            BareItemFromInput::Decimal(val) => BareItem::Decimal(val),
529            BareItemFromInput::String(val) => BareItem::String(val.into_owned()),
530            BareItemFromInput::ByteSequence(val) => BareItem::ByteSequence(val),
531            BareItemFromInput::Boolean(val) => BareItem::Boolean(val),
532            BareItemFromInput::Token(val) => BareItem::Token(val.to_owned()),
533            BareItemFromInput::Date(val) => BareItem::Date(val),
534            BareItemFromInput::DisplayString(val) => BareItem::DisplayString(val.into_owned()),
535        }
536    }
537}
538
539impl<'a> From<RefBareItem<'a>> for BareItem {
540    fn from(val: RefBareItem<'a>) -> BareItem {
541        match val {
542            RefBareItem::Integer(val) => BareItem::Integer(val),
543            RefBareItem::Decimal(val) => BareItem::Decimal(val),
544            RefBareItem::String(val) => BareItem::String(val.to_owned()),
545            RefBareItem::ByteSequence(val) => BareItem::ByteSequence(val.to_owned()),
546            RefBareItem::Boolean(val) => BareItem::Boolean(val),
547            RefBareItem::Token(val) => BareItem::Token(val.to_owned()),
548            RefBareItem::Date(val) => BareItem::Date(val),
549            RefBareItem::DisplayString(val) => BareItem::DisplayString(val.to_owned()),
550        }
551    }
552}
553
554impl<'a, S, T, D> From<&'a [u8]> for GenericBareItem<S, &'a [u8], T, D> {
555    fn from(val: &'a [u8]) -> Self {
556        Self::ByteSequence(val)
557    }
558}
559
560impl<'a, S, B, D> From<&'a Token> for GenericBareItem<S, B, &'a TokenRef, D> {
561    fn from(val: &'a Token) -> Self {
562        Self::Token(val)
563    }
564}
565
566impl<'a, S, B, D> From<&'a TokenRef> for GenericBareItem<S, B, &'a TokenRef, D> {
567    fn from(val: &'a TokenRef) -> Self {
568        Self::Token(val)
569    }
570}
571
572impl<'a, B, T, D> From<&'a String> for GenericBareItem<&'a StringRef, B, T, D> {
573    fn from(val: &'a String) -> Self {
574        Self::String(val)
575    }
576}
577
578impl<'a, B, T, D> From<&'a StringRef> for GenericBareItem<&'a StringRef, B, T, D> {
579    fn from(val: &'a StringRef) -> Self {
580        Self::String(val)
581    }
582}
583
584impl<S1, B1, T1, D1, S2, B2, T2, D2> PartialEq<GenericBareItem<S2, B2, T2, D2>>
585    for GenericBareItem<S1, B1, T1, D1>
586where
587    for<'a> RefBareItem<'a>: From<&'a Self>,
588    for<'a> RefBareItem<'a>: From<&'a GenericBareItem<S2, B2, T2, D2>>,
589{
590    fn eq(&self, other: &GenericBareItem<S2, B2, T2, D2>) -> bool {
591        match (RefBareItem::from(self), RefBareItem::from(other)) {
592            (RefBareItem::Integer(a), RefBareItem::Integer(b)) => a == b,
593            (RefBareItem::Decimal(a), RefBareItem::Decimal(b)) => a == b,
594            (RefBareItem::String(a), RefBareItem::String(b)) => a == b,
595            (RefBareItem::ByteSequence(a), RefBareItem::ByteSequence(b)) => a == b,
596            (RefBareItem::Boolean(a), RefBareItem::Boolean(b)) => a == b,
597            (RefBareItem::Token(a), RefBareItem::Token(b)) => a == b,
598            (RefBareItem::Date(a), RefBareItem::Date(b)) => a == b,
599            (RefBareItem::DisplayString(a), RefBareItem::DisplayString(b)) => a == b,
600            _ => false,
601        }
602    }
603}
604
605/// A version for serialized structured field values.
606///
607/// Each HTTP specification that uses structured field values must indicate
608/// which version it uses. See [the guidance from RFC 9651][RFC 9651] for details.
609///
610/// [RFC 9651]: <https://httpwg.org/specs/rfc9651.html#using-new-structured-types-in-extensions>
611#[derive(Clone, Copy, Debug, PartialEq, Eq)]
612#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
613pub enum Version {
614    /// [RFC 8941], which does not support dates or display strings.
615    ///
616    /// [RFC 8941]: <https://httpwg.org/specs/rfc8941.html>
617    Rfc8941,
618    /// [RFC 9651], which supports dates and display strings.
619    ///
620    /// [RFC 9651]: <https://httpwg.org/specs/rfc9651.html>
621    Rfc9651,
622}
623
624impl fmt::Display for Version {
625    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
626        f.write_str(match self {
627            Self::Rfc8941 => "RFC 8941",
628            Self::Rfc9651 => "RFC 9651",
629        })
630    }
631}
632
633mod private {
634    #[allow(unused)]
635    pub trait Sealed {}
636}