Comprehensive path decomposition and parameter extraction system for building REST APIs with type-safe URL routing, query parameter handling, and file extension matching.
Core path decomposition operators for building URL routing patterns.
/**
* Root path constant - represents the root "/" path
*/
val Root: Uri.Path.Root.type
/**
* Path segment extractor - separates path into parent and last segment
*/
object / {
def unapply(path: Path): Option[(Path, String)]
}
/**
* Path prefix extractor - extracts first segment and remaining path
*/
object /: {
def unapply(path: Path): Option[(String, Path)]
}Usage Examples:
import org.http4s.dsl.io._
val routes = HttpRoutes.of[IO] {
// Root path
case GET -> Root =>
Ok("Home page")
// Single segment
case GET -> Root / "about" =>
Ok("About page")
// Multiple segments
case GET -> Root / "api" / "v1" / "users" =>
Ok("Users API v1")
// Path prefix matching
case GET -> ("api" /: rest) =>
Ok(s"API endpoint: $rest")
}Type-safe extraction of path parameters with automatic parsing and validation.
/**
* Integer path variable extractor
*/
object IntVar extends PathVar[Int] {
def unapply(str: String): Option[Int]
}
/**
* Long path variable extractor
*/
object LongVar extends PathVar[Long] {
def unapply(str: String): Option[Long]
}
/**
* UUID path variable extractor
*/
object UUIDVar extends PathVar[UUID] {
def unapply(str: String): Option[UUID]
}
/**
* Base class for path variable extractors
*/
abstract class PathVar[A](cast: String => Try[A]) {
def unapply(str: String): Option[A]
}Usage Examples:
import org.http4s.dsl.io._
import java.util.UUID
val routes = HttpRoutes.of[IO] {
// Integer path parameter
case GET -> Root / "users" / IntVar(userId) =>
Ok(s"User ID: $userId")
// Long path parameter
case GET -> Root / "orders" / LongVar(orderId) =>
Ok(s"Order ID: $orderId")
// UUID path parameter
case GET -> Root / "sessions" / UUIDVar(sessionId) =>
Ok(s"Session: $sessionId")
// Multiple path variables
case GET -> Root / "users" / IntVar(userId) / "orders" / LongVar(orderId) =>
Ok(s"User $userId, Order $orderId")
// Path variables with validation (returns None for invalid formats)
case GET -> Root / "products" / IntVar(productId) if productId > 0 =>
Ok(s"Valid product ID: $productId")
}Extract file extensions from paths and filenames for content negotiation and file handling.
/**
* File extension extractor for paths and filenames
*/
object ~ {
/** Extract extension from URI path */
def unapply(path: Path): Option[(Path, String)]
/** Extract extension from filename string */
def unapply(fileName: String): Option[(String, String)]
}Usage Examples:
import org.http4s.dsl.io._
val routes = HttpRoutes.of[IO] {
// File extension from path
case GET -> Root / "documents" / (fileName ~ "pdf") =>
Ok(s"PDF document: $fileName")
// Multiple extension matching
case GET -> Root / "images" / (fileName ~ ext) =>
ext match {
case "jpg" | "png" | "gif" => Ok(s"Image: $fileName.$ext")
case "svg" => Ok(s"Vector image: $fileName.$ext")
case _ => BadRequest("Unsupported image format")
}
// Combined with path variables
case GET -> Root / "api" / "files" / IntVar(id) ~ "json" =>
Ok(s"JSON file for ID: $id")
}Comprehensive query parameter handling with type safety and validation.
/**
* Query parameter extractor - extracts all query parameters
*/
object :? {
def unapply[F[_]](req: Request[F]): Some[(Request[F], Map[String, collection.Seq[String]])]
}
/**
* Multiple query parameter extractor - allows chaining parameter matchers
*/
object +& {
def unapply(
params: Map[String, collection.Seq[String]]
): Some[(Map[String, collection.Seq[String]], Map[String, collection.Seq[String]])]
}Usage Examples:
import org.http4s.dsl.io._
// Define query parameter matchers
object NameParam extends QueryParamDecoderMatcher[String]("name")
object AgeParam extends OptionalQueryParamDecoderMatcher[Int]("age")
object ActiveParam extends FlagQueryParamMatcher("active")
val routes = HttpRoutes.of[IO] {
// Single query parameter
case GET -> Root / "search" :? NameParam(name) =>
Ok(s"Searching for: $name")
// Multiple query parameters
case GET -> Root / "users" :? NameParam(name) +& AgeParam(maybeAge) =>
val ageStr = maybeAge.map(_.toString).getOrElse("unknown")
Ok(s"User: $name, Age: $ageStr")
// Flag parameter (present/absent)
case GET -> Root / "items" :? ActiveParam(isActive) =>
Ok(s"Show active items: $isActive")
// Access raw query parameters
case GET -> Root / "debug" :? queryParams =>
Ok(s"Query params: $queryParams")
}Type-safe query parameter matchers with validation and error handling.
/**
* Basic query parameter matcher with decoder
*/
abstract class QueryParamDecoderMatcher[T: QueryParamDecoder](name: String) {
def unapply(params: Map[String, collection.Seq[String]]): Option[T]
def unapplySeq(params: Map[String, collection.Seq[String]]): Option[collection.Seq[T]]
}
/**
* Optional query parameter matcher
*/
abstract class OptionalQueryParamDecoderMatcher[T: QueryParamDecoder](name: String) {
def unapply(params: Map[String, collection.Seq[String]]): Option[Option[T]]
}
/**
* Query parameter matcher with default value
*/
abstract class QueryParamDecoderMatcherWithDefault[T: QueryParamDecoder](name: String, default: T) {
def unapply(params: Map[String, collection.Seq[String]]): Option[T]
}
/**
* Flag query parameter matcher (checks presence/absence)
*/
abstract class FlagQueryParamMatcher(name: String) {
def unapply(params: Map[String, collection.Seq[String]]): Option[Boolean]
}
/**
* Multi-value query parameter matcher
*/
abstract class OptionalMultiQueryParamDecoderMatcher[T: QueryParamDecoder](name: String) {
def unapply(
params: Map[String, collection.Seq[String]]
): Option[ValidatedNel[ParseFailure, List[T]]]
}
/**
* Validating query parameter matcher with detailed error reporting
*/
abstract class ValidatingQueryParamDecoderMatcher[T: QueryParamDecoder](name: String) {
def unapply(params: Map[String, collection.Seq[String]]): Option[ValidatedNel[ParseFailure, T]]
}Usage Examples:
import org.http4s.dsl.io._
import cats.data.Validated
// Custom query parameter matchers
object LimitParam extends QueryParamDecoderMatcherWithDefault[Int]("limit", 10)
object TagsParam extends OptionalMultiQueryParamDecoderMatcher[String]("tags")
object SortParam extends ValidatingQueryParamDecoderMatcher[String]("sort")
val routes = HttpRoutes.of[IO] {
// Parameter with default value
case GET -> Root / "items" :? LimitParam(limit) =>
Ok(s"Limit: $limit items")
// Multi-value parameters
case GET -> Root / "posts" :? TagsParam(Validated.Valid(tags)) =>
Ok(s"Tags: ${tags.mkString(", ")}")
case GET -> Root / "posts" :? TagsParam(Validated.Invalid(errors)) =>
BadRequest(s"Invalid tags: ${errors.toList.mkString(", ")}")
// Validating parameters with error handling
case GET -> Root / "data" :? SortParam(Validated.Valid(sortBy)) =>
Ok(s"Sorting by: $sortBy")
case GET -> Root / "data" :? SortParam(Validated.Invalid(errors)) =>
BadRequest(s"Invalid sort parameter: ${errors.head.sanitized}")
}Advanced URI matrix parameter extraction for multi-dimensional resource addressing.
/**
* Matrix parameter extractor for multi-dimensional resource identification
*/
abstract class MatrixVar[F[_]: Foldable](name: String, domain: F[String]) {
def unapplySeq(str: String): Option[Seq[String]]
}Usage Examples:
import org.http4s.dsl.io._
// Define matrix parameter extractor
object BoardVar extends MatrixVar("square", List("x", "y"))
val routes = HttpRoutes.of[IO] {
// Matrix parameters: /board/square;x=5;y=3
case GET -> Root / "board" / BoardVar(IntVar(x), IntVar(y)) =>
Ok(s"Board position: ($x, $y)")
}Complex pattern matching combinations using the conjunction operator.
/**
* Conjunction extractor for combining multiple patterns
*/
object & {
def unapply[A](a: A): Some[(A, A)]
}Usage Examples:
import org.http4s.dsl.io._
// Define custom extractors
object EvenNumber { def unapply(i: Int): Boolean = i % 2 == 0 }
object PositiveNumber { def unapply(i: Int): Boolean = i > 0 }
val routes = HttpRoutes.of[IO] {
// Conjunction matching
case GET -> Root / "numbers" / IntVar(EvenNumber() & PositiveNumber()) =>
Ok("Even and positive number")
case GET -> Root / "numbers" / IntVar(n) =>
Ok(s"Number: $n")
}Extract authentication context from authenticated requests using the as extractor.
/**
* Authentication extractor for AuthedRequest
*/
trait Auth {
object as {
def unapply[F[_], A](ar: AuthedRequest[F, A]): Option[(Request[F], A)]
}
}Usage Examples:
import org.http4s.dsl.io._
import org.http4s.AuthedRequest
// Assuming you have authentication middleware that produces AuthedRequest[IO, User]
val authedRoutes = AuthedRoutes.of[User, IO] {
case GET -> Root / "profile" as user =>
Ok(s"Profile for user: ${user.name}")
case POST -> Root / "posts" as user =>
// Create post for authenticated user
Ok(s"Post created by ${user.name}")
case authedReq @ GET -> Root / "admin" as user if user.isAdmin =>
Ok("Admin panel")
}Path matching provides automatic error handling for common scenarios:
None from extractors, causing route to not matchValidatedNel for detailed error reporting