Skip to content

Latest commit

 

History

History
241 lines (209 loc) · 5.52 KB

logical_rel_support.md

File metadata and controls

241 lines (209 loc) · 5.52 KB

Logical Relation

There are some logical relation operators in Joi:

  • and
  • nand
  • or
  • xor
  • oxor (After Joi v14)
  • with
  • without

For different operator, I have managed to describe them in JSON schema as below for different cases. Some named as xxxGeneral means it should be supported since JSON Draft 4. Some named as xxxDraft7 means it is using some features until JSON Draft 7.

Hence, if you are converting the Joi to Draft 4 or OpenAPI, the xor will be ignored.

Usage

By default, this feature is enabled. It can be disabled completely by passing option { logicalOpParser: false } to parse API:

parse(joiObj, 'json', {}, { logicalOpParser: false })

It's also possible to disable one particular operator or use your own convertor if you come up with a more suitable JSON schema representation by passing options like { logicalOpParser: { xor: convertorFun, oxor: null } }.

The signature of the convertorFun is function (schema, dependency). For example, the built-in or convertor function is:

function (schema, dependency) {
  schema.anyOf = _.map(dependency.peers, (peer) => {
    return { required: [peer] }
  })
}

OR

// At least one of a or b or c exists.
// Failed on { d: 1 }
const orJoi = joi.object({
  a: joi.string(),
  b: joi.number(),
  c: joi.boolean(),
  d: joi.number()
}).or('a', 'b', 'c');

const orSchemaGeneral = {
  type: 'object',
  anyOf: [
    { required: ['a'] }, { required: ['b'] }, { required: ['c'] }
  ],
  properties: { a: { type: 'string' }, b: { type: 'number' }, c: { type: 'boolean' }, d: { type: 'number' } },
  additionalProperties: false
}

AND

// Either a, b, and c All NOT exists, or all of them exists
// Failed on { a: 'hi', b: 1 }
const andJoi = joi.object({
  a: joi.string(),
  b: joi.number(),
  c: joi.boolean(),
  d: joi.number()
}).and('a', 'b', 'c');

const andSchemaGeneral = {
  type: 'object',
  oneOf: [
    {
      allOf: [
        {
          not: { required: ['a'] }
        },
        {
          not: { required: ['b'] }
        },
        {
          not: { required: ['c'] }
        }
      ]
    },
    { required: ['a', 'b', 'c'] }
  ],
  properties: { a: { type: 'string' }, b: { type: 'number' }, c: { type: 'boolean' }, d: { type: 'number' } },
  additionalProperties: false
};

NAND

// a, b, and c cannot all exist at the same time
// Failed on { a: 'hi', b: 1, c: true }
const nandJoi = joi.object({
  a: joi.string(),
  b: joi.number(),
  c: joi.boolean(),
  d: joi.number()
}).nand('a', 'b', 'c');

const nandSchemaGeneral = {
  type: 'object',
  not: { required: ['a', 'b', 'c'] },
  properties: { a: { type: 'string' }, b: { type: 'number' }, c: { type: 'boolean' }, d: { type: 'number' } },
  additionalProperties: false
};

XOR

// Only one of a, b and c can and must exist
// Failed on { d: 1 } or { a: 'hi', b: 1 }
const xorJoi = joi.object({
  a: joi.string(),
  b: joi.number(),
  c: joi.boolean(),
  d: joi.number()
}).xor('a', 'b', 'c');

const xorSchemaDraft7 = {
  type: 'object',
  if: { propertyNames: { enum: ['a', 'b', 'c'] }, minProperties: 2 },
  then: false,
  else: {
    oneOf: [
      {
        required: ['a']
      },
      {
        required: ['b']
      },
      {
        required: ['c']
      }
    ]
  },
  properties: { a: { type: 'string' }, b: { type: 'number' }, c: { type: 'boolean' }, d: { type: 'number' } },
  additionalProperties: false
};

OXOR

// Only one of a, b and c can exist but none is required
// Failed on { a: 'hi', b: 1 }
const oxorJoi = joi.object({
  a: joi.string(),
  b: joi.number(),
  c: joi.boolean(),
  d: joi.number()
}).oxor('a', 'b', 'c');

const oxorSchemaGeneral = {
  type: 'object',
  oneOf: [
    { required: ['a'] },
    { required: ['b'] },
    { required: ['c'] },
    {
      not: {
        oneOf: [
          { required: ['a'] },
          { required: ['b'] },
          { required: ['c'] },
          { required: ['a', 'b'] },
          { required: ['a', 'c'] },
          { required: ['b', 'c'] } // Combination up to 2 elements
        ]
      }
    }
  ],
  properties: { a: { type: 'string' }, b: { type: 'number' }, c: { type: 'boolean' }, d: { type: 'number' } },
  additionalProperties: false
};

WITH

// With d exists, both a and b must exist
// Failed on { d: 1, a: '' }
const withJoi = jjoi.object({
  a: joi.string(),
  b: joi.number(),
  c: joi.boolean(),
  d: joi.number()
}).with('c', ['a']).with('d', ['a', 'b']);

const withSchemaBeforeBefore2019 = {
  type: 'object',
  dependencies: {
    c: ['a'],
    d: ['a', 'b']
  },
  properties: { a: { type: 'string' }, b: { type: 'number' }, c: { type: 'boolean' }, d: { type: 'number' } },
  additionalProperties: false
};

const withSchemaBeforeDraft2019 = {
  type: 'object',
  dependentRequired: {
    c: ['a'],
    d: ['a', 'b']
  },
  properties: { a: { type: 'string' }, b: { type: 'number' }, c: { type: 'boolean' }, d: { type: 'number' } },
  additionalProperties: false
};

WITHOUT

// With a exists, either b or c must not exist
// Failed on { a: '', b: 1 }
const withoutJoi = jjoi.object({
  a: joi.string(),
  b: joi.number(),
  c: joi.boolean(),
  d: joi.number()
}).without('a', ['b', 'c']);

const withoutSchemaDraft7 = {
  type: 'object',
  if: { required: ['a'] },
  then: {
    not: {
      anyOf: [{ required: ['b'] }, { required: ['c'] }]
    }
  },
  properties: { a: { type: 'string' }, b: { type: 'number' }, c: { type: 'boolean' }, d: { type: 'number' } },
  additionalProperties: false
};