# `X402.Extensions.Bazaar.Metadata`
[🔗](https://github.com/cardotrejos/x402/blob/v0.9.0/lib/x402/extensions/bazaar/metadata.ex#L1)

Validation rules for bazaar service metadata and `routeTemplate` values.

The bazaar extension lets resource servers publish provider-level metadata
on the `PaymentRequired.resource` object (`serviceName`, `tags`,
`iconUrl`) and, for dynamic routes, a top-level `routeTemplate` on the
extension. Clients echo the `resource` block back to the facilitator, so
a hostile client could try to poison a discovery catalog through it. The
extension spec therefore defines *soft-drop* rules every SDK applies
identically: a field that fails its rule is discarded and the rest of the
metadata is kept.

These helpers mirror the reference SDKs' `isValidServiceName`,
`sanitizeTags`, `isValidIconUrl`, `sanitizeResourceServiceMetadata`, and
`isValidRouteTemplate`. `X402.Plug.PaymentGate` applies them to its own
route configuration at init (rejecting invalid values outright, since a
misconfigured server is a programmer error), and facilitators apply the
soft-drop form through `sanitize_resource/1` and `extract_route_template/1`
when cataloging.

One deliberate deviation: the spec asks for UTS #46 (IDNA) host
normalization before the loopback / IP-literal checks on `iconUrl`. This
library carries no IDNA dependency, so any host still containing
non-ASCII characters after percent-decoding is rejected (fail closed)
rather than normalized.

See the
[bazaar extension spec](https://github.com/x402-foundation/x402/blob/main/specs/extensions/bazaar.md).

# `resource`

```elixir
@type resource() :: %{optional(String.t()) =&gt; term()}
```

A string-keyed `ResourceInfo` map as carried on the wire.

# `extract_route_template`
*since 0.9.0* 

```elixir
@spec extract_route_template(term()) :: String.t() | nil
```

Returns the validated `routeTemplate` of a bazaar extension map, or `nil`.

A facilitator falls back to the concrete URL path when this returns
`nil`, as the spec requires for absent or invalid templates.

## Examples

    iex> X402.Extensions.Bazaar.Metadata.extract_route_template(%{"routeTemplate" => "/users/:userId"})
    "/users/:userId"

    iex> X402.Extensions.Bazaar.Metadata.extract_route_template(%{"routeTemplate" => "../etc"})
    nil

    iex> X402.Extensions.Bazaar.Metadata.extract_route_template(%{"info" => %{}})
    nil

# `sanitize_resource`
*since 0.9.0* 

```elixir
@spec sanitize_resource(resource()) :: resource()
```

Applies the soft-drop rules to a string-keyed `ResourceInfo` map.

Drops `serviceName` and `iconUrl` when they fail their rules, replaces
`tags` with its sanitized form (dropping the key when nothing valid is
left), and leaves every other key untouched.

## Examples

    iex> X402.Extensions.Bazaar.Metadata.sanitize_resource(%{
    ...>   "url" => "https://api.example.com/weather",
    ...>   "serviceName" => "Example Weather",
    ...>   "tags" => ["weather", "WEATHER", "forecast"],
    ...>   "iconUrl" => "http://localhost/icon.png"
    ...> })
    %{
      "url" => "https://api.example.com/weather",
      "serviceName" => "Example Weather",
      "tags" => ["weather", "forecast"]
    }

    iex> X402.Extensions.Bazaar.Metadata.sanitize_resource(%{"url" => "https://a.example", "tags" => "nope"})
    %{"url" => "https://a.example"}

# `sanitize_tags`
*since 0.9.0* 

```elixir
@spec sanitize_tags(term()) :: [String.t()]
```

Sanitizes a `tags` value.

Keeps the entries that pass the `serviceName` rules (non-empty printable
ASCII, at most 32 characters), drops duplicates case-insensitively
(first occurrence wins), and truncates the result to the first five
entries. Anything that is not a list sanitizes to `[]`.

## Examples

    iex> X402.Extensions.Bazaar.Metadata.sanitize_tags(["weather", "Weather", "", "forecast", 42])
    ["weather", "forecast"]

    iex> X402.Extensions.Bazaar.Metadata.sanitize_tags(~w(a b c d e f g))
    ["a", "b", "c", "d", "e"]

    iex> X402.Extensions.Bazaar.Metadata.sanitize_tags("weather")
    []

# `valid_icon_url?`
*since 0.9.0* 

```elixir
@spec valid_icon_url?(term()) :: boolean()
```

Checks an `iconUrl` value.

Valid values are at most 2048 characters, contain no control characters,
and parse as an absolute `http://` or `https://` URL without userinfo.
After percent-decoding, the host must not be an IP literal (v4 or v6), a
loopback name (`localhost`, `localhost.localdomain`, `ip6-localhost`,
`ip6-loopback`), an all-digit name (a decimal IP encoding such as
`2130706433`), a hex literal (`0x7f000001`), or contain non-ASCII
characters.

## Examples

    iex> X402.Extensions.Bazaar.Metadata.valid_icon_url?("https://api.example.com/icon.png")
    true

    iex> X402.Extensions.Bazaar.Metadata.valid_icon_url?("data:image/png;base64,AAAA")
    false

    iex> X402.Extensions.Bazaar.Metadata.valid_icon_url?("https://user@api.example.com/icon.png")
    false

    iex> X402.Extensions.Bazaar.Metadata.valid_icon_url?("http://127.0.0.1/icon.png")
    false

    iex> X402.Extensions.Bazaar.Metadata.valid_icon_url?("http://[::1]/icon.png")
    false

    iex> X402.Extensions.Bazaar.Metadata.valid_icon_url?("http://%6cocalhost/icon.png")
    false

    iex> X402.Extensions.Bazaar.Metadata.valid_icon_url?("http://2130706433/icon.png")
    false

    iex> X402.Extensions.Bazaar.Metadata.valid_icon_url?("http://0x7f000001/icon.png")
    false

# `valid_route_template?`
*since 0.9.0* 

```elixir
@spec valid_route_template?(term()) :: boolean()
```

Checks a `routeTemplate` value.

Valid templates are non-empty, start with `/`, match
`^/[a-zA-Z0-9_/:.\-~%]+$`, and contain neither `..` nor `://` once
percent-encoding is decoded.

## Examples

    iex> X402.Extensions.Bazaar.Metadata.valid_route_template?("/users/:userId")
    true

    iex> X402.Extensions.Bazaar.Metadata.valid_route_template?("/users/../admin")
    false

    iex> X402.Extensions.Bazaar.Metadata.valid_route_template?("/users/%2e%2e/admin")
    false

    iex> X402.Extensions.Bazaar.Metadata.valid_route_template?("/redirect/http://evil.example")
    false

    iex> X402.Extensions.Bazaar.Metadata.valid_route_template?("users/:userId")
    false

# `valid_service_name?`
*since 0.9.0* 

```elixir
@spec valid_service_name?(term()) :: boolean()
```

Checks a `serviceName` value.

Valid names are non-empty, at most 32 characters long, and made of
printable ASCII (`U+0020`–`U+007E`) only.

## Examples

    iex> X402.Extensions.Bazaar.Metadata.valid_service_name?("Example Weather")
    true

    iex> X402.Extensions.Bazaar.Metadata.valid_service_name?("")
    false

    iex> X402.Extensions.Bazaar.Metadata.valid_service_name?("Wetter für alle")
    false

    iex> X402.Extensions.Bazaar.Metadata.valid_service_name?(String.duplicate("a", 33))
    false

    iex> X402.Extensions.Bazaar.Metadata.valid_service_name?(:weather)
    false

---

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