Hitesh Kumar
Hitesh Kumar

Reputation: 3698

Ajv: How to leverage $data reference with custom keyword?

I am using Ajv in my project. I am trying to add a custom keyword by the help of ajv.addKeyword api. I am able to add keyword by doing this (borrowed from docs):

var ajv = new Ajv({
  $data: true
});

ajv.addKeyword('range', {
  type: 'number',
  compile: function(sch, parentSchema) {
    var min = sch[0];
    var max = sch[1];
    return parentSchema.exclusiveRange === true ? function(data) {
      return data > min && data < max;
    } : function(data, dataPath, parentData, parentDataProperty) {
      return data >= min && data <= max;
    }
  }
});

var schema = {
  "properties": {
    "smaller": {
      "type": "number"
    },
    "larger": {
      "type": "number",
      "range": [2, 10]
    }
  }
};

var validData = {
  smaller: 15,
  larger: 17
};

let validateData = ajv.compile(schema);
validateData(validData);
console.log('Errors after validations --> ', validateData.errors)

Everything is working fine. Now I need to use $data cause data for my custom field will be the value of some other field. To achieve it this is what I tried with my schema:

var schema = {
  "properties": {
    "smaller": {
      "type": "number"
    },
    "larger": {
      "type": "number",
      // "range": [2, 10],
      "range": {
        "$data": "1/myRange" // referencing to myRange
      }
    },
    "myRange": {
      type: "array",
      items: {
        type: "number"
      }
    }
  }
};

But it looks likes custom fields are not supported with $data ref yet. As mentioned in the docs, only following fields are supported for $data ref.

$data reference is supported in the keywords: const, enum, format, maximum/minimum, exclusiveMaximum / exclusiveMinimum, maxLength / minLength, maxItems / minItems, maxProperties / minProperties, formatMaximum / formatMinimum, formatExclusiveMaximum / formatExclusiveMinimum, multipleOf, pattern, required, uniqueItems.

One way to get the value is, I use parameters of validate function data, dataPath, parentData, parentDataProperty and write logic to extract value of field defined by $data ref. But I am not sure this is a right way to achieve it or not. Can anyone please help me on this? Here's the plunkr to play. Thanks.

Upvotes: 1

Views: 3706

Answers (2)

Ray
Ray

Reputation: 1214

Also for anybody now finding this, just to add to @hitesh's answer if you want to add errors to the custom keyword you do so by attaching them to the validation function. I have included Hitesh's answer with the update for adding an error message

// Code goes here
console.clear();
var ajv = new Ajv({
 $data: true
});


ajv.addKeyword('range', {
 type: 'number',
 errors: true,
 $data: true, // important part
 validate: function myCustomKeyword(schema, data, parentSchema) {
  if (
    myCustomKeyword["errors"] === undefined ||
    myCustomKeyword["errors"] === null
  )
    myCustomKeyword["errors"] = [];
   const {
   exclusiveRange: isExclusive
   } = parentSchema;
   const [min, max] = schema;
   if (isExclusive) {
     return data > min && data < max;
   } else {
       myCustomKeyword["errors"].push({
        keyword: "range",
        message: `range message`,
        params: {
          keyword: "range",
        },
      });
   }
   return data >= min && data <= max;
  }
 });

var schema = {
"properties": {
"smaller": {
  "type": "number",
  "maximum": {
    "$data": "1/larger"
  }
},
"larger": {
  "type": "number",
  // "range": [2, 10],
  "range": {
    "$data": "1/myRange"
  },
  "exclusiveRange": true
},
"myRange": {
   type: "array",
   items: {
     type: "number"
   }
  }
 }
};


var validData = {
 smaller: 3,
 larger: 7,
 myRange: [2, 10]
};

let validateData = ajv.compile(schema);
validateData(validData);
console.log(ajv);
console.log('Errors after validations --> ', validateData.errors)

Upvotes: 0

Hitesh Kumar
Hitesh Kumar

Reputation: 3698

After digging documentation for a while finally I made it working. It's always good to share the solution for posterior readers. This is what I have done:

// Code goes here
console.clear();
var ajv = new Ajv({
  $data: true
});


ajv.addKeyword('range', {
  type: 'number',
  errors: true,
  $data: true, // important part
  validate: function(schema, data, parentSchema) {
    const {
      exclusiveRange: isExclusive
    } = parentSchema;
    const [min, max] = schema;
    if (isExclusive) {
      return data > min && data < max;
    }
    return data >= min && data <= max;
  }
});

var schema = {
  "properties": {
    "smaller": {
      "type": "number",
      "maximum": {
        "$data": "1/larger"
      }
    },
    "larger": {
      "type": "number",
      // "range": [2, 10],
      "range": {
        "$data": "1/myRange"
      },
      "exclusiveRange": true
    },
    "myRange": {
      type: "array",
      items: {
        type: "number"
      }
    }
  }
};


var validData = {
  smaller: 3,
  larger: 7,
  myRange: [2, 10]
};

let validateData = ajv.compile(schema);
validateData(validData);
console.log(ajv);
console.log('Errors after validations --> ', validateData.errors)

The salient option is $data in definition.It needs to be set true. Here's the working plunkr

Upvotes: 10

Related Questions