Is it possible to set Response Code from Marshaller in spray.io? - scala

I have following code snippet where dataService returns Option[LocationDataResult].
I would like to set NotFound when dataService returns None and send the data back in case of Some( ...).
I have following code:
val route: Route = {
pathPrefix("service" / "data") {
pathPrefix( "infobox") {
get {
parameters(('mes.as[String], 'oks.as[String])) {
(me, okr) =>
val resp = dataService.ask(GetocationInfoboxData(me,okr)).mapTo[LocationInfoboxDataResult]
.map(remapInfoboxToResponseObject(_)).map { r =>
r match {
case None => StatusCodes.NotFound
case Some(dataToRespond) => dataToRespond
}
}
complete {
resp
}
}
}
}
}
}
implicit val responseMarhaller: Marshaller[LocationInfobox] = Marshaller.of[WikiLocationInfobox](ContentTypes.`application/json`) { (value, contentType, ctx) =>
val result: String = mapper.writeValueAsString(value)
ctx.marshalTo(HttpEntity(contentType, result))
}
I am not able to find a proper way from marshaller and from route via complete function I am not able to make it work.
Could someone more experienced give me a hint? Am I missing some important concept here?
Thx
UPDATE: Error message" Expression of type Future[Object] doesn't conform to expected type ToResponseMarsallable.

The code looks ok, not sure what doesn't work. Try to rewrite it with Spray directives for futures instead of completing the future itself:
val locationData = dataService.ask(GetocationInfoboxData(me,okr)).mapTo[LocationInfoboxDataResult]
onSuccess(locationData.map(remapInfoboxToResponseObject)) {
case None => complete(StatusCodes.NotFound)
case Some(data) => complete(data)
}

This works for me fine taking advantage of MetaMarshallers
val resp = dataService.ask(GetocationInfoboxData(me, okr)).mapTo[LocationInfoboxDataResult].map(remapInfoboxToResponseObject(_)).map{
r =>
r match {
case None => Left(StatusCodes.NotFound)
case Some(data) => Right(data)
}
}
complete(resp)

Related

Play framework ignore following map/flatmap

Is there a way to ignore the following map/flatmap's without failed?
This is what I have:
def delete(serverId: UUID) = authAction.async { implicit request =>
val user = request.user.get
serverService.findByIdAndUserId(serverId, user.id.get)
.flatMap{s =>
if (s.isEmpty) {
Future.failed(new DeleteFailedException)
// Can I return a `NotFound("...")` here instead of failed?
} else {
Future.successful(s.get)
}
}
.map{s =>
serverService.delete(s)
}.map{_ =>
Ok(Json.toJson(Map("success" -> "true")))
}
}
When I would return a NotFound("...") in the flatMap the following map would still be executed. Is there a way to ignore the following map/flatmap's?
Think so should be fine (I assumed that findByIdAndUserId returns Future[Option[_]], not an Option[_] as you answered in comment). In my approach I also removed usage of get and unnecessary map
def delete(serverId: UUID) = authAction.async { implicit request =>
val user = request.user.get
request.user.flatMap(_.id).fold {
Future.successfull(NotFound("no user"))
} {userId =>
serverService.findByIdAndUserId(serverId, userId).map {
case None =>
NotFound("no server")
case Some(s) =>
serverService.delete(s)
Ok(Json.toJson(Map("success" -> "true")))
}
}
}
Instead of doing a Future.failed with an exception. you can return an Either. The good thing about either is that you can pattern match on it and then construct the appropriate http response.
def delete(serverId: UUID) = authAction.async { implicit request =>
val user = request.user.get
serverService.findByIdAndUserId(serverId, user.id.get)
.flatMap{s =>
if (s.isEmpty) {
Left(new DeleteFailedException)
} else {
Right(s.get)
}
}
.map{s => s match {
case Left(notFound) =>
// construct the Json for failure
case Right(s) =>
serverService.delete(s)
// construct json for success.
}}
}

Combining/chaining futures in scala play framework async action

I'm a scala newbie trying to write a Rest Api using play framework. I have the following 3 data access methods
getDataDict: (dsType:String, name:String) => Future[Option[DatasetDictionary]]
getDatasetData: (DatasetDictionary) => Future[List[DatasetData]]
getMetadata: (DatasetDictionary) => Future[List[Metadata]]
I need to use these 3 methods to get the result of my async action method.
def index(dstype:String, name:String, metadata:Option[Boolean]) = Action.async{
/*
1. val result = getDataDict(type, name)
2. If result is Some(d) call getDatasetData
3.1 if metadata = Some(true)
call getMetadata function
return Ok((dict, result, metadata))
3.2 if metadata is None or Some(false)
return Ok(result)
4. If result is None
return BadRequest("Dataset not found")
*/
}
I got the steps 1 and 2 working as follows
def index1(dsType:String, dsName: String, metadata:Option[Boolean]) = Action.async {
getDataDict(dsType, dsName) flatMap {
case Some(x) => getDatasetData(x) map (x => Ok(Json.toJson(x)))
case None => Future.successful(BadRequest("Dataset not found"))
}
}
I'm stuck at how to get the metadata part working.
First of all, it is not very clear (d, result, x) what you really want to return. Hopefully I guessed it correctly:
def index(dstype:String, name:String, metadata:Option[Boolean]) = Action.async {
getDataDict(dstype, name) flatMap {
case Some(datasetDictionary) =>
getDatasetData(datasetDictionary) flatMap { datasetDataList =>
if (metadata == Some(true)) {
getMetadata(datasetDictionary) map { metadataList =>
Ok(Json.toJson((datasetDictionary, datasetDataList, metadataList)))
}
} else {
Future.successful(Ok(Json.toJson(datasetDataList)))
}
}
case None => Future.successful(BadRequest("Dataset not found"))
}
}

missing FromRequestUnmarshaller[Entity] on akka post route

Let me start by saying that i am very new to akka-http, none of the books i have covered the marsheling topic well. So it is bit of a blackbox for me. I was able to obtain the following (Un)Marsheller which is capable of returning both json and protobuf based on a request header.
This part of the code works fine and i have a get route defined in akka-http and it works fine.
trait PBMarshaller {
private val protobufContentType = ContentType(MediaType.applicationBinary("octet-stream", Compressible, "proto"))
private val applicationJsonContentType = ContentTypes.`application/json`
implicit def PBFromRequestUnmarshaller[T <: GeneratedMessage with Message[T]](companion: GeneratedMessageCompanion[T]): FromEntityUnmarshaller[T] = {
Unmarshaller.withMaterializer[HttpEntity, T](_ => implicit mat => {
case entity#HttpEntity.Strict(`applicationJsonContentType`, data) =>
val charBuffer = Unmarshaller.bestUnmarshallingCharsetFor(entity)
FastFuture.successful(JsonFormat.fromJsonString(data.decodeString(charBuffer.nioCharset().name()))(companion))
case entity#HttpEntity.Strict(`protobufContentType`, data) =>
FastFuture.successful(companion.parseFrom(CodedInputStream.newInstance(data.asByteBuffer)))
case entity =>
Future.failed(UnsupportedContentTypeException(applicationJsonContentType, protobufContentType))
})
}
implicit def PBToEntityMarshaller[T <: GeneratedMessage]: ToEntityMarshaller[T] = {
def jsonMarshaller(): ToEntityMarshaller[T] = {
val contentType = applicationJsonContentType
Marshaller.withFixedContentType(contentType) { value =>
HttpEntity(contentType, JsonFormat.toJsonString(value))
}
}
def protobufMarshaller(): ToEntityMarshaller[T] = {
Marshaller.withFixedContentType(protobufContentType) { value =>
HttpEntity(protobufContentType, value.toByteArray)
}
}
Marshaller.oneOf(protobufMarshaller(), jsonMarshaller())
}
}
the issue i am facing is on the post route.
(post & entity(as[PropertyEntity])) { propertyEntity =>
complete {
saveProperty(propertyEntity)
}
}
During compilation time, i get the following error
Error:(20, 24) could not find implicit value for parameter um: akka.http.scaladsl.unmarshalling.FromRequestUnmarshaller[PropertyEntity]
(post & entity(as[PropertyEntity])) { propertyEntity =>
I am not sure exactly what i am missing. Do i need to define an implicit FromRequestUnmarshaller ? if so what should it have?
i was able to hack something together that works for the moment, but i still don't know how to create a general Unmarshaller that can decode any ScalaPB case class
implicit val um:Unmarshaller[HttpEntity, PropertyEntity] = {
Unmarshaller.byteStringUnmarshaller.mapWithCharset { (data, charset) =>
val charBuffer = Unmarshaller.bestUnmarshallingCharsetFor(data)
JsonFormat.fromJsonString(data.decodeString(charBuffer.nioCharset().name()))(PropertyEntity)
/*PropertyEntity.parseFrom(CodedInputStream.newInstance(data.asByteBuffer))*/
}
}
even this i don't know how to have both decoders enabled at the same time. so i have commented one out.

Scala future with akka-http

I need to write simple web service with akka-http and reactivemongo.
Function to save data looks like this
def saveRoute(route: Route):Future[WriteResult] = {
collection.insert(route)
}
a code that calls this function looks like this
val userRoutes = {
logRequestResult("akka-http-microservice") {
path("routes") {
(post & entity(as[Route])) { route =>
Database.saveRoute(route)
}
}
}
}
I need to return result with inserted ID of Route and do this without making the thread to wait.
if try
Database.saveRoute(route).onComplete{
case Success(r) => complete(r.toString)
case Failure(e) => complete(e.getMessage)
}
It cannot compile, because it doesn't return value.
I know how to make it in dirty way, but really want to make in appropriate manner.
What should be done in this case?
Seems like I've found most efficient way to do this. It's built in onComplete directive
(path("routes" / "add") & post & entity(as[Route])) {
route =>
onComplete(routesController.addRoute(route)) {
case Success(result) => complete(StatusCodes.Created, "OK")
case Failure(ex) => complete(new ErrorResponse(StatusCodes.InternalServerError.intValue, ErrorResponse.ERROR, ex.getMessage))
}
}
Use onSuccess to handle the valid response when the future finishes and handleExceptions to handle when the future does not succeed.
val userRoutes = {
handleExceptions(mongoDbExceptionHandler) {
logRequestResult("akka-http-microservice") {
path("routes") {
(post & entity(as[Route])) { route =>
onSuccess(Database.saveRoute(route)) { result =>
complete(result)
}
}
}
}
}
}
// Something like this for whatever the exceptions you expect are
val mongoDbExceptionHandler = ExceptionHandler {
case ex: MongoDbReadException => complete(HttpResponse(InternalServerError, "No database")))
}
onSuccess:
http://doc.akka.io/docs/akka/2.4.9/scala/http/routing-dsl/directives/future-directives/onSuccess.html
handleExceptions:
http://doc.akka.io/docs/akka/2.4.9/scala/http/routing-dsl/exception-handling.html
You can map over the future and then complete the request like below.
val future = Database.saveRoute(route)
val response = future.map(_.getId).recover(_.getMessage)
complete(response)
On a side note, for handling exceptions, it is a good practice to have a ExceptionHandler and wrap it with your route. You can find example here.
You have few option i will try to put the most commonly used ones for REST API based solutions:
OnSuccess use it when you want your expectations to be bubbled and handled by expectionHandler
concat(
path("success") {
onSuccess(Future { "Ok" }) { extraction =>
complete(extraction)
}
},
path("failure") {
onSuccess(Future.failed[String](TestException)) { extraction =>
complete(extraction)
}
}
)
https://doc.akka.io/docs/akka-http/current/routing-dsl/directives/future-directives/onSuccess.html
onComplete: When you want to manually handle the exception. Try Monad wrapped.
val route =
path("divide" / IntNumber / IntNumber) { (a, b) =>
onComplete(divide(a, b)) {
case Success(value) => complete(s"The result was $value")
case Failure(ex) => complete((InternalServerError, s"An error occurred: ${ex.getMessage}"))
}
}
https://doc.akka.io/docs/akka-http/current/routing-dsl/directives/future-directives/onComplete.html
How about this, replace:
Database.saveRoute(route)
with:
complete(Database.saveRoute(route).map(_.toString).recover(_.getMessage))
When you use RequestContext you should use something like this:
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.RouteResult.{Complete, Rejected}
...
val myRoute: Route = (path("my-path") & get) { req: RequestContext =>
val futureResp: Future[HttpResponse] = ???
futureResp.map(resp => RouteResult.Complete(resp))
}

Handle services returning Try values in Spray

I am working on a codebase where calling my Spray API need to synchronously call a service that returns a Try which Spray need to format and return over HTTP.
My initial attempt looked like this :
// Assume myService has a run method that returns a Try[Unit]
lazy val myService = new Service()
val routes =
path("api") {
get {
tryToComplete {
myService.run()
}
}
} ~
path("api" / LongNumber) { (num: Long) =>
get {
tryToComplete {
myService.run(num)
}
}
}
def tryToComplete(result: => Try[Unit]): routing.Route = result match {
case Failure(t) => complete(StatusCodes.BadRequest, t.getMessage)
case Success(_) => complete("success")
}
However this caused myService.run() to be called when the application started. I am not sure why this method was called as there was no HTTP call made.
So I have two questions :
Why is my service being called as part of initialising the routes?
What is the cleanest way to handle this case? Imagine that there are a few other end points following a similar pattern. So I need to be able to handle this consistently.
Even though you have result parameter as call-by-name, it'll immediately get evaluated as you're doing
result match {
For it not to get evaluated, it has to be within the complete, ie your code should look something like (haven't tried to compile this):
def tryToComplete(result: => Try[Unit]): routing.Route = complete {
result match {
case Failure(t) => StatusCodes.BadRequest, t.getMessage
case Success(_) => "success"
}
}
The way I solved this was do do the following :
lazy val myService = new Service()
val routes =
path("api") {
get {
complete {
handleTry {
myService.run()
}
}
}
} ~
path("api" / LongNumber) { (num: Long) =>
get {
complete {
handleTry {
myService.run(num)
}
}
}
}
private def handleTry(operation: Try[_]):HttpResponse = operation match {
case Failure(t) =>
HttpResponse(status = StatusCodes.BadRequest, entity = t.getMessage)
case Success(_) =>
HttpResponse(status = StatusCodes.OK, entity = successMessage)
}