Path parsing for ReST API in Spray

116 Views Asked by At

I have following code in Spray to parse the REST service called by user:

val route = {
  pathPrefix("v0") {
    pathPrefix("pets") {
      pathEndOrSingleSlash {
        pathEnd {
          get {
            complete("/v0/pets")
          }
        } ~
        get {
          complete("/v0/pets/")
        }
      } ~
      parameters('id ?) {
        id =>
          get {
            complete("/v0/pets?id=" + id)
          }
      }
    }
  }
}

The problem is that it is not behaving as expected. I am expecting following behaviour from the code:

http://127.0.0.1/v0/pets => /v0/pets
http://127.0.0.1/v0/pets/ => /v0/pets/
http://127.0.0.1/v0/pets?id=1234 = > /v0/pets?id=Some(1234)

But, I am getting following results for the queries:

http://127.0.0.1/v0/pets => /v0/pets
http://127.0.0.1/v0/pets/ => /v0/pets
http://127.0.0.1/v0/pets?id=1234 => /v0/pets
2

There are 2 best solutions below

2
Gabriele Petronella On
pathPrefix("v0") {
  pathPrefix("pets") {
    pathEndOrSingleSlash {
      get {
      }
    }
  }
}

matches http://127.0.0.1/v0/pets?id=1234.

This is why

GET /v0/pets?id=1234
 ^   ^   ^  ^
 |___|___|__|____ get  
     |   |  |
     |   |  |
     |   |  |__ pathEndOrSlash
     |   |  
     |   |__ pathPrefix("pets")
     |
     |__ pathPrefix("v0")

You need to intercept the parameter before. Try

val route = {
  pathPrefix("v0") {
    pathPrefix("pets") {
      parameters('id ?) {
        id =>
          get {
            complete("/v0/pets?id=" + id)
          }
      } ~
      pathEndOrSingleSlash {
        pathEnd {
          get {
            complete("/v0/pets")
          }
        } ~
        get {
          complete("/v0/pets/")
        }
      }
    }
  }
}
0
mfirry On

One of the issue here is that since id is declared as optional your parameter route handles both the presence and the absence of it (and that is handling v0/pets).

One way of doing it (and I also assume you only want to handle get requests).

You basically just declare id not optional on your parameter route:

val route = get {
  pathPrefix("v0") {
    pathPrefix("pets") {
      pathEnd {
        parameter('id) { id =>
          complete("/v0/pets?id=" + id)
        } ~ complete("/v0/pets")
      } ~
      pathSingleSlash {
        complete("/v0/pets/")
      }
    }
  }
}

Another way basically just exploits the fact that id is an optional parameter, so you just pattern match on the Option:

val route = {
  pathPrefix("v0") {
    pathPrefix("pets") {
      pathEnd {
        get {
          parameters('id ?) { id =>
            id match {
              case Some(id) => complete("/v0/pets?id=" + id)
              case None => complete("/v0/pets")
            }
          }
        }
      } ~
      pathSingleSlash {
        get {
          complete("/v0/pets/")
        }
      }
    }
  }
}