2
votes

I am new to spray and I am trying to write a custom directive. I would like the directive to reject the request if the header value is not valid otherwise leave the request alone.

I've tried to absorb this page: http://spray.io/documentation/1.1.2/spray-routing/key-concepts/directives/

Specifically, the part about the responder chain. I'm trying to create something at the level of the bar Directive in the illustration. I'm just not getting how to pass the context unchanged to the inner route.

My else block below is not correct but expresses what I am trying to do. I just can't figure out how to implement it.

Any help would be greatly appreciated.

trait ApiKeyDirective {
    import spray.routing.directives.HeaderDirectives._
    import spray.routing.directives.BasicDirectives._

    def validateApiKey(): Directive1 = {

       headerValueByName("api-key") {key =>
           val valid = key == "123"
           if (!valid) reject() else pass
       }
    }
}

object ApiKeyDirective extends ApiKeyDirective
2

2 Answers

2
votes

You can combine

headerValueByName:

def headerValueByName(headerName: String): Directive1[String]

with validate:

def validate(check: ⇒ Boolean, errorMsg: String): Directive0

For example:

  def validateApiKey(route: Route) =
    headerValueByName("api-key") { key =>
      validate(key == "123", "Invalid API key") {
        route
      }
    }

or without validate:

  def validateApiKey(route: Route) =
    headerValueByName("api-key") { key =>
      if (key == "123")
        route
      else
        reject(ValidationRejection("Invalid API key"))
    }

Usage:

lazy val route = ...
    ... ~
    pathPrefix("test_directive") {
      get {
        validateApiKey {
          complete("ok")
        }
      }
    } ~
    ...

Test from cmd/shell:

# curl http://localhost:8080/test_directive
Request is missing required HTTP header 'api-key'

# curl http://localhost:8080/test_directive -H 'api-key: bad'
Invalid API key

# curl http://localhost:8080/test_directive -H 'api-key: 123'
"ok"
0
votes

I'm just not getting how to pass the context unchanged to the inner route.

Spray does that for you!

Your code is mostly correct, there are just 2 simple problems to fix! Firstly, you need to flatMap headerValueByName("api-key") directive. Secondly, the return type will be Directive0 because the directive won't provide any value.

So final code would look like this:

object ApiKeyDirective {
  import spray.routing.Directives._

  val validateApiKey: Directive0 =
    headerValueByName("api-key").flatMap { key =>
      val valid = key == "123"
      if (!valid) reject() else pass
    }

}

Also, I recommend you to add a custom rejection to reject() block so that API users will be informed when their api key is invalid.