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

Behaviour and compile-time convenience DSL for MCP resources.

A resource module declares either one exact `:uri` or one `:uri_template`.
URI routing stays application-owned: the framework does not implement RFC
6570 expansion or authorization policy.

A template inside the simple-expansion subset described in
`Snodo.Resource.Template` gets a generated `matches?/1`, so the common
`scheme://{var}/literal` shape needs no matcher at all. A template outside
that subset matches nothing until the module implements `matches?/1` itself.

`matches?/1` may answer in two ways. `true` and `false` route without saying
anything more. `{:ok, variables}`, a map of string keys to string values,
routes *and* hands the extracted template variables to `read/2`, so a
matcher never has to be paired with a second parse of the same URI. The
generated matcher uses this form. Variables may not shadow `"uri"` or
`"_meta"`, which the request itself owns.

`read/2` receives the request params, merged with any variables the matcher
bound, plus the immutable request context. It returns
`Snodo.Result.resource_read/2` containing text or blob content maps.

    defmodule PackageInfo do
      use Snodo.Resource, uri_template: "hex://{name}/info", name: "package_info"

      @impl true
      def read(%{"name" => name}, _context), do: fetch(name)
    end

Content maps must contain only JSON values, which means string keys.
`Snodo.JSONValue.encodable!/1` converts an atom-keyed domain value into one.

# `complete`
*optional* 

```elixir
@callback complete(Snodo.Completion.t(), Snodo.Context.t()) ::
  {:ok, Snodo.Result.t()} | {:error, Snodo.Error.t() | term()}
```

# `definition`

```elixir
@callback definition() :: Snodo.Resource.Definition.t()
```

# `matches?`

```elixir
@callback matches?(uri :: String.t()) ::
  boolean() | {:ok, Snodo.Resource.Template.variables()}
```

# `read`

```elixir
@callback read(params :: map(), Snodo.Context.t()) ::
  {:ok, Snodo.Result.t()} | {:error, Snodo.Error.t() | term()}
```

# `blob`

```elixir
@spec blob(String.t(), String.t(), keyword()) :: map()
```

Builds one base64-encoded binary resource-content map.

# `definition`

```elixir
@spec definition(module()) :: Snodo.Resource.Definition.t()
```

Returns and validates the protocol-neutral definition for a resource module.

# `json`

```elixir
@spec json(String.t(), term(), keyword()) :: map()
```

Builds one JSON text resource-content map.

# `text`

```elixir
@spec text(String.t(), String.t(), keyword()) :: map()
```

Builds one text resource-content map.

# `validate_module!`

```elixir
@spec validate_module!(module()) :: :ok
```

Validates all resource callbacks and static metadata.

---

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