How to validate optional query parameters in Play Framework? - scala

So I have a routes file that looks something like this:
GET /myRes controllers.MyController.get(ids: Option[String], elems: Option[String])
All well and good. Users can get stuff by doing:
/myRes
/myRes?ids=X
/myRes?elems=Y
/myRes?ids=X&elems=Y
However, they can also query the interface by doing:
/myRes?id=X
Which is problematic, because in this case the user is gets the same result as if they had queried /myRes, which is almost certainly not the result they expected. This has been causing a lot of confusion/bugs for developers of the API. Is there an elegant way to catch incorrect/unspecified query parameters being passed to the controller and return a hard error for such queries?
Edit: Changed title to something more descriptive. My problem is basically validating the query parameters to catch any query parameters passed to the API which are not valid/correct.

It can be done with the help of a macro annotation like the following one:
import scala.reflect.macros.whitebox.Context
import scala.language.experimental.macros
import scala.annotation.StaticAnnotation
import scala.annotation.compileTimeOnly
import play.api.mvc._
#compileTimeOnly("respond 400 bad request in case of unexpected params")
class StrictParams extends StaticAnnotation {
def macroTransform(annottees: Any*): Any = macro StrictParamsMacro.impl
}
object StrictParamsMacro {
def impl(c: Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
import c.universe._
annottees.map(_.tree).toList match {
case q"def $name(..$params) = $action { ..$body }" :: Nil =>
val supportedParamNames = params.map(ab => ab.name.toString).toSet
c.Expr[Any](
q"""def $name(..$params) = { req: Request[_] =>
val unsupportedParams = req.queryString.keySet -- $supportedParamNames
if (unsupportedParams.nonEmpty) {
BadRequest(unsupportedParams.mkString("Unsupported Params: ", ", ", ""))
} else {
$body
}
}"""
)
}
}
}
Then you can annotate your action method like this:
#StrictParams
def get(ids: Option[String], elems: Option[String]) = Action {
...
}

i usually pass it like this on get method
GET /getSomething Controllers.Application.getData()
GET /getSomething/:id Controllers.Application.getData(id:Integer)
GET /getSomething/:id/:name Controllers.Application.getData(id:Integer, name :String)

You can define a QueryStringBindable[A] to bind a Map of query string parameters to an instance of type A.
See the corresponding documentation.
Didn't fully explore it yet, but it doesn't seem too difficult to implement a QueryStringBindable[Option[A]].
If you want to forbid confusing parameters (e.g. allowing ids but not id), you can check for unexpected keys in the parameters Map and return an error message if needed (although I would recommand to accept the id key to match the behaviour expected by users).

Related

Macros/Shapeless to control string value in method

I'm creating a DSL where users have a given when then, where they pass a string with the action they want.
All those actions are controlled using some regular expressions.
Now using shapeless or macros I would like to control what users compones using the DSL, and in case the string does not fit the string as we expect make the code don't compile.
Si for instance
Given("a good action") -> fine
When("wrong string action") -> compilation error
Can anybody please point me to a good blog or documentation of how to achieve this?
Regards
After the solution of Gabriele now I´m facing a design issue. This is my vaidator class
object DSLValidator {
import scala.language.experimental.macros
def Given(message: String): Any = macro checkActionImpl
def When(message: String): Any = macro checkActionImpl
def Then(message: String): Any = macro checkActionImpl
def And(message: String): Any = macro checkActionImpl
def checkActionImpl(c: blackbox.Context)(message: c.Tree): c.Tree = {
import c.universe._
def isValidAction(s: String): Boolean = checkMessage(s)
message match {
case _tree#Literal(Constant(s: String)) if isValidAction(s) => _tree
case _ => c.abort(c.enclosingPosition, "Invalid action for DSL. Check the allowed actions in RegexActions")
}
}
val PARAMS = "(.*)=(.*)"
val PAYLOAD_VALUE_REGEX = s"^Payload $PARAMS".r
def checkMessage(action: String): Boolean = {
action match {
case PAYLOAD_VALUE_REGEX(c, c1) => true
case "Make a request to server" => true
case _ => false
}
}
}
And if I invoke the Given/When/Then from another class with wrong arguments the compiler complain as we expect.
#Test
def TestDSL(): Unit = {
DSLValidator.Given("Make a request to wrong")--> not compiling
DSLValidator.When("Payload code=works") --> compiling
println("End")
}
But now thinking the whole idea of was not only to check the values on compilation time but also execute the logic of the Given/When/then
And I realize than when I use macros in a function there´s nothing else that we can do so.
def Given(message: String): Any = {
macro checkActionImpl
//My bussiness logic
}
is not compiling, so my question is, where can I put now the business logic and where the invocation of the macro function to continue working.
Also using a static class as bridge does not work and wont compile since the string message does not fit.
object Macros {
def Given(message: String) = {
DSLValidator.Given(message)
//Logic here
println("Given")
}
}
Macros.Given("Make a request to wrong")
Any idea?
Regards
I think shapeless cannot help you in this case, so a macro would be the way to go.
Here's a skeleton for a macro you can start from:
import scala.language.experimental.macros
def Given(message: String): /* the return type of Given */ = macro givenImpl
def givenImpl(c: Context)(message: c.Tree): c.Tree = {
import c.universe._
def isValid(s: String): Boolean = ??? // your validation logic here
message match {
case case t # Literal(Constant(s: String)) if isValid(s) => t
case _ => c.abort(c.enclosingPosition, "Invalid message")
}
}
In order to fully understand this I can recommend you to read:
http://docs.scala-lang.org/overviews/macros/overview.html
https://blog.scalac.io/2016/02/25/def-hello-macro-world.html

Either[String,Unit] - is it idiomatic ? Is there a more idiomatic type for this?

What to return on success in add ?
Currently I return Unit, is there a more idiomatic way ?
I mean, the Either[String, Unit] does not feel right, because Right is meant to return a value, since it has a type parameter.
The method can fail or complete with success, however when it completes with success then there is nothing to return, so I just return Right(). I wonder what is the idiomatic way to describe such situation?
What would be a good type to represent this situation ?
import scala.collection.immutable.HashMap
import scala.concurrent.ExecutionContext
object UUID{
def apply():UUID= UUID(java.util.UUID.randomUUID().toString)
}
case class UUID(id:String) case class Ref[T](c:Class[T], id:UUID) {
override def equals(that:Any)=id.equals(that)
override def hashCode()=id.hashCode()
}
case class RefVal[T](r:Ref[T],v:T)
package container {
import scala.concurrent.Future
trait MapContainer[T] {
var map: HashMap[Ref[T], RefVal[T]] = HashMap[Ref[T], RefVal[T]]();
private[container] def add(rv: RefVal[T]): Future[Either[String, Unit]] = Future
{
if (!map.contains(rv.r)) {
map = map updated(rv.r, rv)
Right()
} else Left("add, class already exists with this uuid :" + rv.r.c)
}
private[container] def notExposed=println("cannot run this from outside package 'model'")
def delete(r:Ref[T]) : Future[Either[String,Unit]]= Future {
if (map.contains(r))
{
map = map - r
Right()
}
else Left(r+"element not found")
}
...
}
I think a more idiomatic way would be :
Create a custom exception class for your exception case (arguable)
Return an Either[Throwable, Map] in the add method, returning the modified map on the right side
Btw, you can use codereview.stackexchange for, well, code review needs :)
Edit : as #massg pointed, at this point, Try[Map] has exactly the semantic of Either[Throwable, Map], and is indeed more weel suited

Could someone explain how is the this code executing?

How is the createUser or updateUser using the unmarshalValueJs function and passing arguments.
package controllers.user
import play.api._
import play.api.mvc._
import play.api.libs.json._
import play.api.libs.json.Reads._
import play.api.libs.functional.syntax._
import services.user.UserServiceComponent
import domain.user.User
trait UserController extends Controller {
self: UserServiceComponent =>0
def emailAlreadyExists(implicit reads: Reads[String]) =
Reads[String](js => reads.reads(js).flatMap { e =>
userService.tryFindByEmail(e).map(_ => JsError("error.custom.emailAlreadyExists")).getOrElse(JsSuccess(e))
})
implicit val userReads = (__ \ "email").read[String](email andKeep emailAlreadyExists)
.map(resource => UserResource(resource))
implicit val userWrites = new Writes[User] {
override def writes(user: User): JsValue = {
Json.obj(
"id" -> user.id,
"email" -> user.email
)
}
}
What create User passes to the unmarshalJsValue?
Where does resource come from?
def createUser = Action(parse.json) {request =>
unmarshalJsValue(request) { resource: UserResource =>
val user = User(Option.empty, resource.email)
userService.createUser(user)
Created
}
}
def updateUser(id: Long) = Action(parse.json) {request =>
unmarshalJsValue(request) { resource: UserResource =>
val user = User(Option(id), resource.email)
userService.updateUser(user)
NoContent
}
}
def findUserById(id: Long) = Action {
val user = userService.tryFindById(id)
if (user.isDefined) {
Ok(Json.toJson(user))
} else {
NotFound
}
}
def deleteUser(id: Long) = Action {
userService.delete(id)
NoContent
}
What is R over here?
What get passed back to the createUser?
def unmarshalJsValue[R](request: Request[JsValue])(block: R => Result)(implicit rds : Reads[R]): Result =
request.body.validate[R](rds).fold(
valid = block,
invalid = e => {
val error = e.mkString
Logger.error(error)
BadRequest(error)
}
)
}
case class UserResource(email: String)
R is a type variable. The definition def unmarshalJsValue[R](request: Request[JsValue])(block: R => Result)(implicit rds : Reads[R]) reads:
I'm a method called unmarshallJsValue.
I require a type parameter that could potentially be anything.
I require a value of Request[JsValue] (presumably a request contianin a JSON entity).
I require a function that, given some value of type R, produces a Result.
I require a value to be in implicit scope that is a Reads for type R. In other words, I need a way to convert a JsValue to an R, whatever R might happen to be. (If you look at the docs for Reads, you'll notice that its only method, reads, has this effect.)
To put it all together, entire function just says, give me some block of code that produces a Result from some piece of data I know how to convert to from JSON. The body of the function simply attempts to convert the JSON to the desired type (known as unmarshalling). If this succeeds, it runs the block. Otherwise, you get a BadRequest client error.
The compiler can't know whether the JSON that gets provided in real life by the client will be convertible to some type R, but it can require a JSON -> R converter, and some error handling code if it fails on real data.

required: spray.httpx.marshalling.ToResponseMarshallable Error

Hey im pretty new to Spray and reactive mongo .
Im trying to return a list of result as json but i'm having some issue with converting the result to list of json.
this is my model
import reactivemongo.bson.BSONDocumentReader
import reactivemongo.bson.BSONObjectID
import reactivemongo.bson.Macros
case class Post(id: BSONObjectID, likes: Long, message: String, object_id: String, shares: Long)
object Post {
implicit val reader: BSONDocumentReader[Post] = Macros.reader[Post]
}
the Mongo method
def getAll(): Future[List[Post]] ={
val query = BSONDocument(
"likes" -> BSONDocument(
"$gt" -> 27))
collection.find(query).cursor[Post].collect[List]()
}
and this is the route
val route1 =
path("posts") {
val res: Future[List[Post]]= mongoService.getAll()
onComplete(res) {
case Success(value) => complete(value)
case Failure(ex) => complete(ex.getMessage)
}
}
error
type mismatch; found : List[com.example.model.Post] required: spray.httpx.marshalling.ToResponseMarshallable
thanks,
miki
You'll need to define how a Post will be serialized, which you can do via a spray-json Protocol (see the docs for more detailed information). It's quite easy to do so, but before that, you'll also need to define a format for the BSONObjectId type, since there's no built-in support for that type in spray-json (alternatively, if object_id is a string representation of the BSONObjectId, think about removing the id property from your Post class or change it to be a String):
// necessary imports
import spray.json._
import spray.httpx.SprayJsonSupport._
implicit object BSONObjectIdProtocol extends RootJsonFormat[BSONObjectID] {
override def write(obj: BSONObjectID): JsValue = JsString(obj.stringify)
override def read(json: JsValue): BSONObjectID = json match {
case JsString(id) => BSONObjectID.parse(id) match {
case Success(validId) => validId
case _ => deserializationError("Invalid BSON Object Id")
}
case _ => deserializationError("BSON Object Id expected")
}
}
Now, we're able to define the actual protocol for the Post class:
object PostJsonProtocol extends DefaultJsonProtocol {
implicit val format = jsonFormat5(Post.apply)
}
Furthermore, we'll also need to make sure that we have the defined format in scope:
import PostJsonProtocol._
Now, everything will compile as expected.
One more thing: have a look at the docs about the DSL structure of spray. Your mongoService.getAll() isn't within a complete block, which might not reflect your intentions. This ain't an issue yet, but probably will be if your route get more complex. To fix this issue simply put the future into the onComplete call or make it lazy:
val route1 =
path("posts") {
onComplete(mongoService.getAll()) {
case Success(value) => complete(value)
case Failure(ex) => complete(ex.getMessage)
}
}

How to transform submitted json in Play 2.0?

I'm trying to build a REST API using play 2.0. I have a User case class that contains some fields (like username & password) that shouldn't be updatable by the updateMember method.
Is there a good, functional way, of dealing with multiple Options somehow, because request.body.asJson returns an Option[JsValue], and my user lookup also returns an Option:
package controllers.api
import org.joda.time.LocalDate
import play.api.Play.current
import play.api.db.slick.{DB, Session}
import play.api.mvc._
import play.api.libs.json._
import play.api.libs.functional.syntax._
import models.{Gender, User, UserId}
import repositories.UserRepository
object Member extends Controller {
def updateMember(id: Long) = Action {
DB.withSession {
implicit session: Session =>
val json: JsValue = request.body.asJson // how to deal with this?
val repository = new UserRepository
repository.findById(new UserId(id)).map {
user =>
def usernameAppender = __.json.update(
__.read[JsObject].map { o => o ++ Json.obj("username" -> user.username) }
)
json.transform(usernameAppender) // transform not found
Ok("updated")
}.getOrElse(NotFound)
}
}
}
I could move the map call to where I try to parse the request, but then inside there I guess I'd need another map over the user Option like I already have. So in that style, I'd need a map per Option.
Is there a better way of dealing with multiple Options like this in FP?
You're basically dealing with a nested monad, and the main tool for working with such is flatMap, particularly if both options being None has the same semantic meaning to your program:
request.body.asJson.flatMap { requestJson =>
val repository = new UserRepository
repository.findById(new UserId(id)).map { user =>
def usernameAppender = __.json.update(
__.read[JsObject].map { o => o ++ Json.obj("username" -> user.username) }
)
requestJson.transform(usernameAppender)
Ok("updated") // EDIT: Do you not want to return the JSON?
}
}.getOrElse(NotFound)
But it might also be the case that your Nones have different meanings, in which case, you probably just want to pattern match, and handle the error cases separately:
request.body.asJson match {
case Some(requestJson) =>
val repository = new UserRepository
repository.findById(new UserId(id)).map { user =>
def usernameAppender = __.json.update(
__.read[JsObject].map { o => o ++ Json.obj("username" -> user.username) }
)
requestJson.transform(usernameAppender)
Ok("updated")
}.getOrElse(NotFound)
case None => BadRequest // Or whatever you response makes sense for this case
}