javascalafunctional-programmingplayframework-2.6

How to pass optional parameters while accessing web service API in play framework?


I am new to play framework and trying to integrate a microservice. I am using WSClient to call a web service. In order to call a POST rest API, I am passing request body which contains some required parameters and some optional. My question is how to pass Optional parameters here?

My Code sample is

def add = Action.async { implicit request =>
    UserDataForm.bindFromRequest.fold(
      profileForm => {
        val response = profileForm.errorsAsJson
        Future(BadRequest(response).as("application/json"))
      },
      userData => try {
        ws.url("http://localhost:9000/" + "api/profile")
          .post(
            Map("userId" -> userData.userId, "firstName" -> userData.firstName, "lastName" -> userData.lastName, "country" -> userData.country, "address" -> userData.address)).map { response =>

          OutputWithErrors(response.body, response.status)
        }
      } catch {
        case e: Exception =>
          Future(BadRequest(Message(false, e.getMessage).toJson.prettyPrint).as("application/json"))
      })
  }

So here address is optional parameter.

And form data i.e. userData here

case class UserForm(userId: String, firstName: String, lastName: String, country: String, address: Option[String])
  val UserDataForm = Form(
    mapping(
      "userId" -> nonEmptyText,
      "firstName" -> nonEmptyText,
      "lastName" -> nonEmptyText,
      "country" -> nonEmptyText,
      "address" -> optional(text)
    )(UserForm.apply)(UserForm.unapply)
  )

So while calling rest service API near ws.url(...)post(Map("userId" -> userData, ...)) compiler is complaining about No implicits found for parameter evidence$2: BodyWritable[Map[String, Serializable]]


Solution

  • The problem here is the Map you are passing to post. All but one entry is (String, String), but the last one is (String, Option[String]).

    The compiler now has to figure out what the type of this collection might be and goes up the type hierarchy. The best type it can figure out is Map[String, Serializable]. Because that's the narrowest super type String and Option implement.

    This is the definition for post:

    def post[T: BodyWritable](body: T): Future[Response]

    You can see there's a type constraint on T, which means that you need an implicit converter* for the body that is passed to post to BodyWritable. You can find the implicit BodyWritables in DefaultBodyWritables. And there is no BodyWritable[Map[String, Serializable]]. But there is one for Map[String, String]

    You have to choices:

    1. Change the Map so it has the type of Map[String, String], either by providing a default value for address via getOrElse or don't add address to the map if it's None.
    2. Write a BodyWritable for Map[String, Serializable]. (Which might not be possible in a satisfying way)

    I suggest you go with #1.

    * please correct me if that's not the right term for these kind of implicits.