octopus icon indicating copy to clipboard operation
octopus copied to clipboard

How to write rule for validating collections?

Open mii9000 opened this issue 6 years ago • 7 comments

case class Customer(id: Int, name: String)

case class CustomerCollection(customers: Vector[Customer])

implicit val customerValidator: AsyncValidator[CustomerCollection] =
    AsyncValidator[CustomerCollection]
.async.rule(_.customers, ....)

mii9000 avatar Mar 23 '18 11:03 mii9000

That's the point - you don't need to write rules for collections manually.

import octopus.dsl._
import octopus.syntax._

case class Customer(id: Int, name: String)
case class CustomerCollection(customers: Vector[Customer])

implicit val cutsomerValidator: Validator[Customer] = Validator[Customer]
  .rule(_.name, (_: String).trim.nonEmpty, "name must not be empty")

val customers = CustomerCollection(Vector(Customer(1, "C1"), Customer(2, "   "), Customer(3, "")))
customers.validate
// invalid:
//  customers[1].name: name must not be empty
//  customers[2].name: name must not be empty

Octopus will derive rules for collection automatically!

However sometimes you may want to additionally restrict collection itself. In this case you can define your own rules and compose with derived ones:

implicit val customerCollectionValidator: Validator[CustomerCollection] = Validator[CustomerCollection]
  .derived
  .rule(_.customers, (_: Vector[Customer]).length % 2 == 0, "number of customers must be even")

customers.validate
// invalid:
//  customers[1].name: name must not be empty
//  customers[2].name: name must not be empty
//  customers: number of customers must be even

Intentionally this should work equally well for asynchronous validators.

krzemin avatar Mar 23 '18 17:03 krzemin

@krzemin This works as well but how do I combine the collection validator to the actual class validator as in my original example:

case class Customer(id: Int, name: String)

case class CustomerCollection(customers: Vector[Customer])

implicit val customerValidator: AsyncValidator[CustomerCollection] =
    AsyncValidator[CustomerCollection]
.async.rule(_.customers, ....)

the advantage I would get is all the validation result would be combined

mii9000 avatar Mar 26 '18 05:03 mii9000

I'm not sure if I got your question right. If you mark your rule for Customer as implicit, validation results for the whole collection would be automatically combined by the library (as in the example). In case this is not what you want to achieve, can you write more precisely what kind of result composing would you expect? Perhaps providing a type signature or code with example values and expected semantics.

krzemin avatar Mar 26 '18 09:03 krzemin

I think I misunderstood. If it combines automatically due to implicit then that should be good enough. I will try it out. Thanks.

mii9000 avatar Mar 26 '18 09:03 mii9000

@Ibrahim-Islam can we close this?

andyczerwonka avatar Nov 01 '20 19:11 andyczerwonka

Hello,

I'm using version 0.4.1, which seems to be the latest. I have tried the example above like this:

  test("Customer collectionsexample") {

    import octopus.dsl._
    import octopus.syntax._

    case class Customer(id: Int, name: String)
    case class CustomerCollection(customers: Vector[Customer])

    implicit val customerValidator: Validator[Customer] = Validator[Customer]
      .rule(_.name, (_: String).trim.nonEmpty, "name must not be empty")

    implicit val customerCollectionValidator: Validator[CustomerCollection] = Validator
      .derived
      .rule(_.customers, (_: Vector[Customer]).length % 2 == 0, "number of customers must be even")

    val customers = CustomerCollection(Vector(Customer(1, "C1"), Customer(2, "   "), Customer(3, "")))
    println(customers.validate)
  }

And the test can not compile with the following error:

value derived is not a member of octopus.Validator[CustomerCollection]
possible cause: maybe a semicolon is missing before `value derived`?
      .derived

Do I missed something ?

Notice also that when I remove the customerCollection validator, it works fine. But I need to introduce validation items at collection level

pixime avatar Sep 09 '21 15:09 pixime

It seems that works better like this:

    implicit val customerCollectionValidator: Validator[CustomerCollection] = Validator[CustomerCollection]
      .rule(_.customers, (_: Vector[Customer]).length % 2 == 0, "number of customers must be even")
      .composeDerived

pixime avatar Sep 09 '21 16:09 pixime