or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

authentication.mdhttp-methods-routing.mdindex.mdpath-matching.mdquery-parameters.mdstatus-codes-responses.md
tile.json

query-parameters.mddocs/

Query Parameter Processing

Type-safe query parameter extraction and validation with support for optional parameters, multi-value parameters, custom decoders, and validation with detailed error reporting.

Capabilities

Query Parameter Extraction

Basic Query Parameter Extractor (:?)

Fundamental query parameter extractor that provides access to all query parameters.

/**
 * Query parameter extractor
 * Extracts request and its query parameters as a Map
 */
object :? {
  def unapply[F[_]](req: Request[F]): Some[(Request[F], Map[String, collection.Seq[String]])]
}

Usage Examples:

val routes = HttpRoutes.of[IO] {
  // Access all query parameters
  case GET -> Root / "search" :? params =>
    val query = params.get("q").flatMap(_.headOption).getOrElse("")
    Ok(s"Search query: $query")
    
  // Pattern match with specific extractors
  case GET -> Root / "users" :? Limit(limit) +& Offset(offset) =>
    Ok(s"Limit: $limit, Offset: $offset")
}

Parameter Combination (+&)

Combinator for extracting multiple query parameters from the same request.

/**
 * Multiple parameter extractor combinator
 * Allows chaining multiple parameter extractors
 */
object +& {
  def unapply(params: Map[String, collection.Seq[String]]): 
    Some[(Map[String, collection.Seq[String]], Map[String, collection.Seq[String]])]
}

Usage Examples:

object Limit extends QueryParamDecoderMatcher[Int]("limit")
object Offset extends QueryParamDecoderMatcher[Int]("offset")
object SortBy extends QueryParamDecoderMatcher[String]("sort")

val routes = HttpRoutes.of[IO] {
  // Combine multiple parameters
  case GET -> Root / "users" :? Limit(limit) +& Offset(offset) =>
    Ok(s"Pagination: limit=$limit, offset=$offset")
    
  // Chain many parameters
  case GET -> Root / "search" :? Query(q) +& Limit(limit) +& SortBy(sort) =>
    Ok(s"Search: $q, limit: $limit, sort: $sort")
}

Query Parameter Matchers

Basic Query Parameter Decoder Matcher

Type-safe query parameter extraction with automatic type conversion.

/**
 * Basic query parameter matcher with type conversion
 * Uses QueryParamDecoder for type-safe conversion
 */
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]]
}

Usage Examples:

// Define parameter matchers
object UserId extends QueryParamDecoderMatcher[Int]("user_id")
object Active extends QueryParamDecoderMatcher[Boolean]("active")
object Tags extends QueryParamDecoderMatcher[String]("tags")

val routes = HttpRoutes.of[IO] {
  // Single value extraction
  case GET -> Root / "user" :? UserId(id) =>
    Ok(s"User ID: $id")
    
  // Boolean parameters
  case GET -> Root / "users" :? Active(isActive) =>
    Ok(s"Active users only: $isActive")
    
  // Multiple values of same parameter
  case GET -> Root / "posts" :? Tags.unapplySeq(tags) =>
    Ok(s"Tags: ${tags.mkString(", ")}")
}

Query Parameter Matcher with Implicit QueryParam

Simplified matcher that uses implicit QueryParam for parameter name resolution.

/**
 * Query parameter matcher using implicit QueryParam for name resolution
 */
abstract class QueryParamMatcher[T: QueryParamDecoder: QueryParam]
  extends QueryParamDecoderMatcher[T](QueryParam[T].key.value)

Usage Examples:

// Define parameter with implicit QueryParam
case class Limit(value: Int)
implicit val limitParam: QueryParam[Limit] = QueryParam.fromKey("limit")
implicit val limitDecoder: QueryParamDecoder[Limit] = 
  QueryParamDecoder[Int].map(Limit.apply)

object LimitMatcher extends QueryParamMatcher[Limit]

val routes = HttpRoutes.of[IO] {
  case GET -> Root / "data" :? LimitMatcher(limit) =>
    Ok(s"Limit: ${limit.value}")
}

Optional Query Parameters

Optional Query Parameter Decoder Matcher

Handle optional query parameters that may or may not be present.

/**
 * Optional query parameter matcher
 * Returns Some(Some(value)) if parameter present and valid
 * Returns Some(None) if parameter absent
 * Returns None if parameter present but invalid
 */
abstract class OptionalQueryParamDecoderMatcher[T: QueryParamDecoder](name: String) {
  def unapply(params: Map[String, collection.Seq[String]]): Option[Option[T]]
}

Usage Examples:

object OptionalLimit extends OptionalQueryParamDecoderMatcher[Int]("limit")
object OptionalSort extends OptionalQueryParamDecoderMatcher[String]("sort")

val routes = HttpRoutes.of[IO] {
  case GET -> Root / "users" :? OptionalLimit(limitOpt) +& OptionalSort(sortOpt) =>
    val limit = limitOpt.getOrElse(10)
    val sort = sortOpt.getOrElse("name")
    Ok(s"Limit: $limit, Sort: $sort")
}

Query Parameter Matcher with Default Value

Provide default values for missing query parameters.

/**
 * Query parameter matcher with default value
 * Returns default value if parameter is missing
 * Returns None if parameter is present but invalid
 */
abstract class QueryParamDecoderMatcherWithDefault[T: QueryParamDecoder](name: String, default: T) {
  def unapply(params: Map[String, collection.Seq[String]]): Option[T]
}

abstract class QueryParamMatcherWithDefault[T: QueryParamDecoder: QueryParam](default: T)
  extends QueryParamDecoderMatcherWithDefault[T](QueryParam[T].key.value, default)

Usage Examples:

object LimitWithDefault extends QueryParamDecoderMatcherWithDefault[Int]("limit", 10)
object PageWithDefault extends QueryParamDecoderMatcherWithDefault[Int]("page", 1)

val routes = HttpRoutes.of[IO] {
  case GET -> Root / "data" :? LimitWithDefault(limit) +& PageWithDefault(page) =>
    Ok(s"Page: $page, Limit: $limit")
    // ?limit=20&page=3 -> "Page: 3, Limit: 20"
    // ?page=2 -> "Page: 2, Limit: 10" (default limit)
    // (no params) -> "Page: 1, Limit: 10" (both defaults)
}

Multi-Value Query Parameters

Optional Multi-Value Query Parameter Matcher

Handle query parameters that can appear multiple times.

/**
 * Multi-value query parameter matcher
 * Handles parameters that appear multiple times in the query string
 * Returns Validated result with all values or parse errors
 */
abstract class OptionalMultiQueryParamDecoderMatcher[T: QueryParamDecoder](name: String) {
  def unapply(params: Map[String, collection.Seq[String]]): 
    Option[ValidatedNel[ParseFailure, List[T]]]
}

Usage Examples:

import cats.data.Validated
import cats.data.Validated.{Invalid, Valid}

object Tags extends OptionalMultiQueryParamDecoderMatcher[String]("tag")
object Ids extends OptionalMultiQueryParamDecoderMatcher[Int]("id")

val routes = HttpRoutes.of[IO] {
  // Handle multiple tag parameters: ?tag=scala&tag=http4s&tag=web
  case GET -> Root / "posts" :? Tags(tagResult) =>
    tagResult match {
      case Valid(tags) => Ok(s"Tags: ${tags.mkString(", ")}")
      case Invalid(errors) => BadRequest(s"Invalid tags: ${errors.toList.mkString(", ")}")
    }
    
  // Multiple ID parameters: ?id=1&id=2&id=3
  case GET -> Root / "users" :? Ids(idResult) =>
    idResult match {
      case Valid(ids) => Ok(s"User IDs: ${ids.mkString(", ")}")
      case Invalid(_) => BadRequest("Invalid user IDs")
    }
}

Validating Query Parameters

Validating Query Parameter Matcher

Get detailed validation results with error information.

/**
 * Validating query parameter matcher
 * Returns validation result with detailed error information
 */
abstract class ValidatingQueryParamDecoderMatcher[T: QueryParamDecoder](name: String) {
  def unapply(params: Map[String, collection.Seq[String]]): 
    Option[ValidatedNel[ParseFailure, T]]
}

Usage Examples:

import cats.data.Validated.{Invalid, Valid}

object ValidatingAge extends ValidatingQueryParamDecoderMatcher[Int]("age")
object ValidatingEmail extends ValidatingQueryParamDecoderMatcher[String]("email")

val routes = HttpRoutes.of[IO] {
  case GET -> Root / "user" :? ValidatingAge(ageResult) =>
    ageResult match {
      case Valid(age) if age >= 0 && age <= 150 => 
        Ok(s"Valid age: $age")
      case Valid(age) => 
        BadRequest(s"Age out of range: $age")
      case Invalid(errors) => 
        BadRequest(s"Invalid age format: ${errors.head.sanitized}")
    }
}

Optional Validating Query Parameter Matcher

Combine optional parameters with validation.

/**
 * Optional validating query parameter matcher
 * Returns None if parameter absent, Some(validation result) if present
 */
abstract class OptionalValidatingQueryParamDecoderMatcher[T: QueryParamDecoder](name: String) {
  def unapply(params: Map[String, collection.Seq[String]]): 
    Some[Option[ValidatedNel[ParseFailure, T]]]
}

Usage Examples:

object OptionalValidatingLimit extends OptionalValidatingQueryParamDecoderMatcher[Int]("limit")

val routes = HttpRoutes.of[IO] {
  case GET -> Root / "data" :? OptionalValidatingLimit(limitOpt) =>
    limitOpt match {
      case None => Ok("No limit specified")
      case Some(Valid(limit)) if limit > 0 => Ok(s"Limit: $limit")
      case Some(Valid(limit)) => BadRequest("Limit must be positive")
      case Some(Invalid(errors)) => BadRequest(s"Invalid limit: ${errors.head.sanitized}")
    }
}

Flag Query Parameters

Boolean Flag Query Parameter Matcher

Handle boolean flag parameters (present/absent).

/**
 * Boolean flag query parameter matcher
 * Returns true if parameter is present (regardless of value)
 * Returns false if parameter is absent
 */
abstract class FlagQueryParamMatcher(name: String) {
  def unapply(params: Map[String, collection.Seq[String]]): Option[Boolean]
}

Usage Examples:

object DebugFlag extends FlagQueryParamMatcher("debug")
object VerboseFlag extends FlagQueryParamMatcher("verbose")

val routes = HttpRoutes.of[IO] {
  case GET -> Root / "status" :? DebugFlag(debug) +& VerboseFlag(verbose) =>
    val message = (debug, verbose) match {
      case (true, true) => "Debug and verbose mode enabled"
      case (true, false) => "Debug mode enabled"
      case (false, true) => "Verbose mode enabled"
      case (false, false) => "Normal mode"
    }
    Ok(message)
    // ?debug -> debug=true, verbose=false
    // ?debug&verbose -> debug=true, verbose=true
    // (no flags) -> debug=false, verbose=false
}

Custom Query Parameter Decoders

You can create custom decoders for complex parameter types:

// Custom case class
case class SortOrder(field: String, direction: String)

// Custom decoder
implicit val sortOrderDecoder: QueryParamDecoder[SortOrder] =
  QueryParamDecoder[String].emap { str =>
    str.split(":") match {
      case Array(field, direction) if Set("asc", "desc").contains(direction) =>
        Right(SortOrder(field, direction))
      case _ =>
        Left(ParseFailure("Invalid sort format, expected 'field:direction'", ""))
    }
  }

object SortOrderParam extends QueryParamDecoderMatcher[SortOrder]("sort")

val routes = HttpRoutes.of[IO] {
  // ?sort=name:asc or ?sort=date:desc
  case GET -> Root / "users" :? SortOrderParam(sort) =>
    Ok(s"Sort by ${sort.field} ${sort.direction}")
}