# `X402.HTTPSignature.StructuredField`
[🔗](https://github.com/cardotrejos/x402/blob/v0.9.0/lib/x402/http_signature/structured_field.ex#L1)

The RFC 8941 Structured Field subset used by HTTP Message Signatures.

`Signature-Input` and `Signature` (RFC 9421 §4) are Dictionary fields
whose members are a parameterized Inner List of Strings and a Byte
Sequence respectively. This module serializes and parses exactly the
grammar those fields need, using the strict rules of RFC 8941 §4 so
that a value parsed and re-serialized reproduces the bytes a signer
hashed.

## Representation

| Wire type     | Elixir value                                   |
| ------------- | ---------------------------------------------- |
| String        | `binary`                                       |
| Integer       | `integer`                                      |
| Decimal       | `float`                                        |
| Boolean       | `boolean`                                      |
| Token         | `{:token, binary}`                             |
| Byte Sequence | `{:bytes, binary}`                             |
| Item          | `{bare_item, params}`                          |
| Inner List    | `{:inner_list, [item], params}`                |
| Parameters    | `[{key, bare_item}]` (ordered)                 |
| Dictionary    | `[{key, item | inner_list}]` (ordered)         |

Strings and Tokens are distinct wire types, so a bare `binary` always
serializes as a quoted String.

# `bare`

```elixir
@type bare() ::
  binary()
  | integer()
  | float()
  | boolean()
  | {:token, binary()}
  | {:bytes, binary()}
```

A bare item value.

# `dictionary`

```elixir
@type dictionary() :: [{binary(), member()}]
```

An ordered dictionary.

# `error`

```elixir
@type error() :: :invalid_structured_field | :duplicate_key
```

# `inner_list`

```elixir
@type inner_list() :: {:inner_list, [item()], params()}
```

An inner list with its parameters.

# `item`

```elixir
@type item() :: {bare(), params()}
```

An item with its parameters.

# `member`

```elixir
@type member() :: item() | inner_list()
```

A dictionary member value.

# `params`

```elixir
@type params() :: [{binary(), bare()}]
```

Ordered parameters.

# `parse_dictionary`
*since 0.9.0* 

```elixir
@spec parse_dictionary(binary(), [{:duplicate_keys, :overwrite | :error}]) ::
  {:ok, dictionary()} | {:error, error()}
```

Parses a dictionary (RFC 8941 §4.2.2).

## Examples

    iex> X402.HTTPSignature.StructuredField.parse_dictionary(
    ...>   ~S|sig1=("@method" "@path";req);created=1618884473;keyid="k", sig2=:AQID:|
    ...> )
    {:ok, [
      {"sig1", {:inner_list, [{"@method", []}, {"@path", [{"req", true}]}], [{"created", 1618884473}, {"keyid", "k"}]}},
      {"sig2", {{:bytes, <<1, 2, 3>>}, []}}
    ]}

    iex> X402.HTTPSignature.StructuredField.parse_dictionary("a, b;x=?0")
    {:ok, [{"a", {true, []}}, {"b", {true, [{"x", false}]}}]}

    iex> X402.HTTPSignature.StructuredField.parse_dictionary("sig1=(")
    {:error, :invalid_structured_field}

    iex> X402.HTTPSignature.StructuredField.parse_dictionary("a=1,")
    {:error, :invalid_structured_field}

A repeated key (in the dictionary or in any parameter list) overwrites
the earlier value as RFC 8941 §4.2.2 prescribes; pass
`duplicate_keys: :error` to reject the field instead, which is what a
signature verifier wants:

    iex> X402.HTTPSignature.StructuredField.parse_dictionary("a=1, a=2")
    {:ok, [{"a", {2, []}}]}

    iex> X402.HTTPSignature.StructuredField.parse_dictionary("a=1, a=2", duplicate_keys: :error)
    {:error, :duplicate_key}

    iex> X402.HTTPSignature.StructuredField.parse_dictionary("a=1;x=1;x=2", duplicate_keys: :error)
    {:error, :duplicate_key}

# `parse_item`
*since 0.9.0* 

```elixir
@spec parse_item(binary()) :: {:ok, item(), binary()} | :error
```

Parses an item with parameters (RFC 8941 §4.2.3), returning the rest of
the input.

## Examples

    iex> X402.HTTPSignature.StructuredField.parse_item(~S|"@authority";req rest|)
    {:ok, {"@authority", [{"req", true}]}, " rest"}

    iex> X402.HTTPSignature.StructuredField.parse_item("token;n=1.25;b=?1")
    {:ok, {{:token, "token"}, [{"n", 1.25}, {"b", true}]}, ""}

    iex> X402.HTTPSignature.StructuredField.parse_item(~S|"unterminated|)
    :error

# `serialize_bare`
*since 0.9.0* 

```elixir
@spec serialize_bare(bare()) :: {:ok, binary()} | {:error, error()}
```

Serializes a bare item (RFC 8941 §4.1.3.1).

## Examples

    iex> X402.HTTPSignature.StructuredField.serialize_bare("say \"hi\" \\")
    {:ok, "\"say \\\"hi\\\" \\\\\""}

    iex> X402.HTTPSignature.StructuredField.serialize_bare(-42)
    {:ok, "-42"}

    iex> X402.HTTPSignature.StructuredField.serialize_bare(1.5)
    {:ok, "1.5"}

    iex> X402.HTTPSignature.StructuredField.serialize_bare(false)
    {:ok, "?0"}

    iex> X402.HTTPSignature.StructuredField.serialize_bare({:bytes, <<1, 2, 3>>})
    {:ok, ":AQID:"}

    iex> X402.HTTPSignature.StructuredField.serialize_bare(1_000_000_000_000_000)
    {:error, :invalid_structured_field}

# `serialize_dictionary`
*since 0.9.0* 

```elixir
@spec serialize_dictionary(dictionary()) :: {:ok, binary()} | {:error, error()}
```

Serializes a dictionary (RFC 8941 §4.1.2).

## Examples

    iex> X402.HTTPSignature.StructuredField.serialize_dictionary([
    ...>   {"sig1", {:inner_list, [{"@method", []}], [{"created", 1_618_884_473}]}}
    ...> ])
    {:ok, ~S|sig1=("@method");created=1618884473|}

    iex> X402.HTTPSignature.StructuredField.serialize_dictionary([{"sig1", {{:bytes, "abc"}, []}}])
    {:ok, "sig1=:YWJj:"}

    iex> X402.HTTPSignature.StructuredField.serialize_dictionary([{"Sig", {1, []}}])
    {:error, :invalid_structured_field}

# `serialize_inner_list`
*since 0.9.0* 

```elixir
@spec serialize_inner_list(inner_list()) :: {:ok, binary()} | {:error, error()}
```

Serializes an inner list with parameters (RFC 8941 §4.1.1.1).

## Examples

    iex> X402.HTTPSignature.StructuredField.serialize_inner_list(
    ...>   {:inner_list, [{"@query-param", [{"name", "Pet"}]}, {"@path", [{"req", true}]}], [{"tag", "x"}]}
    ...> )
    {:ok, ~S|("@query-param";name="Pet" "@path";req);tag="x"|}

    iex> X402.HTTPSignature.StructuredField.serialize_inner_list({:inner_list, [], []})
    {:ok, "()"}

# `serialize_item`
*since 0.9.0* 

```elixir
@spec serialize_item(item()) :: {:ok, binary()} | {:error, error()}
```

Serializes an item with parameters (RFC 8941 §4.1.3).

## Examples

    iex> X402.HTTPSignature.StructuredField.serialize_item({"content-type", []})
    {:ok, ~S|"content-type"|}

    iex> X402.HTTPSignature.StructuredField.serialize_item({{:token, "a"}, [{"x", 1}, {"y", true}]})
    {:ok, "a;x=1;y"}

    iex> X402.HTTPSignature.StructuredField.serialize_item({"caf\u00e9", []})
    {:error, :invalid_structured_field}

# `serialize_key`
*since 0.9.0* 

```elixir
@spec serialize_key(binary()) :: {:ok, binary()} | {:error, error()}
```

Serializes a dictionary or parameter key (RFC 8941 §4.1.1.3).

## Examples

    iex> X402.HTTPSignature.StructuredField.serialize_key("sig-b26")
    {:ok, "sig-b26"}

    iex> X402.HTTPSignature.StructuredField.serialize_key("Sig1")
    {:error, :invalid_structured_field}

---

*Consult [api-reference.md](api-reference.md) for complete listing*
