# ol.sfv RFC 9651 Structured Field Values for HTTP. See the project website for info on motivation and design: <https://github.com/outskirtslabs/sfv> This ns provides parsing and serialization of Structured Fields containing Items, Lists, and Dictionaries with Parameters. Returns precise AST representations that round-trip byte-for-byte with RFC 9651 strings. Primary functions: [`parse`](#parse), [`parse-item`](#parse-item), [`parse-list`](#parse-list), [`parse-dict`](#parse-dict), [`serialize`](#serialize) ## Conventions * `s-or-bytes`: Input string or byte array (decoded as ascii) containing HTTP field value * `field-type`: One of `:item`, `:list`, or `:dict` specifying the top-level structure * `:bare`: The raw value within an Item (Integer, Decimal, String, Token, Byte Sequence, Boolean, Date, or Display String) ## Primitive Types Each Item’s `:bare` is one of these RFC 9651 types: | | | | | | --- | --- | --- | --- | | SFV type | Header | AST example | Clojure type (`:value`) | | Integer | `42`, `-17`, `999999999999999` | `{:type :integer :value 1618884473}` | `long` | | Decimal | `3.14`, `-0.5` | `{:type :decimal :value 3.14M}` | `BigDecimal` | | String | `" hello world "` | `{:type :string :value " hello "}` | `java.lang.String` | | Token | `simple-token` | `{:type :token :value " simple-token "}` | `String` | | Byte Sequence | `:SGVsbG8=:` | `{:type :bytes :value }` | `byte[]` | | Boolean | `?1` / `?0` | `{:type :boolean :value true}` | `true` / `false` | | Date | `@1659578233` | `{:type :date :value 1659578233}` | epoch seconds as `long` | | Display String | `%" Gr%c3%bc%c3%9fe "` | `{:type :dstring :value " Grüße "}` | `String` (percent-decoded, validated) | ## parse ```clojure (parse field-type s-or-bytes) ``` Parse a Structured Field of the given `field-type`. Takes a `field-type` (`:list`, `:dict`, or `:item`) and a string or byte array. Returns an AST representation following RFC 9651. ```clojure (parse :item "42") ;; => {:type :item :bare {:type :integer :value 42} :params []} (parse :dict "max-age=3600, must-revalidate") ;; => {:type :dict :entries [["max-age" {...}] ["must-revalidate" {...}]]} ``` [source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.2.x/src/ol/sfv.clj#L41-L55) --- ## parse-list ```clojure (parse-list s-or-bytes) ``` Parse a Structured Field List from `s-or-bytes`. Returns a List AST with `:type :list` and `:members` vector containing Items and Inner Lists. ```clojure (parse-list "apple, pear;sweet=true, orange") ;; => {:type :list :members [...]} ``` [source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.2.x/src/ol/sfv.clj#L57-L67) --- ## parse-dict ```clojure (parse-dict s-or-bytes) ``` Parse a Structured Field Dictionary from `s-or-bytes`. Returns a Dictionary AST with `:type :dict` and `:entries` as ordered key-value pairs. Each entry maps from a key to either an Item or Inner List. ```clojure (parse-dict "max-age=3600, must-revalidate") ;; => {:type :dict :entries [["max-age" {...}] ["must-revalidate" {...}]]} ``` [source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.2.x/src/ol/sfv.clj#L69-L80) --- ## parse-item ```clojure (parse-item s-or-bytes) ``` Parse a Structured Field Item from `s-or-bytes`. Returns an Item AST with `:type :item`, `:bare` value, and `:params` Parameters. The bare value can be Integer, Decimal, String, Token, Byte Sequence, Boolean, Date, or Display String. ```clojure (parse-item "42") ;; => {:type :item :bare {:type :integer :value 42} :params []} (parse-item "pear;sweet") ;; => {:type :item :bare {:type :token :value "pear"} :params `"sweet" {...}`} ``` [source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.2.x/src/ol/sfv.clj#L82-L96) --- ## serialize ```clojure (serialize x) ``` Serialize a Structured Field AST `x` to its string representation. Takes any parsed AST (Item, List, or Dictionary) and returns the RFC 9651 string. ```clojure (serialize {:type :item :bare {:type :integer :value 42} :params []}) ;; => "42" (serialize-list {:type :list :members [...]}) ;; => "apple, pear;sweet=true, orange" (serialize-dict {:type :dict :entries [["max-age" {...}] ["must-revalidate" {...}]]}) ;; => "max-age=3600, must-revalidate" ``` [source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.2.x/src/ol/sfv.clj#L98-L114) --- ## token ```clojure (token s) ``` [source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.2.x/src/ol/sfv.clj#L117-L118) --- ## token? ```clojure (token? x) ``` [source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.2.x/src/ol/sfv.clj#L120-L121) --- ## decimal ```clojure (decimal x) ``` [source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.2.x/src/ol/sfv.clj#L123-L124) --- ## decimal? ```clojure (decimal? x) ``` [source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.2.x/src/ol/sfv.clj#L125-L126) --- ## integer ```clojure (integer n) ``` [source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.2.x/src/ol/sfv.clj#L128-L129) --- ## integer? ```clojure (integer? x) ``` [source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.2.x/src/ol/sfv.clj#L131-L132) --- ## string ```clojure (string s) ``` [source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.2.x/src/ol/sfv.clj#L134-L135) --- ## string? ```clojure (string? x) ``` [source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.2.x/src/ol/sfv.clj#L137-L138) --- ## dstring ```clojure (dstring s) ``` [source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.2.x/src/ol/sfv.clj#L140-L141) --- ## dstring? ```clojure (dstring? x) ``` [source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.2.x/src/ol/sfv.clj#L143-L144) --- ## bytes ```clojure (bytes b) ``` [source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.2.x/src/ol/sfv.clj#L146-L147) --- ## bytes? ```clojure (bytes? x) ``` [source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.2.x/src/ol/sfv.clj#L149-L150) --- ## bool ```clojure (bool b) ``` [source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.2.x/src/ol/sfv.clj#L152-L153) --- ## bool? ```clojure (bool? x) ``` [source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.2.x/src/ol/sfv.clj#L155-L156) --- ## date ```clojure (date seconds) ``` [source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.2.x/src/ol/sfv.clj#L158-L159) --- ## date? ```clojure (date? x) ``` [source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.2.x/src/ol/sfv.clj#L161-L162) --- ## params ```clojure (params & kvs) ``` [source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.2.x/src/ol/sfv.clj#L165-L166) --- ## param-get ```clojure (param-get ps k) ``` [source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.2.x/src/ol/sfv.clj#L168-L169) --- ## param-keys ```clojure (param-keys ps) ``` [source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.2.x/src/ol/sfv.clj#L171-L172) --- ## item ```clojure (item bare) (item bare ps) ``` [source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.2.x/src/ol/sfv.clj#L175-L179) --- ## item? ```clojure (item? x) ``` [source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.2.x/src/ol/sfv.clj#L181-L182) --- ## item-bare ```clojure (item-bare i) ``` [source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.2.x/src/ol/sfv.clj#L184-L185) --- ## item-params ```clojure (item-params i) ``` [source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.2.x/src/ol/sfv.clj#L187-L188) --- ## inner-list ```clojure (inner-list items) (inner-list items ps) ``` [source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.2.x/src/ol/sfv.clj#L191-L195) --- ## inner-list? ```clojure (inner-list? x) ``` [source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.2.x/src/ol/sfv.clj#L197-L198) --- ## inner-items ```clojure (inner-items il) ``` [source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.2.x/src/ol/sfv.clj#L200-L201) --- ## inner-params ```clojure (inner-params il) ``` [source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.2.x/src/ol/sfv.clj#L203-L204) --- ## sf-list ```clojure (sf-list members) ``` [source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.2.x/src/ol/sfv.clj#L207-L208) --- ## sf-list? ```clojure (sf-list? x) ``` [source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.2.x/src/ol/sfv.clj#L210-L211) --- ## list-members ```clojure (list-members l) ``` [source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.2.x/src/ol/sfv.clj#L213-L214) --- ## sf-dict ```clojure (sf-dict entries) ``` [source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.2.x/src/ol/sfv.clj#L217-L218) --- ## sf-dict? ```clojure (sf-dict? x) ``` [source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.2.x/src/ol/sfv.clj#L220-L221) --- ## dict-keys ```clojure (dict-keys d) ``` [source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.2.x/src/ol/sfv.clj#L223-L224) --- ## dict-get ```clojure (dict-get d k) ``` [source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.2.x/src/ol/sfv.clj#L226-L227) --- ## dict->pairs ```clojure (dict->pairs d) ``` [source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.2.x/src/ol/sfv.clj#L229-L230) --- ## flag ```clojure (flag) (flag ps) ``` [source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.2.x/src/ol/sfv.clj#L233-L237) --- ## flag? ```clojure (flag? x) ``` [source,window=_blank](https://github.com/outskirtslabs/sfv/blob/v0.2.x/src/ol/sfv.clj#L239-L240)