Reputation: 14307
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.
Upvotes: 2
Views: 1245
Reputation: 1828
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}"
}
}
}
Upvotes: 2