4
votes

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.

1
What is the return type of getRecipeById?M.K.
Future[Option[Recipe]]David Siro
So, what do you get when you run the query for that id? Do you get a None?M.K.
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
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

1 Answers

9
votes

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.