Skip to main content

RoutePattern

RoutePattern[A] pairs an HTTP method with a typed path pattern. It is the primary routing descriptor in zio-blocks-endpoint: every Endpoint carries a RoutePattern that determines which HTTP method and URL path it matches. Its shape is:

final case class RoutePattern[A](
method: Method,
pathCodec: PathCodec[A],
doc: Doc = Doc.empty
)

Motivation

HTTP routing requires matching both a method (GET, POST, …) and a path (/users/42). RoutePattern holds both in a single typed value. The type parameter A is the type of values extracted from the dynamic path segments — Unit for fully-literal paths, Int for a single integer segment, (String, UUID) for two dynamic segments, and so on.

Using a typed route pattern means route construction and path extraction are verified at compile time: the type of the extracted path value is always consistent with the path codec definition.

Construction

Several construction forms exist. The method-first syntax is the most common and most readable; the others cover less typical use cases.

The primary syntax uses the Method extension method / to produce a RoutePattern directly:

import zio.blocks.endpoint._
import zio.blocks.endpoint.RoutePattern._
import zio.http.Method

val getUsers = Method.GET / "users"
val postUser = Method.POST / "users"
val deleteOrder = Method.DELETE / "orders" / PathCodec.uuid("orderId")

This is the recommended construction style: it reads like the route itself and keeps the method close to the path.

Constant constructors

Pre-built method-only patterns are available as constants on the RoutePattern companion:

import zio.blocks.endpoint._
import zio.blocks.endpoint.RoutePattern._

val get = RoutePattern.GET
val post = RoutePattern.POST
val put = RoutePattern.PUT
val delete = RoutePattern.DELETE
val patch = RoutePattern.PATCH
val head = RoutePattern.HEAD
val options = RoutePattern.OPTIONS

These are equivalent to RoutePattern(Method.GET) etc., with an empty path codec. Additional constants like CONNECT and TRACE are also available for the complete set of HTTP methods.

From a Path value

To construct a pattern from a runtime Path (all literal segments), use RoutePattern.apply(method, path):

import zio.blocks.endpoint._
import zio.blocks.endpoint.RoutePattern._
import zio.http.{Method, Path}

val route = RoutePattern(Method.GET, Path("/users/active"))

Catch-all trailing patterns

To match any path suffix, use RoutePattern.any:

import zio.blocks.endpoint._
import zio.blocks.endpoint.RoutePattern._
import zio.http.{Method, Path}

val catchAll: RoutePattern[Path] = RoutePattern.any
val getAny: RoutePattern[Path] = RoutePattern.any(Method.GET)

Path Composition with /

To append additional PathCodec segments to a RoutePattern, use the / method:

import zio.blocks.endpoint._
import zio.blocks.endpoint.RoutePattern._
import zio.http.Method

val route = Method.GET / "users" / PathCodec.int("id") / "posts"

Each / call produces a new RoutePattern with a widened type. The type is automatically flattened, eliminating Unit components: Unit / Int / Unit becomes Int, not ((Unit, Int), Unit).

Decoding and Encoding

RoutePattern is bidirectional: RoutePattern#decode validates a live request against the pattern, and RoutePattern#encode rebuilds a request pair from a typed path value.

RoutePattern#decode

To check whether a method and path match, and extract the typed path value:

import zio.blocks.endpoint._
import zio.blocks.endpoint.RoutePattern._
import zio.http.{Method, Path}

val route = Method.GET / "users" / PathCodec.int("id")

val result: Either[String, Int] = route.decode(Method.GET, Path("/users/42"))

RoutePattern#decode returns Left if the method does not match or any segment fails to parse, and Right(value) with the extracted path value otherwise. HEAD requests automatically fall back to GET for compatibility.

RoutePattern#encode

To turn a typed value back into a (Method, Path) pair:

import zio.blocks.endpoint._
import zio.blocks.endpoint.RoutePattern._
import zio.http.{Method, Path}

val route = Method.POST / "orders" / PathCodec.uuid("orderId")

val result: Either[String, (Method, Path)] = route.encode(java.util.UUID.fromString("550e8400-e29b-41d4-a716-446655440000"))

RoutePattern#matches

To test membership without extracting the value:

import zio.blocks.endpoint._
import zio.blocks.endpoint.RoutePattern._
import zio.http.{Method, Path}

val route = Method.GET / "users"
val matches = route.matches(Method.GET, Path("/users"))

Structural Operations

Three structural operations transform an existing RoutePattern without building a new one from scratch: RoutePattern#alternatives, RoutePattern#nest, and RoutePattern#render.

Alternatives

RoutePattern#alternatives expands Method.ANY and Method.Methods into a flat list of single-method patterns. RouteTree calls this before inserting into the trie:

import zio.blocks.endpoint._
import zio.blocks.endpoint.RoutePattern._
import zio.http.Method

val anyGet: RoutePattern[?] = RoutePattern.any(Method.GET)
val expanded = anyGet.alternatives

Nesting

RoutePattern#nest prepends a literal path prefix without modifying the route's type or dynamic segments:

import zio.blocks.endpoint._
import zio.blocks.endpoint.RoutePattern._
import zio.http.Method

val route = Method.GET / "users" / PathCodec.int("id")
val versioned = route.nest(PathCodec("/api/v1"))

This is useful for adding a version prefix to a group of existing routes without rewriting each one.

Rendering

RoutePattern#render produces a human-readable string representation, useful for logging and OpenAPI path generation:

import zio.blocks.endpoint._
import zio.blocks.endpoint.RoutePattern._
import zio.http.Method

val route = Method.GET / "users" / PathCodec.int("id")
val rendered: String = route.render

Dynamic segments are rendered as {name} by default, matching OpenAPI path parameter convention.