gcanti / tcomb-validation

Validation library based on type combinators
MIT License
400 stars 23 forks source link

Dependent validations for structs #29

Closed mohanzhang closed 9 years ago

mohanzhang commented 9 years ago

Is it possible to validate one field of a struct depending on the value of another? In particular, I have a startDate and an optional endDate field, and I would like to make sure that endDate is after startDate, if endDate is given.

More specifically, if I do something like

var MyType = t.struct({
  startDate: t.Dat,
  endDate: t.maybe(t.Dat),
  foo: t.Num
});

How can I validate MyType as a whole? It doesn't seem like struct takes a validator function as an argument?

gcanti commented 9 years ago

Is it possible to validate one field of a struct depending on the value of another

Yes, with a subtype:

function isValid(x) {
  if (t.Dat.is(x.endDate)) {
    return x.endDate.getTime() >= x.startDate.getTime();
  }
  return true;
}

var MyType = t.subtype(t.struct({
  startDate: t.Dat,
  endDate: t.maybe(t.Dat),
  foo: t.Num
}), isValid);

console.log(t.validate({startDate: new Date(2015, 10, 30), foo: 1}, MyType).isValid()); // true
console.log(t.validate({startDate: new Date(2015, 10, 20), endDate: new Date(2015, 10, 21), foo: 1}, MyType).isValid()); // true
console.log(t.validate({startDate: new Date(2015, 10, 20), endDate: new Date(2015, 10, 19), foo: 1}, MyType).isValid()); // false

All the the combinators struct, subtype, list, etc... can be composed at your will

mohanzhang commented 9 years ago

Ah, that's brilliant! My next question was going to be how to ensure that the validations get layered so that the original struct's validations also run, but with subtype composition, it happens naturally. Thanks :+1:!

chirag04 commented 7 years ago

@gcanti instead, i feel like the predicate should have access to the options/context.

eg:

isValidConfirmPassword = (confirmPassword, context) => context.password === confirmPassword;

const ConfirmPassword = t.refinement(t.String, isValidConfirmPassword);

const Person = t.struct({
  name: t.String,
  password: t.String,
  confirmPassword: ConfirmPassword,
}, 'Person');

const formValues = { name: 'a', password: 'b', confirmPassword: 'c' };

validate(formValues, Person, { context: formValues });

Or the predicate could have the same signature as validate. (x, type, path, options)

Person struct can be complex and having to set Person as a refinement on struct and then handle all dependent field in one predicate is not ideal imo. makes sense?