4

I'm trying to achieve very simple thing.

Say, I have a REST API. When I call

/api/recipe/1

I'd like to a resource as a json to be returned.

When I hit

/api/recipe/2

a 404 Not Found HTTP response should be returned. Simple as that.

Clearly I'm missing something about how routing directives work, as I'm not able to compose them to respect above-mentioned logic.

Unfortunately I was unable to find any concrete example and official documentation isn't particularly helpful.

I'm trying something like this but the code gives compilation error:

class RecipeResource(recipeService: RecipeService)(implicit executionContext: ExecutionContext) extends DefaultJsonProtocol {

  implicit val recipeFormat = jsonFormat1(Recipe.apply)

  val routes = pathPrefix("recipe") {
    (get & path(LongNumber)) { id =>
      complete {
        recipeService.getRecipeById(id).map {
          case Some(recipe) => ToResponseMarshallable(recipe)
          // type mismatch here, akka.http.scaladsl.marshalling.ToResponseMarshallable 
          // is required
          case None => HttpResponse(StatusCodes.NotFound)
        }
      }
    }
  }
}

Update

Here's the code of recipeService for greater clarity:

class RecipeService(implicit executionContext: ExecutionContext) {

  def getRecipeById(id: Long): Future[Option[Recipe]] = {
    id match {
      case 1 => Future.successful(Some(Recipe("Imperial IPA")))
      case _ => Future.successful(None)
    }
  }
}

The compilation error I get:

[error] /h......../....../...../RecipeResource.scala:22: type mismatch;
[error]  found   : scala.concurrent.Future[Object]
[error]  required: akka.http.scaladsl.marshalling.ToResponseMarshallable
[error]         recipeService.getRecipeById(id).map {
[error]                                             ^
[error] one error found
[error] (compile:compileIncremental) Compilation failed

Update 2

Based on leachbj's answer I got rid of needless pattern matching in the route. Now the code compiles and looks like this:

class RecipeResource(recipeService: RecipeService)(implicit executionContext: ExecutionContext) extends DefaultJsonProtocol {

  implicit val recipeFormat = jsonFormat1(Recipe.apply)

  val routes = pathPrefix("recipe") {
    (get & path(LongNumber)) { id =>
      complete(recipeService.getRecipeById(id))
    }
  }
}

When the recipe exists (e.g. /api/recipe/1) I get the JSON response and 200 OK, which is expected.

Now, in case of the non-existent resource (e.g. /api/recipe/2) the response in empty, but 200 OK status code is received.

My question is, how can I tweak akka-http to be able to complete(Future[None[T]]) that would return 404 Not found.

I'm looking for a generic approach that would work for any Future[None] return value.

David Siro
  • 1,826
  • 14
  • 33
  • What is the return type of `getRecipeById`? –  Aug 19 '16 at 22:48
  • ``Future[Option[Recipe]]`` – David Siro Aug 19 '16 at 23:00
  • So, what do you get when you run the query for that id? Do you get a None? –  Aug 19 '16 at 23:01
  • I've just a simple stub that returns ``Some`` in case of id is 1 and ``None`` otherwise. The code has been added to the post. – David Siro Aug 19 '16 at 23:06
  • What's the compiler error? Can you post that? –  Aug 19 '16 at 23:07
  • Basically it expects ``akka.http.scaladsl.marshalling.ToResponseMarshallable`` as return type of the closure. The compiler output has been added to the post. – David Siro Aug 19 '16 at 23:14
  • 1
    You're returning two incompatible types. You should have an implicit marshaller in scope. Calling `ToResponseMarshallable` is the smell I'm smelling here. EDIT: i just had a look at some of my Akka-Http code...I return a tuple of `StatusCode` -> `response` and don't futz with ToResponseMarshallable at all. – Chris Martin Aug 20 '16 at 07:46
  • Could you eventually post some routing code that conditionally returns: a) an object that is later marshaled into response, b) the empty 404 response? That would help a lot, cheers. – David Siro Aug 20 '16 at 09:33

1 Answers1

9

If you complete(Future[Option[T]]) and there is a suitable Json Marshaller available the Akka will return the response as json if the value is Some(v) or an empty 200 response for None. If you use spray-json create a RootJsonFormat[T] implicit and add import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._. There are similar support implicits for other marshalling libraries.

To generate a 404 for None you need to wrap the complete with the rejectEmptyResponse directive.

leachbj
  • 161
  • 2
  • Thanks, I did refactor the code as suggested. Now in case of ``Some`` I receive JSON and 200 OK, but in case of ``None``, I get obviously no response, but still 200 OK. Now I wonder if this is somehow default in akka-http or whether it can be adjusted. – David Siro Aug 25 '16 at 18:47
  • Please see updated text, the rejectEmptyResponse directive is what you need. – leachbj Aug 26 '16 at 00:39