yavi icon indicating copy to clipboard operation
yavi copied to clipboard

Cross-field validation at different levels

Open sergtitov opened this issue 2 years ago • 9 comments

Please add support for a cross-field validation at different levels, for example:

I have an object with type and collection of items:

record MyObject(
        MyType type,
        List<MyItem> items
){}

I need to be able to validate every item in collection depending on type:

var validator = ValidatorBuilder.<MyObject>of()
                .forEach(MyObject::items, "items", <there is no way to reference MyObject.type here>)
                .build();

sergtitov avatar Sep 21 '21 05:09 sergtitov

Thanks for the request. It makes sense to add that functionality.

making avatar Sep 21 '21 06:09 making

@sergtitov Can you give me a more specific usage example?

It's true that you can't use it like the above, but I'd like to consider if there is an alternative to achieve what you want to do. I'm afraid it's probably difficult to implement the above approach with the current structure.

making avatar Sep 21 '21 14:09 making

@making, we have several rules where fields validity on one level "depends" on the values of the fields on another level of an object.

Continuing the example from above where we have an object with type and collection of items:

record MyObject(
        MyType type,
        List<MyItem> items
){}

Validity of each element in items collection depends on the type, for some types an element would be valid, for others it would not.

For example:

record Animal(
        AnimalType type,
        List<String> actions
){}

The validator would need to be like this:

var validator = ValidatorBuilder.< Animal >of()
                .forEach(Animal:: actions, "actions", 
                {
                  if parent.type == AnimalType.dog then check if action is one of ["run", "bark", ...]
                  if parent.type == AnimalType.cat then check if action is one of ["run", "meow", ...]
                 })
                .build();

And the error message would be something like "For type dog an action "meow" is not allowed. Allowed values are: ["run", "bark", ...]"

sergtitov avatar Sep 22 '21 00:09 sergtitov

Hi @making, is the example above is sufficient? Please let me know if you need more information. Thx!

sergtitov avatar Oct 11 '21 19:10 sergtitov

I'm considering expanding the use of constraintOnCondition to collection.

making avatar Oct 12 '21 00:10 making

@sergtitov It seems that your case is already feasible using constraintOnCondition. Can you check the example? https://gist.github.com/making/facf1fce97c5d6686b017618c4959e15

making avatar Oct 12 '21 04:10 making

Great work!

ffroliva avatar Jan 04 '22 23:01 ffroliva

Hi @making I have a more complexe situation and I don't know if it's possible to solve it with yavi.

I have the following objects:

public class Customer {
    public CustomerType type;
    public InvoiceInformation invoiceInformation;


    public static class InvoiceInformation {
        public Object address;
        public AdministrativeIdentifiers administrativeIdentifiers;
    }

    public static class AdministrativeIdentifiers {
        public String fiscalCode;
        public String pIva;
        public String pec;
    }

    public enum CustomerType {
        PRO, INHABITANT
    }
}

my Administrative validator should be something like that:

      public static Validator<AdministrativeIdentifiers> VALIDATOR = ValidatorBuilder.<AdministrativeIdentifiers>of()
                .constraintOnCondition((a, g) -> isCustomerTypePro(), b -> b.constraint(AdministrativeIdentifiers::getFiscalCode, "fiscalCode", Constraint::notNull))
                .build();

Where isCustomerTypePro should check if the customer is a PRO or not.

Do you think there are a solution?

Thanks

deblockt avatar Feb 18 '22 18:02 deblockt

@deblockt

The validation logic should be in the customer class.

import am.ik.yavi.builder.ValidatorBuilder;
import am.ik.yavi.core.Validator;

public class Customer {
  public CustomerType type;

  public InvoiceInformation invoiceInformation;

  public boolean isCustomerTypePro() {
    return this.type == CustomerType.PRO;
  }

  public CustomerType getType() {
    return type;
  }

  public InvoiceInformation getInvoiceInformation() {
    return invoiceInformation;
  }

  public static Validator<Customer> getValidator() {
    return validator;
  }

  public static class InvoiceInformation {
    public Object address;

    public AdministrativeIdentifiers administrativeIdentifiers;

    public Object getAddress() {
      return address;
    }

    public AdministrativeIdentifiers getAdministrativeIdentifiers() {
      return administrativeIdentifiers;
    }
  }

  public static class AdministrativeIdentifiers {
    public String fiscalCode;

    public String pIva;

    public String pec;

    public String getFiscalCode() {
      return fiscalCode;
    }

    public String getpIva() {
      return pIva;
    }

    public String getPec() {
      return pec;
    }
  }

  public enum CustomerType {
    PRO, INHABITANT
  }

  public static Validator<Customer> validator = ValidatorBuilder.<Customer>of()
      .constraintOnCondition((customer, __) -> customer.isCustomerTypePro(),
          b -> b.nest(Customer::getInvoiceInformation, "invoiceInformation",
              ib -> ib.nest(InvoiceInformation::getAdministrativeIdentifiers, "administrativeIdentifiers",
                  ab -> ab.constraint(AdministrativeIdentifiers::getFiscalCode, "fiscalCode", c -> c.notNull()))))
      .build();
}

making avatar Feb 19 '22 13:02 making