Reputation: 1498
I have ServerA which exposes an API method for a client, which looks like this:
def methodExposed()= Action.async(json) { req =>
val reqAsModel = request.body.extractOpt[ClientRequestModel]
reqAsModel match {
case Some(clientRequest) =>
myApiService
.doSomething(clientRequest.someList)
.map(res => ???)
case None =>
Future.successful(BadRequest("could not extract request"))
}
}
So, I have a case class for the client request and if I cannot extract it from the request body, then I return a BadRequest
with the message and otherwise I call an internal apiService
to perform some action with this request.
doSomething
performs an API call to ServerB that can return 3 possible responses:
doSomething
looks like this:
def doSomething(list: List[String]) = {
wSClient.url(url).withHeaders(("Content-Type", "application/json")).post(write(list)).map { response =>
response.status match {
case Status.BAD_REQUEST =>
parse(response.body).extract[ServiceBResponse]
case Status.INTERNAL_SERVER_ERROR =>
val ex = new RuntimeException(s"ServiceB Failed with status: ${response.status} body: ${response.body}")
throw ex
}
}
}
Now I have two issues:
200
returns with no body and 400 has a body, I don't know what should be the return type of doSomething
methodExposed
?Upvotes: 4
Views: 7266
Reputation: 12212
I would do something like this:
case class ServiceBResponse(status: Int, body: Option[String] = None)
And then, doSomething
would be like:
def doSomething(list: List[String]) = {
wSClient.url(url).withHeaders(("Content-Type", "application/json")).post(write(list)).map { response =>
response.status match {
case Status.OK =>
ServiceBResponse(response.status)
case Status.BAD_REQUEST =>
ServiceBResponse(response.status, Option(response.body))
case Status.INTERNAL_SERVER_ERROR =>
val message = s"ServiceB Failed with status: ${response.status} body: ${response.body}"
ServiceBResponse(response.status, Option(message))
}
}
}
Finally, inside the controller:
def methodExposed() = Action.async(json) { req =>
val reqAsModel = request.body.extractOpt[ClientRequestModel]
reqAsModel match {
case Some(clientRequest) =>
myApiService
.doSomething(clientRequest.someList)
.map(serviceBResponse => Status(serviceBResponse.status)(serviceBResponse.getOrElse("")))
case None =>
Future.successful(BadRequest("could not extract request"))
}
}
Another alternative is directly use WSResponse
:
def doSomething(list: List[String]) = {
wSClient
.url(url)
.withHeaders(("Content-Type", "application/json"))
.post(write(list))
}
And the controller:
def methodExposed() = Action.async(json) { req =>
val reqAsModel = request.body.extractOpt[ClientRequestModel]
reqAsModel match {
case Some(clientRequest) =>
myApiService
.doSomething(clientRequest.someList)
.map(wsResponse => Status(wsResponse.status)(wsResponse.body))
case None =>
Future.successful(BadRequest("could not extract request"))
}
}
Upvotes: 1
Reputation: 4794
If 400 is a common expected error, I think the type Future[Either[Your400CaseClass, Unit]]
makes sense. In terms of how methodExposed
returns the result to the client depends on your business logic:
You should throw an exception (using Future.failed) if doSomething
returns 500 (or more generally, any unexpected http code).
(Lastly, I hope you're not using 500 to communicate a 'normal' error like validation / authentication error. 5xx code should only be used for exceptional and unrecoverable errors. Most http clients I know will throw an exception immediately when a 5xx is encountered, which means the user won't get the chance to handle it)
Upvotes: 0