I have
case class AclRuleScope(kind: String, value: String)
and i want convert Json to AclRuleScope with restrictions:
type may be only: "default" | "user" | "group" | "domain"
value may be only email if type is "user" | "group", and some string in another cases
I have object with Reader and Writer, but i cant understand how can i get type value when reading value:
object AclRuleScope {
implicit val aclRuleScopeRead = (
(__ \ "type").read[String](pattern("^(default|user|group|domain)$".r)) and
(__ \ "value").read[String](
email keepAnd
filter(
ValidationError("error.scope.value")
)( ??? == JsString("user") || ??? == JsString("group")))
)(this.apply _)
}
What must be in ???
JsConstraints#filter has the following signature
def filter[A](otherwise: ValidationError)(p: A => Boolean)(implicit reads: Reads[A])
when you write
filter(ValidationError("error.scope.value"))(??? == JsString("user") || ??? == JsString("group")))
the code (??? == JsString("user") || ??? == JsString("group")) is actually the second parameter to filter, it should therefore be a predicate of A => Boolean. Also since this is applied after the email reads which is a Reads[String], your actual A is String so you should drop the JsString.
The smallest change you could write is:
implicit val aclRuleScopeRead = (
(__ \ "type").read[String](pattern("^(default|user|group|domain)$".r)) and
(__ \ "value").read[String](
email keepAnd
filter(
ValidationError("error.scope.value")
)(x => x == "user" || x == "group"))
).tupled
I strongly encourage you to extract the predicate to its own method:
def isValidEmail: (String) => Boolean = {
x => x == "user" || x == "group"
}
and write your reads as
implicit val aclRuleScopeRead = (
(__ \ "type").read[String](pattern("^(default|user|group|domain)$".r)) and
(__ \ "value").read[String](
email keepAnd filter(ValidationError("error.scope.value"))(isValidEmail))
).tupled
even better you could have
val validEmail = email keepAnd filter(ValidationError("error.scope.value"))(isValidEmail))
and write
implicit val aclRuleScopeRead = (
(__ \ "type").read[String](pattern("^(default|user|group|domain)$".r)) and
(__ \ "value").read[String](validEmail)
).tupled
Upon clarification in the comments, you only want to parse the email if the type is "user" or "group" returning an empty string if not the case.
The answer is close to the solution outlined in this question
The reads of the value field first needs to check the value of the type field. The condition on the type field looks like :
(__ \ "type").read[String].filter(ValidationError("error.scope.value"))(isEmailType)
where isEmailType is defined as
def isEmailType: (String) => Boolean = { x => x == "user" || x == "group" }
This will return a read that gives a JsSuccess if the type is user or group and a JsError otherwise. From the comments we know that we should return the empty string if the type is not user or group, the reads can become:
(__ \ "type").read[String]
.filter(ValidationError("error.scope.value"))(isEmailType)
.orElse Reads.pure("")
Which is safe and will never return a JsError. This is fine since there is a dedicated reads to enforce the validation on type, the reads we are currently manipulating is only there as part of the validation of value.
Now we need to change the read to parse value if the parsing is a JsSuccess :
(__ \ "type").read[String]
.filter(ValidationError("error.scope.value"))(isEmailType)
.flatMap(_ => (__ \ "value").read[String](email))
.orElse Reads.pure("")
Using flatMap, we replace the type reads by the correct reads on value if the type reads is a success.
Thanks @Jean, you are pointing me in the right direction.
flatMap and filter helps me, and i have
case class AclRuleScope(kind: String, value: Option[String])
val aclRuleScopeRead = (
(__ \ "type").read[String] and
(__ \ "type").read[String](pattern("^(default|user|group|domain)$".r)).flatMap {
case t if "user".equals(t) || "group".equals(t) =>
(__ \ "value").readNullable[String](email)
.filter(ValidationError(s"error.acl.scope.value omitted for '$t' type"))(_.isDefined)
.filter(ValidationError(s"error.acl.scope.value not defained for '$t' type"))(_.exists(_.nonEmpty))
case "domain" =>
(__ \ "value").readNullable[String]
.filter(ValidationError("error.acl.scope.value omitted for 'domain' type"))(_.isDefined)
.filter(ValidationError("error.acl.scope.value not defained for 'domain' type"))(_.exists(_.nonEmpty))
case "default" =>
(__ \ "value").readNullable[String]
.filter(ValidationError("error.acl.scope.value must be omitted for 'default' type"))(_.isEmpty)
}
)(AclRuleScope.apply _)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With