0
votes

I am trying to validate JSON payload in POST request in Play Controller. I am very new to Play & Scala world.

Below code is what I have got so far. This works fine without any input validation. Now I am trying to add validation to IP address fields on IPRangeStr class and running into issues. I am not able to figure out the validation to json list that contains enum.

Thanks

object listTypes extends Enumeration {
  type listTypes = Value
  val TypeA= Value("TypeA")
  val TypeB = Value("TypeB")
  val Unknown = Value("Unknown")

  def withNameWithDefault(name: String): Value =
    values.find(_.toString.toLowerCase == name.toLowerCase()).getOrElse(Unknown)
}

case class IPRangeStr(firstIP: String, lastIP: String, listType: listTypes, action: String)


@Singleton
class DataController @Inject()(cc: ControllerComponents, actorSvc: IPDataActorService)(implicit exec: ExecutionContext) extends AbstractController(cc) {

  implicit val listTypeEnumFormat = new Format[listTypes.listTypes] {
    def reads(json: JsValue) = JsSuccess(listTypes.withNameWithDefault(json.as[String]))
    def writes(listEnum: listTypes.listTypes) = JsString(listEnum.toString)
  }

  implicit val ipRangeStrReads: Reads[IPRangeStr] = Json.format[IPRangeStr]
  implicit val ipRangeStrWrites = Json.writes[IPRangeStr]

  def process = Action.async(parse.json[List[IPRangeStr]]) { implicit request =>
   if(validationSuccess){    
        // process if validation successfull 
        Ok 
      } else {
        BadRequest // validation error
      }
    } recoverWith {
      case e: Exception  =>
        Future(InternalServerError)
    }
  }
}

#Edit-1 My final solution based on @cchantep ans

val validIPPattern: String = "(([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.){3}([01]?\\d\\d?|2[0-4]\\d|25[0-5])"

def isValidIP(ip:String) = ip.matches(validIPPattern)

implicit val ipRangeStrReads: Reads[IPRangeStr] =
    Json.format[IPRangeStr].filter { ipR =>
      isValidIP(ipR.firstIP) && isValidIP(ipR.lastIP)  //true  or false, according validation of ipR
}     

implicit val ipRangeStrWrites = Json.writes[IPRangeStr]

When using Json.format, is not required to have a second call to Json.write for the same type, as OFormat is able to read and write.

If I remove my write, I get an exception in my controller when I convert object to Json string.

Try to implement an implicit Writes or Format for this type.

I would rather use enumNameReads and enumNameWrites.

This is good to know shorthand. I ended up using my previous solution that converts json string to lowercase before converting to enum.

It's recommended to name type with an initial cap, without final "s" for plural (e.g. ListType).

I agree completely on this. Unfortunately it's part of legacy code which I don't want to refactor right now.

1
Rather use Json.reads for ipRangeStrReads - cchantep

1 Answers

1
votes

As a monadic type, the function .filter is available for Reads.

implicit val ipRangeStrReads: Reads[IPRangeStr] =
  Json.format[IPRangeStr].filter { ipR =>
    true // or false, according validation of ipR
  }

When using Json.format, is not required to have a second call to Json.write for the same type, as OFormat is able to read and write.

I would rather use enumNameReads and enumNameWrites.

implicit val r = Reads.enumNameReads(listTypes)
implicit val w = Writes.enumNameWrites[listTypes.type]

It's recommended to name type with an initial cap, without final "s" for plural (e.g. ListType).