Scala Play: Routes optional parameter with regex? - scala

For one of my routes I have an optional parameter i.e. birthDate: Option[String] and can do this:
GET /rest/api/findSomeone/:firstName/:lastName controllers.PeopleController.findSomeone(firstName: String, lastName: String, birthDate: Option[String])
However, to be more strict with the birthDate optional parameter it would be helpful to specify a regex like this:
$birthDate<([12]\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01]))>
But since this is an optional parameter I can't find a way to do that .. it this covered in Play 2.7.x? I'm faced with the dilemma of making the birthDate parameter non-optional or leaving it unchecked.
As a side note. I had been trying to integrate routes binding of Joda time e.g. org.joda.time.LocalDate by adding the following dependency https://github.com/tototoshi/play-joda-routes-binder "com.github.tototoshi" %% "play-joda-routes-binder" % "1.3.0" but it didn't work in my project as I get compilation errors after integrating it so I stashed that approach away for the time being.

For parsing a date, I wouldn't recommend using a regex based validator at all. Instead, you could - for instance - use a custom case class with a query string binder which will do a type-safe parsing of the incoming parameter:
package models
import java.time.LocalDate
import java.time.format.{DateTimeFormatter, DateTimeParseException}
import play.api.mvc.QueryStringBindable
case class BirthDate(date: LocalDate)
object BirthDate {
private val dateFormatter: DateTimeFormatter = DateTimeFormatter.ISO_DATE // or whatever date format you're using
implicit val queryStringBindable = new QueryStringBindable[BirthDate] {
override def bind(key: String, params: Map[String, Seq[String]]): Option[Either[String, BirthDate]] = {
params.get(key).flatMap(_.headOption).map { value =>
try {
Right(BirthDate(LocalDate.parse(value, dateFormatter)))
} catch {
case _: DateTimeParseException => Left(s"$value cannot be parsed as a date!")
}
}
}
override def unbind(key: String, value: BirthDate): String = {
s"$key=${value.date.format(dateFormatter)}"
}
}
}
Now if you change your routes config so birthDate is a parameter of type Option[BirthDate], you'll get the behaviour you want.
If you're insistent on using regexes, you could use a regex-based parser in place of the date formatter and have BirthDate wrap a String instead of a LocalDate, but for the use case presented I really don't see what the advantage of that would be.
EDIT: just for completeness, the regex-based variant:
case class BirthDate(date: String)
object BirthDate {
private val regex = "([12]\\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01]))".r
implicit val queryStringBindable = new QueryStringBindable[BirthDate] {
override def bind(key: String, params: Map[String, Seq[String]]): Option[Either[String, BirthDate]] = {
params.get(key).flatMap(_.headOption).map { value =>
regex.findFirstIn(value).map(BirthDate.apply).toRight(s"$value cannot be parsed as a date!")
}
}
override def unbind(key: String, value: BirthDate): String = {
s"$key=${value.date}"
}
}
}

Related

Extractor does not return exception

We have the following example and it seems that extractors do not work while converting Json to case class.
import play.api.libs.json.Reads._
import play.api.libs.json._
import play.api.libs.json.Format.GenericFormat
val json: JsValue = Json.parse("""
{
"firstName" : "John",
"lastName" : "Doe"
}
""")
trait BasePublicForm {
def firstName: String
def lastName: String
}
case class CustomerPublicForm(firstName: String, lastName: String) extends BasePublicForm
case class LeadPublicForm(firstName: String, lastName: String ) extends BasePublicForm
object CustomerPublicForm {
implicit val writesPublicLeadFormRequest: Writes[CustomerPublicForm] = Json.writes[CustomerPublicForm]
implicit val readsPublicLeadFormRequest: Reads[CustomerPublicForm] = Json.reads[CustomerPublicForm]
def apply(firstName: String, lastName: String): CustomerPublicForm = {
if(firstName.equalsIgnoreCase("John")) {
throw new Exception("John Exception")
}
new CustomerPublicForm(firstName, lastName)
}
}
object LeadPublicForm {
def apply(firstName: String, lastName: String): LeadPublicForm = {
new LeadPublicForm(firstName, lastName)
}
}
val s = json.validate[CustomerPublicForm] match {
case JsSuccess(form, _) => {
form
// do something with place
}
case e: JsError => {
// error handling flow
throw new Exception("Error")
}
}
s
Link -> https://scastie.scala-lang.org/eZrHTOVkQvSUJmoAJMTXfQ
Any ideas why it does not return Exception as expected?
The code doesn't throw an Exception (which is probably not a good idea anyway), as the json input is valid and so .validate returns CustomerPublicForm(John,Doe)
The Json.reads[CustomerPublicForm] seems to be circumventing your apply method.
I'm not sure of the exact details of Play's macro Reads generation, but it's probably just going straight for your class constructor rather than the apply method. Since the apply method you wrote has the same signature as the existing constructor, you'd be better off if you moved the validation (exception throwing) logic into the constructor and just removing the custom apply.
Alternatively you could make a custom apply with a different signature (not that it makes sense in this case, but I'm speaking generally here), and then using non-macro code to implement the Reads for that class.

GET request parameters to case class in Play & Scala

I have inherited 2 controller methods (for GET requests) that accept the same 10 request parameters like so:
class Application #Inject() (cc: ControllerComponents) extends AbstractController(cc) {
def func1(param1: String,
param2: String,
param3: String
...
param10: String
) = Action {
...
}
def func2(param1: String,
param2: String,
param3: String
...
param10: String
) = Action {
...
}
}
These are mapped like so:
GET /f1 blah.blah.Application.func1(p1: String, p2: String...p10: String)
GET /f2 blah.blah.Application.func2(p1: String, p2: String...p10: String)
I like to avoid the repetition. I am wondering if it is possible to define a case class with 10 fields named after the request parameters, have the controller methods accept one parameter of the case-class-type and have Play match request parameter names to field names and bind the value?
This can be easily achieved if the same values were submitted in a POST request body. But this is not an option as this end-point has been exposed to clients.
Query string binders are used for that. Basically, you tell Play how to parse the parameters, group them to a class and reverse (turn them back to String representation). Let's say you want a Page abstraction:
case class Page(from: Int, to: Int)
implicit def pageQSB(implicit intBinder: QueryStringBindable[Int]) = new QueryStringBindable[Page] {
override def bind(key: String, params: Map[String, Seq[String]]): Option[Either[String, Page]] = {
for {
from <- intBinder.bind("from", params)
to <- intBinder.bind("to", params)
} yield {
(from, to) match {
case (Right(from), Right(to)) => Right(Page(from, to))
case _ => Left("Unable to bind a Page")
}
}
}
override def unbind(key: String, page: Page): String = {
intBinder.unbind("from", page.from) + "&" + intBinder.unbind("to", page.to)
}
}
Note that you have to import these implicits to routes scope (in your build.sbt), e.g.
routesImport += "utils.MyBinders._"

Marshalling nested custom Objects in Sangria

I have the following Input Objects:
val BusinessInputType = InputObjectType[BusinessInput]("BusinessInput", List(
InputField("userId", StringType),
InputField("name", StringType),
InputField("address", OptionInputType(StringType)),
InputField("phonenumber", OptionInputType(StringType)),
InputField("email", OptionInputType(StringType)),
InputField("hours", ListInputType(BusinessHoursInputType))
))
val BusinessHoursInputType = InputObjectType[BusinessHoursInput]("hours", List(
InputField("weekDay", IntType),
InputField("startTime", StringType),
InputField("endTime", StringType)
))
And here are my models with custom Marshalling defined:
case class BusinessInput(userId: String, name: String, address: Option[String], phonenumber: Option[String], email: Option[String], hours: Seq[BusinessHoursInput])
object BusinessInput {
implicit val manual = new FromInput[BusinessInput] {
val marshaller = CoercedScalaResultMarshaller.default
def fromResult(node: marshaller.Node) = {
val ad = node.asInstanceOf[Map[String, Any]]
System.out.println(ad)
BusinessInput(
userId = ad("userId").asInstanceOf[String],
name = ad("name").asInstanceOf[String],
address = ad.get("address").flatMap(_.asInstanceOf[Option[String]]),
phonenumber = ad.get("phonenumber").flatMap(_.asInstanceOf[Option[String]]),
email = ad.get("email").flatMap(_.asInstanceOf[Option[String]]),
hours = ad("hours").asInstanceOf[Seq[BusinessHoursInput]]
)
}
}
}
case class BusinessHoursInput(weekDay: Int, startTime: Time, endTime: Time)
object BusinessHoursInput {
implicit val manual = new FromInput[BusinessHoursInput] {
val marshaller = CoercedScalaResultMarshaller.default
def fromResult(node: marshaller.Node) = {
val ad = node.asInstanceOf[Map[String, Any]]
System.out.println("HEY")
BusinessHoursInput(
weekDay = ad("weekDay").asInstanceOf[Int],
startTime = Time.valueOf(ad("startTime").asInstanceOf[String]),
endTime = Time.valueOf(ad("endTime").asInstanceOf[String])
)
}
}
}
My question is, When I have a nested InputObject that has custom Marshalling, I dont see the marshalling of BusinessHoursInput getting invoked before the BusinessInput is marshalled. I noticed this because the print statement of "Hey" is never executed before the print statement of "ad" in BusinessInput. This causes problems later down the road for me when I try to insert the hours field of BusinessInput in the DB because it cannot cast it to BusinessHoursInput object. In Sangria, is it not possible to custom Marshal nested Objects before the parent Object is marshalled?
You are probably are using BusinessInput as an argument type. The actual implicit lookup takes place at the Argument definition time and only for BusinessInput type.
Since FromInput is a type-class based deserialization, you need to explicitly define the dependency between deserializers of nested object. For example, you can rewrite the deserializer like this:
case class BusinessInput(userId: String, name: String, address: Option[String], phonenumber: Option[String], email: Option[String], hours: Seq[BusinessHoursInput])
object BusinessInput {
implicit def manual(implicit hoursFromInput: FromInput[BusinessHoursInput]) = new FromInput[BusinessInput] {
val marshaller = CoercedScalaResultMarshaller.default
def fromResult(node: marshaller.Node) = {
val ad = node.asInstanceOf[Map[String, Any]]
BusinessInput(
userId = ad("userId").asInstanceOf[String],
name = ad("name").asInstanceOf[String],
address = ad.get("address").flatMap(_.asInstanceOf[Option[String]]),
phonenumber = ad.get("phonenumber").flatMap(_.asInstanceOf[Option[String]]),
email = ad.get("email").flatMap(_.asInstanceOf[Option[String]]),
hours = hoursFromInput.fromResult(ad("hours").asInstanceOf[Seq[hoursFromInput.marshaller.Node]])
)
}
}
}
In this version, I'm taking advantage of existing FromInput[BusinessHoursInput] to deserialize BusinessHoursInput from the raw input.
Also as an alternative, you can avoid defining manual FromInput deserializers altogether by taking advantage of existing JSON-based deserializers. For example, in most cases, circe's automatic derivation works just fine. You just need these 2 imports (in the file where you are defining the arguments):
import sangria.marshalling.circe._
import io.circe.generic.auto._
Those import put appropriate FromInput instances into the scope. These instances take advantage of circe's own deserialization mechanism.
import io.circe.Decoder
import io.circe.generic.semiauto.deriveDecoder
import sangria.macros.derive.deriveInputObjectType
import sangria.marshalling.circe._
import sangria.schema.{Argument, InputObjectType}
object XXX {
// when you have FromInput for all types in case class (Int, String) you can derive it
case class BusinessHoursInput(weekDay: Int, startTime: String, endTime: String)
object BusinessHoursInput {
implicit val decoder: Decoder[BusinessHoursInput] = deriveDecoder
implicit val inputType: InputObjectType[BusinessHoursInput] = deriveInputObjectType[BusinessHoursInput]()
}
// the same here, you need InputObjectType also for BusinessHoursInput
case class BusinessInput(userId: String, name: String, address: Option[String], phonenumber: Option[String], email: Option[String], hours: Seq[BusinessHoursInput])
object BusinessInput {
implicit val decoder: Decoder[BusinessInput] = deriveDecoder
implicit val inputType: InputObjectType[BusinessInput] = deriveInputObjectType[BusinessInput]()
}
// for this to work you need to have in scope InputType BusinessInput and FromInput for BusinessInput
// FromInput you can get by having Decoder in scope and import sangria.marshalling.circe._
private val businessInputArg = Argument("businessInput", BusinessInput.inputType)
}
id you do not use circe but different json library you should have of course different typeclasses and proper import in scope

Scala play implicit path binder for two path variables

I want to write implicit PathBinder for this url /repo/:owner/:name and I my controller should be like this:
case class GitHubRepositoryId(owner: String, name: String)
def get(repoId: GitHubRepositoryId) = {}
Is it possible to write one ? From play docs I cannot find solution for that. Only QueryStringBindable can access multiple variables from URL and construct POJO from those.
Thank in advance
Change your route to GET /repo/*repoId controllers.Controller.get(repoId: GitHubRespositoryId)
Then define the PathBindable so that it manually parses out the / between owner and name. Something like this:
implicit val pathBinder = new PathBindable[GitHubRepositoryId] {
override def bind(key: String, value: String): Either[String, GitHubRepositoryId] = {
val parts = value.split('/')
if (parts.size != 2) {
Left("Not found")
} else {
Right(GitHubRepositoryId(parts(0), parts(1)))
}
}
override def unbind(key: String, repoId: GitHubRepositoryId): String = {
s"${repoId.owner}/${repoId.name}"
}
}

Adding functionality before calling constructor in extra constructor

Is it possible to add functionality before calling constructor in extra constructor in scala ?
Lets say, I have class User, and want to get one string - and to split it into attributes - to send them to the constructor:
class User(val name: String, val age: Int){
def this(line: String) = {
val attrs = line.split(",") //This line is leading an error - what can I do instead
this(attrs(0), attrs(1).toInt)
}
}
So I know I'm not able to add a line before sending to this, because all constructors need to call another constructor as the first statement of the constructor.
Then what can I do instead?
Edit:
I have a long list of attributes, so I don't want to repeat line.split(",")
I think this is a place where companion object and apply() method come nicely into play:
object User {
def apply(line: String): User = {
val attrs = line.split(",")
new User(attrs(0), attrs(1).toInt)
}
}
class User(val name: String, val age: Int)
Then you just create your object the following way:
val u1 = User("Zorro,33")
Also since you're exposing name and age anyway, you might consider using case class instead of standard class and have consistent way of constructing User objects (without new keyword):
object User {
def apply(line: String): User = {
val attrs = line.split(",")
new User(attrs(0), attrs(1).toInt)
}
}
case class User(name: String, age: Int)
val u1 = User("Zorro,33")
val u2 = User("Zorro", "33")
Ugly, but working solution#1:
class User(val name: String, val age: Int){
def this(line: String) = {
this(line.split(",")(0), line.split(",")(1).toInt)
}
}
Ugly, but working solution#2:
class User(val name: String, val age: Int)
object User {
def fromString(line: String) = {
val attrs = line.split(",")
new User(attrs(0), attrs(1).toInt)
}
}
Which can be used as:
val johny = User.fromString("johny,35")
You could use apply in place of fromString, but this will lead to a confusion (in one case you have to use new, in the other you have to drop it) so I prefer to use different name
Another ugly solution:
class User(line: String) {
def this(name: String, age: Int) = this(s"$name,$age")
val (name, age) = {
val Array(nameStr,ageStr) = line.split(",")
(nameStr,ageStr.toInt)
}
}
But using a method of the companion object is probably better.