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
Related
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
I have a service bean that I wrote and it looks like this
def myStream(name: String): HttpEntity.Streamed = {
}
In my controller I am able to use it as:
def myControllerHandler(id: Name) = Action {
Ok.sendEntity(myStream(id))
}
I want to change the signature of my service method as
def myStream(name: String):Future[HttpEntity.Streamed] = {
}
Then in my controller, the below code fails to compile, and I am unable to find a way to make it work
def downloadLocalBackup(name: String) = Action {
myStream(name) map { fileStream =>
Ok.sendEntity(fileStream)
}
}
The reason this fails is because Action can take Result type but not Future[Result].
How do I deal with it?
Use Action.async:
def downloadLocalBackup(name: String) = Action.async {
myStream(name) map { fileStream =>
Ok.sendEntity(fileStream)
}
}
From play documentation:
While we were using the Action.apply builder method to build actions
until now, to send an asynchronous result we need to use the
Action.async builder method:
https://www.playframework.com/documentation/2.6.x/ScalaAsync
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").
I want to create a simple helper function that will return a Option[String] based on the posted form's key.
Currently I am doing this:
(request.body.asFormUrlEncoded.get("key1")(0))
I want to make a function that returns an Option[String], which I can then cast to Int or Boolean if required.
Accessing (0) runs the risk of a NullPointerException or IndexOutOfBoundsException.
How about declaring this method in your controller:
def findKey(key:String)(implicit request:Request[AnyContent]):Option[String] = {
request.body.asFormUrlEncoded.flatMap { form =>
form.get(key).flatMap { values =>
values.headOption
}
}
}
which you can then use quite neatly in your handler function:
def workWithFormFields = Action.async { implicit request =>
val maybeKey1:Option[String] = findKey("key1")
...
}
I want to implement a BodyParser which parses and validates request.body, it's based on parse.json and currently looks like this:
def parseModel[A](implicit reads: Reads[A]): BodyParser[JsResult[A]] =
parse.json.map(_.validate[A])
The problem is it is currently of type BodyParser[JsResult[A]] while I want it to be of type BodyParser[A]. In case of JsError I want it basically to return 400 Bad Request with validation errors.
In Play API docs I can't find a method which allows me to inspect result of previous body parser and return a result or continue to a controller.
A BodyParser, after parsing the body, produces an Either[SimpleResult, A], where SimpleResult is an error result that should be returned immediately instead of processing the action. The convenience methods on BodyParser don't let you do this, so instead, create a new body parser that delegates to the JSON body parser:
def validateJson[A](implicit reads: Reads[A]) = new BodyParser[A] {
def apply(request: RequestHeader) = parse.json(request).map(_.right.flatMap { json =>
json.validate[A].asEither.left.map(e => BadRequest(JsError.toFlatJson(e)))
})
}
You can see here that we're mapping the result of the parse, and then taking the right value (a successful parse, will be JsValue), and calling flatMap on it. Our flatMap method converts the JsResult from validate to an Either[JsError, A], so we're halfway there with A, after that we map the left JsError to SimpleResult, and we're good to go.
Ok, I've implemented the desired behaviour via a method which produces an Action:
def validateJson[A](implicit reads: Reads[A]) =
parse.json.map(_.validate[A])
def ModelAction[A](action: A => Result)(implicit r: Reads[A]) =
Action(validateJson[A]) { request =>
request.body match {
case JsSuccess(model, _) => action(model)
case JsError(e) => BadRequest(JsError.toFlatJson(e))
}
}
I can use it like this:
def create = ModelAction[MyModel] { model =>
???
}
I'm still interested if it's possible to do the same with BodyParser and if I need to do so or it's better as it is now?