Snodo.Resource.Template (snodo v0.1.0)

Copy Markdown View Source

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.

Summary

Functions

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

Matches a concrete URI, returning the bound variables.

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

Types

part()

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

t()

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

variables()

@type variables() :: %{optional(String.t()) => String.t()}

Functions

compile(uri_template)

@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(template, uri)

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

Matches a concrete URI, returning the bound variables.

variables(template)

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

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