# `Snodo.Resource.Template`
[🔗](https://github.com/joshrotenberg/snodo/blob/v0.1.0/lib/snodo/resource/template.ex#L1)

An exact matcher for the simple-expansion subset of URI templates.

This is deliberately not an RFC 6570 implementation. It recognises one
narrow shape, the one MCP resource templates overwhelmingly use, and refuses
everything else so an application is never silently given a matcher that is
almost right:

  * the scheme is a literal, matched case-insensitively;
  * the authority is one literal or one `{variable}`;
  * each path segment is one literal or one `{variable}`;
  * there is no query, fragment, userinfo, or port;
  * no operator (`+ # . / ; ? & =`) or modifier (`* :n`) appears;
  * variables do not use the reserved request keys `uri` or `_meta`.

A variable therefore binds exactly one whole segment, which makes matching
and extraction unambiguous. `Snodo.Resource` compiles a template at build time
and generates `matches?/1` from the result; a template outside the subset
compiles to `:unsupported`, and its module must implement `matches?/1`
itself.

Matched values are percent-decoded once and must be valid UTF-8. Malformed
percent escapes and empty segments do not match. Encoded separators stay
inside their original segment; `+` is not decoded as a space. Repeated
variables must bind the same decoded value. Literal authority and path
segments match exactly, without percent-decoding or slash normalization.

# `part`

```elixir
@type part() :: {:literal, String.t()} | {:variable, String.t()}
```

# `t`

```elixir
@type t() :: %Snodo.Resource.Template{
  authority: part(),
  scheme: String.t(),
  segments: [part()]
}
```

# `variables`

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

# `compile`

```elixir
@spec compile(String.t()) :: {:ok, t()} | :unsupported
```

Compiles a URI template, or reports that it is outside the supported subset.

`URI.new/1` rejects the braces, so the template is parsed textually. Its
literals are checked as a concrete URI with safe placeholders for variables,
so an invalid literal cannot produce an unreachable generated matcher.

# `match`

```elixir
@spec match(t(), String.t()) :: {:ok, variables()} | :error
```

Matches a concrete URI, returning the bound variables.

# `variables`

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

Returns the variable names a compiled template binds, in template order.

---

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