Search code examples
scalaakkaakka-http

How do I use two Directives with OR in Akka-HTTP?


I want to create a complex directive in Akka HTTP where I can use the query /api/guitar?id=42 or the path /api/guitar/42. I did use two directives and it worked. However I would like to use everything in one directive where I can benefit from the | operator. So, I have this code:

import akka.actor.ActorSystem
import akka.http.scaladsl.Http
import akka.http.scaladsl.model.{ContentTypes, HttpEntity}
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.Route

object ComplexDirectives {
  def main(args: Array[String]): Unit = {
    implicit val system = ActorSystem("ComplexDirectives")
    val complexRoute: Route = path("api" / "guitar") {
      (path("" / IntNumber) | parameter('id.as[Int])) { (itemNumber: Int) =>
        get {
          println(s"I found the guitar $itemNumber")
          complete(HttpEntity(
            ContentTypes.`text/html(UTF-8)`,
            s"""
               |<html>
               | <body>I found the guitar $itemNumber!</body>
               |</html>
               |""".stripMargin
          ))
        }
      }
    }
    println("http GET localhost:8080/api/guitar?id=43")
    println("http GET localhost:8080/api/guitar/42")
    Http().newServerAt("localhost", 8080).bindFlow(complexRoute)
  }
}

when I use a HTTP GET command http GET localhost:8080/api/guitar?id=43 I get the guitar with id 43. But when I use the command http GET localhost:8080/api/guitar/43 I cannot get the guitar. I also changed the directive to be only (path(IntNumber) | parameter('id.as[Int])) but does not work.


Solution

  • Your issue is that you are trying to concatenate paths. In order to do that you need to use pathPrefix. Try to do:

    val complexRoute: Route = pathPrefix("api" / "guitar") {
      (path(IntNumber) | parameter('id.as[Int])) { (itemNumber: Int) =>
        get {
          println(s"I found the guitar $itemNumber")
          complete(HttpEntity(
            ContentTypes.`text/html(UTF-8)`,
            s"""
               |<html>
               | <body>I found the guitar $itemNumber!</body>
               |</html>
               |""".stripMargin
          ))
        }
      }
    }
    

    Please note that when declaring such path, more paths might be valid. For example, http://localhost:8080/api/guitar/abc?id=43 is valid because it has a prefix of api/guitar and a parameter id.

    If you want to block those paths, you need to use pathEnd or pathEndOrSingleSlash in the following way:

    (path(IntNumber) | (pathEndOrSingleSlash & parameter('id.as[Int])))
    

    which will support both http://localhost:8080/api/guitar/?id=43 and http://localhost:8080/api/guitar?id=43

    Or:

    (path(IntNumber) | (pathEnd & parameter('id.as[Int])))
    

    which will support only http://localhost:8080/api/guitar?id=43