Iron Query Parameters 🔗

Add Iron to your project and import Querson's Iron instances:

import ba.sake.querson.QueryStringRW
import ba.sake.querson.*
import ba.sake.sharaf.*
import ba.sake.sharaf.exceptions.ProblemDetails
import io.github.iltotore.iron.*
import io.github.iltotore.iron.constraint.all.*
import sttp.model.StatusCode

Use a refined type in the query parameter case class:

final case class IronException(errors: Seq[ProblemDetails.ArgumentProblem]) extends Exception

given [A, C](using rw: QueryStringRW[A], constraint: RuntimeConstraint[A, C]): QueryStringRW[A :| C] with {
  override def write(path: String, value: A :| C): QueryStringData = rw.write(path, value)

  override def parse(path: String, data: QueryStringData): A :| C = {
    val value = rw.parse(path, data)
    value.refineEither[C].fold(
      message => throw IronException(Seq(ProblemDetails.ArgumentProblem(path, message, Some(value.toString)))),
      identity
    )
  }
}

type SearchTerm = String :| Not[Blank]
case class SearchQuery(term: SearchTerm) derives QueryStringRW

Install a custom mapper before Sharaf's default JSON mapper:

val exceptionMapper: ExceptionMapper = {
  case error: IronException =>
    Response
      .withBody(
        ProblemDetails(
          StatusCode.UnprocessableEntity.code,
          "Validation errors",
          invalidArguments = error.errors
        )
      )
      .withStatus(StatusCode.UnprocessableEntity)
}.orElse(ExceptionMapper.json)

Pass it to the server and read it with the normal request API:

val query = Request.current.queryParams[SearchQuery]

An invalid refined value produces a 422 Unprocessable Entity validation response.

⬅️ Query Parameters Redirect ➡️