HTTP Structured Field parser implementation following RFC 8941.
This module exposes high-level helpers to parse the three Structured Field top-level types defined by the RFC:
- items with
parse_item/2(a single bare item with optional parameters) - lists with
parse_list/2(a comma separated sequence of items or inner lists) - dictionaries with
parse_dict/2(a comma separated sequence of key/item pairs, where a bare key implies atrueboolean)
Returned data is, by default, tagged with the parsed value type. You can opt into two orthogonal transformations using options:
unwrap: true- remove the type tag wrapper from items and attributesmaps: true- turn attribute collections and dictionaries into maps
Shapes
By default (no options):
- item:
{type, value, attributes} - attribute:
{key, {type, value}} - inner list:
{:inner_list, [item, ...], attributes} - dictionary: list of
{key, item}tuples
With unwrap: true:
- item:
{value, attributes}(type tag removed) - attribute:
{key, value} - inner list:
{[unwrapped_item, ...], attributes}
With maps: true:
- attribute collections (item / inner list parameters) become a
%{key => attr}map - dictionary becomes a
%{key => item}map
Both options compose: unwrap: true, maps: true yields unwrapped values and
maps for every attribute / dictionary collection.
Error handling
On invalid input an {:error, {reason, remainder}} tuple is returned:
iex> Texture.HttpStructuredField.parse_item("not@@valid")
{:error, {:invalid_value, "not@@valid"}}
Summary
Functions
Parses a structured field dictionary, a comma separated sequence of key/item
pairs. A bare key implies a true boolean value.
Parses a structured field item, a single bare value with optional parameters.
Parses a structured field list, a comma separated sequence of items or inner lists.
Applies the :unwrap and :maps transformations to a dictionary already
parsed with the default options.
Applies the :unwrap and :maps transformations to an item already parsed
with the default options.
Applies the :unwrap and :maps transformations to each item of a list
already parsed with the default options.
Types
@type attribute() :: wrapped_attribute() | unwrapped_attribute()
@type attrs() :: Enumerable.t(attribute())
@type item() :: wrapped_item() | unwrapped_item()
@type tag() ::
:integer
| :decimal
| :string
| :token
| :byte_sequence
| :boolean
| :inner_list
@type value() :: term()
Functions
@spec parse_dict(binary(), [option()]) :: {:ok, Enumerable.t({binary(), item()})} | {:error, term()}
Parses a structured field dictionary, a comma separated sequence of key/item
pairs. A bare key implies a true boolean value.
Returns {:ok, pairs} where pairs is a list of {key, item} tuples (or a
map with the :maps option), or {:error, reason} on invalid input. Accepts
the same options as parse_item/2. See the module documentation for the
returned shapes.
Examples
Example with explicit and implicit boolean members plus inner list:
iex> Texture.HttpStructuredField.parse_dict("foo=123, bar, baz=\"hi\";a=1;b=2, qux=(1 2);p")
{:ok,
[
{"foo", {:integer, 123, []}},
{"bar", {:boolean, true, []}},
{"baz", {:string, "hi", [{"a", {:integer, 1}}, {"b", {:integer, 2}}]}},
{"qux",
{:inner_list, [{:integer, 1, []}, {:integer, 2, []}],
[{"p", {:boolean, true}}]}}
]}Unwrapped:
iex> Texture.HttpStructuredField.parse_dict("foo=123, bar, baz=\"hi\";a=1;b=2, qux=(1 2);p", unwrap: true)
{:ok,
[
{"foo", {123, []}},
{"bar", {true, []}},
{"baz", {"hi", [{"a", 1}, {"b", 2}]}},
{"qux", {[{1, []}, {2, []}], [{"p", true}]}}
]}As a map (still wrapped):
iex> Texture.HttpStructuredField.parse_dict("foo=123, bar, baz=\"hi\";a=1;b=2, qux=(1 2);p", maps: true)
{:ok,
%{
"bar" => {:boolean, true, %{}},
"baz" => {:string, "hi", %{"a" => {:integer, 1}, "b" => {:integer, 2}}},
"foo" => {:integer, 123, %{}},
"qux" => {:inner_list, [{:integer, 1, %{}}, {:integer, 2, %{}}],
%{"p" => {:boolean, true}}}
}}Maps + Unwrapped:
iex> Texture.HttpStructuredField.parse_dict("foo=123, bar, baz=\"hi\";a=1;b=2, qux=(1 2);p", unwrap: true, maps: true)
{:ok,
%{
"bar" => {true, %{}},
"baz" => {"hi", %{"a" => 1, "b" => 2}},
"foo" => {123, %{}},
"qux" => {[{1, %{}}, {2, %{}}], %{"p" => true}}
}}
Parses a structured field item, a single bare value with optional parameters.
Returns {:ok, item} where the item is a {type, value, params} tuple, or
{:error, reason} on invalid input. See the module documentation for the
returned shapes.
Options
:unwrap- whentrue, removes the type tag from values and parameters.:maps- whentrue, collects parameters into a map.
Examples
An item with no parameters:
iex> Texture.HttpStructuredField.parse_item("123")
{:ok, {:integer, 123, []}}Item with boolean (implicit) and integer parameters:
iex> Texture.HttpStructuredField.parse_item("123;a;b=5")
{:ok, {:integer, 123, [{"a", {:boolean, true}}, {"b", {:integer, 5}}]}}Unwrapped (type tags removed):
iex> Texture.HttpStructuredField.parse_item("123;a;b=5", unwrap: true)
{:ok, {123, [{"a", true}, {"b", 5}]}}Attributes as a map (still wrapped):
iex> Texture.HttpStructuredField.parse_item("123;a;b=5", maps: true)
{:ok, {:integer, 123, %{"a" => {:boolean, true}, "b" => {:integer, 5}}}}Both together (unwrapped values and attribute map):
iex> Texture.HttpStructuredField.parse_item("123;a;b=5", unwrap: true, maps: true)
{:ok, {123, %{"a" => true, "b" => 5}}}
Parses a structured field list, a comma separated sequence of items or inner lists.
Returns {:ok, items} or {:error, reason} on invalid input. Accepts the
same options as parse_item/2. See the module documentation for the returned
shapes.
Examples
A list can contain bare items and inner lists:
iex> Texture.HttpStructuredField.parse_list("123, \"hi\";a=1, (1 2 3);p")
{:ok,
[
{:integer, 123, []},
{:string, "hi", [{"a", {:integer, 1}}]},
{:inner_list,
[{:integer, 1, []}, {:integer, 2, []}, {:integer, 3, []}],
[{"p", {:boolean, true}}]}
]}Unwrapping removes all type tags recursively:
iex> Texture.HttpStructuredField.parse_list("123, \"hi\";a=1, (1 2 3);p", unwrap: true)
{:ok,
[
{123, []},
{"hi", [{"a", 1}]},
{[{1, []}, {2, []}, {3, []}], [{"p", true}]}
]}Using maps for attributes (note inner list parameter map):
iex> Texture.HttpStructuredField.parse_list("123, \"hi\";a=1, (1 2 3);p", unwrap: true, maps: true)
{:ok,
[
{123, %{}},
{"hi", %{"a" => 1}},
{[{1, %{}}, {2, %{}}, {3, %{}}], %{"p" => true}}
]}
@spec post_process_dict(Enumerable.t({binary(), item()}), [option()]) :: Enumerable.t({binary(), item()})
Applies the :unwrap and :maps transformations to a dictionary already
parsed with the default options.
Accepts the same options as parse_item/2.
Applies the :unwrap and :maps transformations to an item already parsed
with the default options.
Accepts the same options as parse_item/2.
Examples
iex> {:ok, item} = Texture.HttpStructuredField.parse_item("123;a")
iex> Texture.HttpStructuredField.post_process_item(item, unwrap: true, maps: true)
{123, %{"a" => true}}
Applies the :unwrap and :maps transformations to each item of a list
already parsed with the default options.
Accepts the same options as parse_item/2.