# Format Filters

Exonerate supports JSONSchema format validation through the `:format` option.
This guide details all available format filters and how to configure them.

## Basic Usage

To enable format validation, pass the `:format` option to any Exonerate macro:

```elixir
Exonerate.function_from_string(:def, :validate, """
  {"type": "string", "format": "email"}
""", format: true)
```

## Configuration

The `:format` option accepts either `true` or a keyword list:

- `true`: shorthand for `[default: true]`

- keywords:
  - `:at`: a list of `{<json pointer>, <filter-spec>}` tuples to apply
    format filters in specific locations in the schema.  This can be used
    to specify custom filters for non-default format types.  It can also be
    used to override default formatting.  `<json pointer>` should be a
    string which may be relative if no `"id"` or `"$id"` metadata are
    present in the parents of the location.  Otherwise, the pointer must
    the a uri of the nearest parent containing `"id"` or `"$id"` metadata,
    with the relative pointer applied as the fragment of the uri.

    `<filter-spec>` may either be a module, which implies the existence of
    the `module.validate/1` function, `{module, function}` which implies
    the existence of `module.function/1`, or `{module, function, [extra-args]}`
    which implies the existence of `module.function/n` where the extra args
    are appended to the end of string to be validated.

    In all cases, the validation function is expected to emit `:ok` if the
    string validates, or `{:error, reason}`, if it does not.  `reason` should
    either be a string or `nil`.
  - `:type`: a list of `{<format-type>, <filter-spec>}` to apply across
    the schema whenever `<format-type>` is encountered.  This can be used
    to specify custom filters for non-default format types.  It can also
    be used to override default formatting.  `<filter-spec>` is as above.
  - `:default`: `true` to enable all default filters or a list of strings
    specifying the default format types to enable.

## Available Format Types

### Date and Time

#### `"date-time"`

Enables the default date-time filter for all `{"format": "date-time"}` contexts.
This uses Elixir's `NaiveDateTime.from_iso8601/1` parser.

#### `"date-time-utc"`

Enables the default date-time-utc filter for all `{"format": "date-time-utc"}` contexts.
This uses Elixir's `DateTime.from_iso8601/1` parser, and requires the offset to be
0 from UTC.

#### `"date-time-tz"`

Enables the default date-time filter for all `{"format": "date-time-tz"}` context strings.
This uses Elixir's `DateTime.from_iso8601/1` parser, which requires an offset to
be specified.

#### `"date"`

Enables the default date filter for all `{"format": "date"}` context strings.
This uses Elixir's `Date.from_iso8601/1` parser.

#### `"time"`

Enables the default time filter for all `{"format": "time"}` context strings.
This uses Elixir's `Time.from_iso8601/1` parser.

#### `"duration"`

Enables the default duration filter for all `{"format": "duration"}` context strings.
This uses a custom ABNF validator that matches Appendix A of RFC 3339:
https://www.rfc-editor.org/rfc/rfc3339.txt

The validation function can be generated by `Exonerate.Formats.Duration`.
Requires `NimbleParsec` and `Pegasus` dependencies.

### Network

#### `"ipv4"`

Enables the default ipv4 filter for all `{"format": "ipv4"}` context strings.
This uses Erlang's `:inet.parse_ipv4strict_address/1` parser.

#### `"ipv6"`

Enables the default ipv6 filter for all `{"format": "ipv6"}` context strings.
This uses Erlang's `:inet.parse_ipv6strict_address/1` parser.

#### `"email"`

Enables the default email filter for all `{"format": "email"}` context strings.
This uses a custom ABNF validator that matches section 4.1.2 of RFC 5322:
https://www.rfc-editor.org/rfc/rfc5322.txt

The validation function can be generated by `Exonerate.Formats.Email`.
Requires `NimbleParsec` and `Pegasus` dependencies.

#### `"idn-email"`

Enables the default idn-email (i18n email address) filter for all
`{"format": "idn-email"}` context strings.  This uses a custom ABNF validator
that matches section 3.3 of RFC 6531: https://www.rfc-editor.org/rfc/rfc5322.txt

The validation function can be generated by `Exonerate.Formats.IdnEmail`.
Requires `NimbleParsec` and `Pegasus` dependencies.

#### `"hostname"`

Enables the default hostname filter for all `{"format": "hostname"}` context strings.
This uses a custom ABNF validator that matches section 2.1 of RFC 1123:
https://www.rfc-editor.org/rfc/rfc1123.txt

The validation function can be generated by `Exonerate.Formats.Hostname`.
Requires `NimbleParsec` and `Pegasus` dependencies.

#### `"idn-hostname"`

Enables the default idn-hostname (i18n hostname) filter for all
`{"format": "idn-hostname"}` context strings.

Note that in order to use this filter, you must add the `:idna` library to your dependencies.

The validation function can be generated by `Exonerate.Formats.IdnHostname`.
Requires `NimbleParsec` and `Pegasus` dependencies.

### URIs and IRIs

#### `"uri"`

Enables the default uri filter for all `{"format": "uri"}` context strings.
This uses a custom ABNF validator that matches section 3 of RFC 3986:
https://www.rfc-editor.org/rfc/rfc3986.txt.

> ### Absolute URIs {: .warning}
>
> URIs must be absolute, i.e. they must contain a scheme, host,
> and path.  If you need relative URIs, use the `uri-reference` filter.

The validation function can be generated by `Exonerate.Formats.Uri`.
Requires `NimbleParsec` and `Pegasus` dependencies.

#### `"uri-reference"`

Enables the default uri-reference filter for all `{"format": "uri-reference"}` context strings.
This uses a custom ABNF validator that matches section 3 of RFC 3986:
https://www.rfc-editor.org/rfc/rfc3986.txt.

The validation function can be generated by `Exonerate.Formats.UriReference`.
Requires `NimbleParsec` and `Pegasus` dependencies.

#### `"iri"`

Enables the default iri (i18n uri) filter for all `{"format": "iri"}` context strings.
This uses a custom ABNF validator that matches section 2.2 of RFC 3987:
https://www.rfc-editor.org/rfc/rfc3987.txt.

> ### Absolute IRIs {: .warning}
>
> IRIs must be absolute, i.e. they must contain a scheme, host,
> and path.  If you need relative IRIs, use the `iri-reference` filter.

The validation function can be generated by `Exonerate.Formats.Iri`.
Requires `NimbleParsec` and `Pegasus` dependencies.

#### `"iri-reference"`

Enables the default iri-reference (i18n uri) for all `{"format": "iri-reference"}` context strings.
This uses a custom ABNF validator that matches section 2.2 of RFC 3987:
https://www.rfc-editor.org/rfc/rfc3987.txt.

The validation function can be generated by `Exonerate.Formats.IriReference`.
Requires `NimbleParsec` and `Pegasus` dependencies.

#### `"uri-template"`

Enables the default uri-template filter for all `{"format": "uri-template"}` context strings.
This uses a custom ABNF validator that matches section 2.3 of RFC 6570:
https://www.rfc-editor.org/rfc/rfc6570.txt.

> ### URI-Template parent {: .info}
>
> uri-templates are templated against iri-reference strings.  This means they do not
> need to be absolute, and they may include unicode characters.

The validation function can be generated by `Exonerate.Formats.UriTemplate`.
Requires `NimbleParsec` and `Pegasus` dependencies.

### JSON Pointers

#### `"json-pointer"`

Enables the default json-pointer filter for all `{"format": "json-pointer"}` context strings.
This uses a custom ABNF validator that matches section 3 of RFC 6901:
https://www.rfc-editor.org/rfc/rfc6901.txt

The validation function can be generated by `Exonerate.Formats.JsonPointer`.
Requires `NimbleParsec` and `Pegasus` dependencies.

#### `"relative-json-pointer"`

Enables the default relative-json-pointer filter for all
`{"format": "relative-json-pointer"}` context strings.
This uses a custom ABNF validator that matches the following RFC proposal:
https://datatracker.ietf.org/doc/html/draft-handrews-relative-json-pointer-01

The validation function can be generated by `Exonerate.Formats.RelativeJsonPointer`.
Requires `NimbleParsec` and `Pegasus` dependencies.

### Other

#### `"uuid"`

Enables the default uuid filter for all `{"format": "uuid"}` context strings.

#### `"regex"`

Enables the default regex filter for all `{"format": "regex"}` context strings.
Note: this does not compile the regex, instead it uses a custom ABNF validator
that matches the ECMA-262 standard:
https://www.ecma-international.org/publications-and-standards/standards/ecma-262/

The validation function can be generated by `Exonerate.Formats.Regex`.
Requires `NimbleParsec` and `Pegasus` dependencies.

## Custom Format Filters

You can define custom format filters by implementing a validation function that:

1. Accepts a string as its first argument
2. Returns `:ok` if the string validates
3. Returns `{:error, reason}` if validation fails, where `reason` is a string or `nil`

### Example

```elixir
defmodule MyApp.Formats.PhoneNumber do
  def validate(string) do
    if Regex.match?(~r/^\+?[1-9]\d{1,14}$/, string) do
      :ok
    else
      {:error, "invalid phone number format"}
    end
  end
end

# Use with :type option (applies globally)
Exonerate.function_from_string(:def, :validate, """
  {"type": "string", "format": "phone"}
""", format: [type: [{"phone", MyApp.Formats.PhoneNumber}]])

# Use with :at option (applies at specific location)
Exonerate.function_from_string(:def, :validate, """
  {"type": "object", "properties": {"phone": {"type": "string", "format": "phone"}}}
""", format: [at: [{"/properties/phone", MyApp.Formats.PhoneNumber}]])
```
