Error when use akka http unmarshall entity as case class with default value - scala

Error found when I send a http post request:
The request content was malformed: No usable value for gender Did
not find value which can be converted into java.lang.String
My request body:
{
"name":"test"
}
Route in my scala code:
path("test"){
(post(entity(as[People]) { req =>
val resp = queryData(req)
complete(resp.meta.getOrElse("statusCode", 200).asInstanceOf[Int] -> resp)
}))
} ~
Code for People:
case class People(name: String, gender: String = "male")
Why still get the malformed error ???

Even though you put a default value, the extraction of the Json will look for that field, and it is not present there, so it will fail.
(I am assuming you are using spray-json as it's the default one in akka-http)
In order to avoid the issue, while keeping it simple, I would recommend you to create a case class for the request to create people, which contains an Option[String] for that field, and you can then convert the PeopleCreateRequest to a People easily.
case class PeopleCreateRequest(name: String, gender: Option[String])
That will work nicely with the framework...
Alternatively, if you want to keep the design that way, you'll need to look into implementing your own JsonFormat[People] which will treat this value as optional but add a default value when missing.
Look into spray-json https://github.com/spray/spray-json#providing-jsonformats-for-other-types
But I imagine it would be something like:
implicit val peopleFormat = new RootJsonFormat[People] {
def read(json: JsValue): People = json match {
case JsArray(Seq(JsString(name), JsString(gender))) =>
People(name, gender)
case JsArray(Seq(JsString(name))) =>
People(name)
case _ => deserializationError("Missing fields")
}
def write(obj: People): JsValue = ???
}
I am normally using different JsonSupport, using circe, but hopefully this gives you direction to solve your issue

Related

Akka-http: How do I map response to object

Not sure if I'm getting this whole routing DSL thing right but here's the question. I want to do a post to external service such as:
val post = pathPrefix("somePath") {
post {
//get the response mapped to my Output object
}
}
Then I want the response (which is a Json) to be mapped to an object matching the fields for example Output (assuming I have my JsonProtocol set up). How is this done?
You are using HTTP server directives to "retrieve" something "externally". This is what typically an HTTP client does.
For this sort of things, you can use akka http client api.
For example:
val response = Http().singleRequest(HttpRequest(uri = "http://akka.io"))
response onComplete {
case Success(res) =>
val entity = Unmarshal(res.entity).to[YourDomainObject]
// use entity here
case Failure(ex) => // do something here
}
However, this requires some Unmarshaller (to deserialize the received json). Take also a look at Json Support, as it helps you define marshallers easily:
case class YourDomainObject(id: String, name: String)
implicit val YourDomainObjectFormat = jsonFormat2(YourDomainObject)
I think what you are trying to ask is how to get the body i.e in JSOn format to the Case class that you have
Here is a quick example:
path("createBot" / Segment) { tag: String =>
post {
decodeRequest {
entity(as[CaseClassName]) { caseclassInstance: CaseClassName =>
val updatedAnswer = doSomeStuff(caseclassInstance)
complete {
"Done"
}
}
}
You can find more detailed example from here : https://github.com/InternityFoundation/Stackoverflowbots/blob/master/src/main/scala/in/internity/http/RestService.scala#L56
I hope it answers your question.

Play Json framework can't parse json string if some field doesn't exist

I defined a case class Fruit:
case class Fruit(name: String, weight: Double)
I wrote an action to save a fruit
def saveFruit = Action(parse.json) { request: Request[JsValue] =>
import models.Implicits._
val json = request.body
val fruitResult = Json.fromJson[Fruit](json)
if (fruitResult.isSuccess) {
val fruit = fruitResult.get
println(fruit)
Ok(JsString("1"))
} else {
Ok(JsString("2"))
}
}
If the request body is { "name":"ABC","weight":10}, then the action will be successfully called.
If the request body is { "name":"ABC"},then an error occurs,complaining that the weight is not undefined on object
{ "name":"ABC"} is a valid json string that can't parsed as a Fruit and weight will be null
I am not sure how to fix this issue.
I am using Play 2.6.0
I strongly suggest not going down the rabbit hole of allowing null anywhere. If you start allowing this in your request objects, you will never know whether there's a null anywhere in your models.
The typical way to approach this is to set this to Option as was suggested in the comments.
If you're working on a more elaborate solution, it might make sense to create dedicated request objects with lots of Options for values where the client might not send a value. You would then map/transform the request objects into your domain model in the controller and validate the input accordingly.
For example:
case class FruitRQ(name: String, weight: Option[Double])
case class Fuit(name: String, weight: Double)
class MyController {
def myAction = {
val fruitRq = Json.fromJson[FruiteRQ](request.body)
(for (weight <- fruitRq.weight) yield {
val fruit = Fruit(fruitRq.name, weight)
// do something with the validate fruit
}).getOrElse {
// return 4xx to client saying that a field is missing
}
}
}

Play2 - validation of dynamic part of route(s)

I'm building an API, that takes in a variable path parameter, or dynamic part of the route, as the play documentation would specify it.
I would like to validate this as to give the client a proper response.
I have the following route setup
GET /:dynamic/all controller.method(dynamic: String)
The dynamic param for the method is used across the API, for multiple methods, so i would like to get some kind of global validation/whitelist of acceptable strings. (eg: "hello"/"hi" would be accepted, and "noooway" would not be accepted, and i would return a 404 not found as response.
I would preferably like my controller method to not contain any validation so that this would be true:
def method(dynamic: String): Action[AnyContent] = Action.async { _ =>
//I already know "dynamic" is valid here.
Future.successful(Ok(Json.toJson(Map("status" -> "OK"))))
}
Instead of: (excuse my javaisc-psuedo-code)
def method(dynamic: String): Action[AnyContent] = Action.async { _ =>
val valid = Helper.validate(dynamic)
if (!valid) return some result/response else
Future.successful(Ok(Json.toJson(Map("status" -> "OK"))))
}
Play allows you to do this by different ways.
1. PathBindable
You can implement a PathBindable[T] for any type T, so that your value extracted from the path of the request is not a simple String but a T.
If you are ready to change the type of dynamic (which would make sense, since it is not supposed to be just any string but a valid one), you could do the following:
case class Validated(str: String) {
assert(Helper.validate(str))
}
object Validated {
implicit val pathBindable = new PathBindable[Validated] {
val string = implicitly[PathBindable[String]]
override def bind(key: String, value: String): Either[String, Validated] =
string.bind(key, value). // bind as if it were a string
right.filter(Helper.validate).getOrElse(Left("Invalid input")). // filter using your validation function, and give error message
right.map(Validated(_)) // encapsulate in your new type
override def unbind(key: String, value: Validated): String =
string.unbind(key, value.str) //unbind as if it were a string
}
}
Note that you need to implement unbind for reverse routing (get a path for a given action call).
Now, you just need to replace String in your router and in your controller by your.package.Validated.
GET /:dynamic/all controller.method(dynamic: your.package.Validated)
NB: if you want to use the simple name of your class, you need to import it in your build.sbt:
(project in file(".").
enablePlugins(PlayScala).
settings(routesImport += "your.package.Validated")
2. Action Composition
You can also implement an action filter to be used whenever your input needs to be validated:
case class ValidatedAction(input: String) extends ActionFilter[Request] {
override protected def filter[A](request: Request[A]): Future[Option[Result]] = Future.successful{
if (Helper.validate(input)) None else Some(BadRequest("Invalid input"))
}
}
def method(dynamic: String) = (Action andThen ValidatedAction(dynamic)).async {
Future.successful(Ok)
}
The code inside the async block will be executed only if the filter method returns None, otherwise, it will return the specified Result (here, BadRequest("Invalid input").

Akka Http - How to Unmarshall ResponseEntity to CustomClass?

I am using Akka Http to make requests to a 3rd party API. The responses are "application/json", and I would like to use Akka Http to convert them to a custom case class. I would like to do something like this:
val request = RequestBuilding.Get("https://service.com/v1/api/items")
val response : Future[ItemsResponse] = http.singleRequest(request).flatMap({ response =>
Unmarshal(response.entity).to[ItemsResponse]
})
This fails to compile, because I am missing an implicit unmarshaller of type akka.http.scaladsl.unmarshalling.Unmarshaller[akka.http.scaladsl.model.ResponseEntity, com.mycompany.models.ItemsResponse].
It's unclear to me what the idiomatic way to do this with akka http is. I am aware that I could use spray-json, but I'd like to understand how to do this without importing another library. It seems possible with Akka Http, but the documentation isn't clear (to me at least).
The simplest way is to use spray-json as it comes as part of Akka HTTP:
import spray.json._
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport
// change 2 to the number of attributes of ItemsResponse
implicit val ItemsResponseFormat = jsonFormat2(ItemsResponse)
This should make your existing code compile.
I think your question is valid, and there are cases where avoiding extra dependencies makes sense. Mine is from making an authentication library, where I don't want to impose my JSON library preferences to the users of such library. The library needs JSON unmarshalling for understanding a token info response.
To the code! :)
case class TokenInfo private (uid: String, realm: String, scope: Seq[String])
object TokenInfo {
private
def parseOpt(s: String): Option[TokenInfo] = {
util.parsing.json.JSON.parseFull(s) match {
case Some(map: Map[String,Any] #unchecked) =>
val tmp: Map[String,Any] = map.collect {
case (k# "uid",x: String) => k -> x
case (k# "realm",x: String) => k -> x
case (k# "scope",x: Seq[String] #unchecked) => k -> x
// other keys are ignored
}.toMap
if (tmp.size == 3) {
Some( TokenInfo( tmp("uid").asInstanceOf[String], tmp("realm").asInstanceOf[String], tmp("scope").asInstanceOf[Seq[String]]) )
} else {
None
}
case _ => None
}
}
implicit
val unm: FromEntityUnmarshaller[TokenInfo] = {
PredefinedFromEntityUnmarshallers.stringUnmarshaller.map{ s => parseOpt(s).getOrElse{
throw new RuntimeException(s"Unknown TokenInfo: $s")
}}
}
}
I chose to use util.parsing.json which comes within Scala. The other option was simply regex's, but in that case I'm either fixing the expected order of fields, or the code might get complex.

Routes parameter transformation throws on parsing failure String -> Int

Router config using Play parameter transformation:
GET /search controllers.Search.query(page: Option[Int])
while search?page=123 works as expected, visiting search?page=asd throws
We're using our own wrappers to contain failures Try { param.toInt }.toOption but is there another way to cleanly contain parse exceptions in routes configuration or Action block?
Also: does anyone see parameter transformation is represented in the Play source?
Change Option[Int] to Option[String]
inside the action
def search(param: Option[String]) = Action { req =>
val page = param.flatMap { Try { _.toInt }.toOption }
doSomething(page).map { Ok(_) }
}
if you use Option[Int] parsing of query params happens at play routes level and play will handle the exception for you and you will not have control on it. So use Option[String] and parse String in the Action to anything you would like to see
use withParamAction to avoid duplicate code
def withParamAction(f: Option[Int] => Request[AnyContent] => Result)(paramName: String = "page"):Result = Action { req =>
f(req.getQueryString(paramName).flatMap { Try { _.toInt }.toOption })(req)
}
extending or writing a custom int bindable should be what is required in your situation customIntBinder
If you want to parse Query string to Complex objects use QueryStringBindlein play. Here is the example code from play docs.
https://www.playframework.com/documentation/2.5.x/ScalaRequestBinders#QueryStringBindable