How to validate relationship between values

37 Views Asked by At

I have a case where there are a few different ways user input should be able to pass validation:

// greatly simplified
Joi.object({
  v4Type: Joi.string().valid('DHCP', 'Static'),
  v6Type: Joi.string().valid('DHCP', 'Static'),
  v4Addr: Joi.string().ip({version: 'ipv4'}),
  v6Addr: Joi.string().ip({version: 'ipv6'}),
})

The user is required to have at least one address family configured, but we don't care which one. The only configuration that passes the above schema that I need to reject is:

{ v4Type: Static, v6Type: Static } // both addrs are missing

They need to have at least one address. It can be for either family, and it can be static or dynamic - but if it is static, it must be supplied. In pseudocode:

function isValid(...) {
  if ([v4Type, v6Type].include('DHCP')) return true;
  if (v4Addr || v6Addr) return true;
  return false;
}

I've followed over a dozen threads here and elsewhere, and haven't found one yet that lets me consider the values of multiple keys at once before deciding pass/fail. I also can't find a way impose a pair of "or'd" constraints on other keys based on one key's value. I've looked into the switch: option to when() and chaining when()'s but all have the same issue.

Here are two pseudo-code Joi schemata that would work, but I can't figure out how to express for real:

Joi.object({
  a1: Joi.string().valid('ok', 'need more').
      when({ is: 'need more', then: Joi.object({a2: DHCP}).or(b1: Joi.required()),
  a2: Joi.string().valid('ok', 'need more').
      when({ is: 'need more', then: Joi.object({a1: DHCP}).or(b2: Joi.required()),
  b1: Joi.string(),
  b2: Joi.string(),
})

or

Joi.object({
  a1: Joi.string().valid('ok', 'need more'),
  a2: Joi.string().valid('ok', 'need more'),
  b1: Joi.string(),
  b2: Joi.string(),
}).when({ is: a1 == 'need more' && a2 == 'need more', then: Joi.keys('b1').or('b2')})
1

There are 1 best solutions below

1
Kamran On

Either of v4Type or v6Type is mandatory, if v4Type or v6Type is 'DHCP', v4Addr and v6Addr are optional, and if its 'Static', respective will be mandatory.

Joi.object({
    v4Type: Joi.string().allow('DHCP','Static'),
    v6Type: Joi.string().allow('DHCP','Static'),
    v4Addr: Joi.string().regex(/^(\d{1,3}\.){3}\d{1,3}$/).when('v4Type', { is: 'Static', then: Joi.string().required(), otherwise: Joi.string().optional() }),
    v6Addr: Joi.string().regex(/^([\da-f]{1,4}:){7}[\da-f]{1,4}$/i).when('v6Type', { is: 'Static', then: Joi.string().required(), otherwise: Joi.string().optional() }),
}).or("v4Type", "v6Type")