简体   繁体   中英

Error Accumulation with Scalaz Validation

I have a complex JSON that it's persisted in a database. It's complexity is "segregated" in "blocks", as follows:

Entire JSON:

{
    "block1" : {
        "param1" : "val1",
        "param2" : "val2"
    },
    "block2" : {
        "param3" : "val3",
        "param4" : "val4"
    },
    ...
}

In the database, every block is stored and treated individually:

Persisted Blocks

"block1" : {
    "param1" : "val1",
    "param2" : "val2"
}

"block2" : {
    "param3" : "val3",
    "param4" : "val4"
}

Every block has a business meaning, so, every one it's mapped to a case-class. I'm building a Play API that stores, updates and retrieves this JSON Structure and i want to validate if someone altered it's data for the sake of integrity.

I'm doing the retrieval (parsing and validation) of every block as follows:

val block1 = Json.parse(block1).validate[Block1].get
val block2 = Json.parse(block2).validate[Block2].get
...

The case-classes:

trait Block
sealed case class Block1 (param1: String, param2: String, ...) extends Block
sealed case class Block2 (param3: String, param4: String, ...) extends Block
sealed case class Request (block1: Block1, block2: Block2, ...)

With the current structure, if some field is altered and doesn't match with the defined type for it Play throws this exception:

[NoSuchElementException: JsError.get]

So, i want to build an accumulative error structure with Scalaz and Validation that catch all possible parsing and validation errors. I have seen this and this so i've coded the validation this way:

def build(block1: String, block2: String, ...): Validation[NonEmptyList[String], Request] = {
    val block1 = Option(Json.parse(block1).validate[Block1].get).toSuccess("Error").toValidationNel
    val block2 = Option(Json.parse(block2).validate[Block2].get).toSuccess("Error").toValidationNel
    ...

    val request = (Request.apply _).curried

    blockn <*> (... <*> (... <*> (...<*> (block2 <*> (block1 map request)))))   
}

Note that i'm using the applicative functor <*> because Request has 20 fields (building that is a parentheses-mess with that syntax), |@| applicative functor only applies for case classes up to 12 parameters.

That code works for the happy path, but, when i modify some field Play throws the Execution Exception later described.

Question : I want to accumulate all possible structure faults that Play can detect while parsing every block. How can i do that?

Note : If in some way Shapeless has something to do with this i'm open to use it (i'm already using it).

If multi-validation is the only reason for adding Scalaz to your project then why not consider an alternative which will not require you adapt your Play project to the monadic way in which Scalaz forces you to solve problems (which might be a good thing, but not necessarily if the only reason for that is multi-validation).

Instead consider using a standard Scala approach with scala.util.Try :

val block1 = Try(Json.parse(block1).validate[Block1].get)
val block2 = Try(Json.parse(block2).validate[Block2].get)
...

val allBlocks : List[Try[Block]] = List(block1, block2, ...)
val failures : List[Failure[Block]] = allBlocks.collect { case f : Failure[Block] => f }

This way you can still operate on standard scala collections to retrieve the list of failures to be processed further. Also this approach does not constrain you in terms of number of blocks.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM