简体   繁体   中英

Akka-HTTP compilation error using custom requireParam() directive

I developed custom generic directive, which will provide param of given type, if it exists, or else reject with my custom exception.

import akka.http.scaladsl.common.NameReceptacle
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.directives.ParameterDirectives.ParamDefAux
import akka.http.scaladsl.server.{Directive1, Route}

class MyCustomException(msg: String) extends Exception(msg)

def requireParam[T](name: NameReceptacle[T])
                   (implicit pdef: ParamDefAux[NameReceptacle[T], Directive1[T]]): Directive1[T] =
  parameter(name).recover { _ =>
    throw new MyCustomException(s"${name.name} is missed!")
  }

It works ok, if I want to create route, using two parameters, for example:

val negSumParams: Route =
  (requireParam("param1".as[Int]) & requireParam("param2".as[Int])) {
    (param1, param2) =>
      complete((-param1-param2).toString)
  }

But if I try to use exactly one parameter, this doesn't compile:

val negParamCompilationFail: Route =
  requireParam("param".as[Int]) {
    param => // scalac complains about missing type param here
      complete((-param).toString)
  }

If I use it with pass directive, it works:

val negParamWithPass: Route =
  (pass & requireParam("param".as[Int])) { // this pass usage looks hacky
    param =>
      complete((-param).toString)
  }

If I write requireParam() return type explicitly, it works too:

val negParamWithExplicitType: Route =
  (requireParam("param".as[Int]): Directive1[Int]) { // DRY violation
    param =>
      complete((-param).toString)
  }

Why do I need these tricks? Why can't it work just with requireParam("param".as[Int]) ?

Scala version 2.12.1, Akka-HTTP 10.0.10.

This error happens due to the Directive companion object apply method. IT allows to create a Route from a function with parameter (T => Route) => Route:

object Directive {

  /**
   * Constructs a directive from a function literal.
   */
  def apply[T: Tuple](f: (T ⇒ Route) ⇒ Route): Directive[T] =
    new Directive[T] { def tapply(inner: T ⇒ Route) = f(inner) }

} 

But the T parameter must be a tuple. In your case, the compiler can not build the Route. Your requireParam("param".as[Int]) returns a Directive1[Int] so the apply method doesn´t work because Int is not a Tuple.

To make this work you shoul use tapply method directly:

(requireParam("param1".as[Int])).tapply((param1) =>
    complete((-param1._1).toString))

and

  val negSumParams2: Route =
    (requireParam("param1".as[Int]) & requireParam("param2".as[Int])).tapply {
      case (param1, param2) =>
        complete((-param1-param2).toString)
    }

So it seems that every Directive tries to convert its param to TupleX. For example:

path("order" / IntNumber) returns a Directive[Tuple1[Int]] instead of Directive1[Int]. In your case requireParam("param1".as[Int]) returns Directive1[Int]

Maybe there is a better solution and to avoid tapply

Johannes from Lightbend answered to this question here: https://groups.google.com/forum/#!topic/akka-user/NmQvcrz5sJg Answer is:

You discovered one of the reasons for the magnet pattern. If you use requireParam("param".as[Int]) { abc => ... } then the { abc => } block is mistaken as the implicit argument of requireParam . So, either you are ok with that and require users to use extra parentheses ( (requireParam("param".as[Int])) { abc => ... } ) or you use the magnet pattern at this level as well. You can read about the magnet pattern on the old spray blog ( http://spray.io/blog/2012-12-13-the-magnet-pattern/ ) or just look into akka-http sources.

A better way to implement the feature would be just to use the existing implementation ;) and install a custom rejection handler that produces whatever output you would like. See https://doc.akka.io/docs/akka-http/10.0.10/scala/http/routing-dsl/rejections.html#customizing-rejection-handling for how to do that.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM