Interpreter Reuse and Implicits

Explicit Interpreters

In a non-trival application you are likely to have more than one parameter of one method that expects an integer. Instead of defining interpreters inline, as in previous examples, you can define a general interpreter once and use it many places.

sourceimport unfiltered.request._
import unfiltered.response._
import unfiltered.directives._, Directives._

val intValue = data.as.Int.fail { (k,v) =>
  BadRequest ~> ResponseString(
    s"'$v' is not a valid int for $k"
  )
}

unfiltered.jetty.Server.portBinding(binding).plan(
  unfiltered.filter.Planify { Directive.Intent {
    case Path("/") =>
      for {
        a <- intValue named "a"
        b <- intValue named "b"
      } yield ResponseString(
        (a ++ b).sum.toString + "n"
      )
  } }
).run()

In the example above we explicitly reference and apply a single interpreter to parameters “a” and “b”, responding with their sum.

Note

Unclear on the summing step? Values a and b are both of the iterable type Option[Int]. We join these with ++ forming a sequence of integers that could be as long as 2 or as short as 0, depending on the input. The sum method on this sequence does what you’d expect, and finally we join with a string.

Implicit Interpreters

Referencing an interpreter was fairly tidy operation, but as we’ll be using interpreters often and in different applications, naming and recalling names for various types could become tedious. Let’s try it with an implicit.

sourceimplicit val implyIntValue =
  data.as.String ~> data.as.Int.fail { (k,v) =>
    BadRequest ~> ResponseString(
      s"'$v' is not a valid int for $k"
    )
  }

unfiltered.jetty.Server.portBinding(binding).plan(
  unfiltered.filter.Planify { Directive.Intent {
    case Path("/") =>
      for {
        a <- data.as.Option[Int] named "a"
        b <- data.as.Option[Int] named "b"
      } yield ResponseString(
        (a ++ b).sum.toString + "\n"
      )
  } }
).run()

The first thing you may notice is that implyIntValue is a bit wordier than its predecessor. An implicit interpreter used for request parameters must interpret from Seq[String]. Think of it as a full interpretation from the input format to the output type. You may define these for any output types you want, and import them into scope wherever you want to use them. Interpreters like response functions can be chained with ~>, and so data.as.String is typically used at the beginning of an interpreter to be used implicit.

If it seems like extra work to define implicit interpreters, keep reading. The payoff comes with required parameters.

The source code for this page can be found here.