keyword.js (3909B)
1 'use strict'; 2 3 var IDENTIFIER = /^[a-z_$][a-z0-9_$-]*$/i; 4 var customRuleCode = require('./dotjs/custom'); 5 var definitionSchema = require('./definition_schema'); 6 7 module.exports = { 8 add: addKeyword, 9 get: getKeyword, 10 remove: removeKeyword, 11 validate: validateKeyword 12 }; 13 14 15 /** 16 * Define custom keyword 17 * @this Ajv 18 * @param {String} keyword custom keyword, should be unique (including different from all standard, custom and macro keywords). 19 * @param {Object} definition keyword definition object with properties `type` (type(s) which the keyword applies to), `validate` or `compile`. 20 * @return {Ajv} this for method chaining 21 */ 22 function addKeyword(keyword, definition) { 23 /* jshint validthis: true */ 24 /* eslint no-shadow: 0 */ 25 var RULES = this.RULES; 26 if (RULES.keywords[keyword]) 27 throw new Error('Keyword ' + keyword + ' is already defined'); 28 29 if (!IDENTIFIER.test(keyword)) 30 throw new Error('Keyword ' + keyword + ' is not a valid identifier'); 31 32 if (definition) { 33 this.validateKeyword(definition, true); 34 35 var dataType = definition.type; 36 if (Array.isArray(dataType)) { 37 for (var i=0; i<dataType.length; i++) 38 _addRule(keyword, dataType[i], definition); 39 } else { 40 _addRule(keyword, dataType, definition); 41 } 42 43 var metaSchema = definition.metaSchema; 44 if (metaSchema) { 45 if (definition.$data && this._opts.$data) { 46 metaSchema = { 47 anyOf: [ 48 metaSchema, 49 { '$ref': 'https://raw.githubusercontent.com/epoberezkin/ajv/master/lib/refs/data.json#' } 50 ] 51 }; 52 } 53 definition.validateSchema = this.compile(metaSchema, true); 54 } 55 } 56 57 RULES.keywords[keyword] = RULES.all[keyword] = true; 58 59 60 function _addRule(keyword, dataType, definition) { 61 var ruleGroup; 62 for (var i=0; i<RULES.length; i++) { 63 var rg = RULES[i]; 64 if (rg.type == dataType) { 65 ruleGroup = rg; 66 break; 67 } 68 } 69 70 if (!ruleGroup) { 71 ruleGroup = { type: dataType, rules: [] }; 72 RULES.push(ruleGroup); 73 } 74 75 var rule = { 76 keyword: keyword, 77 definition: definition, 78 custom: true, 79 code: customRuleCode, 80 implements: definition.implements 81 }; 82 ruleGroup.rules.push(rule); 83 RULES.custom[keyword] = rule; 84 } 85 86 return this; 87 } 88 89 90 /** 91 * Get keyword 92 * @this Ajv 93 * @param {String} keyword pre-defined or custom keyword. 94 * @return {Object|Boolean} custom keyword definition, `true` if it is a predefined keyword, `false` otherwise. 95 */ 96 function getKeyword(keyword) { 97 /* jshint validthis: true */ 98 var rule = this.RULES.custom[keyword]; 99 return rule ? rule.definition : this.RULES.keywords[keyword] || false; 100 } 101 102 103 /** 104 * Remove keyword 105 * @this Ajv 106 * @param {String} keyword pre-defined or custom keyword. 107 * @return {Ajv} this for method chaining 108 */ 109 function removeKeyword(keyword) { 110 /* jshint validthis: true */ 111 var RULES = this.RULES; 112 delete RULES.keywords[keyword]; 113 delete RULES.all[keyword]; 114 delete RULES.custom[keyword]; 115 for (var i=0; i<RULES.length; i++) { 116 var rules = RULES[i].rules; 117 for (var j=0; j<rules.length; j++) { 118 if (rules[j].keyword == keyword) { 119 rules.splice(j, 1); 120 break; 121 } 122 } 123 } 124 return this; 125 } 126 127 128 /** 129 * Validate keyword definition 130 * @this Ajv 131 * @param {Object} definition keyword definition object. 132 * @param {Boolean} throwError true to throw exception if definition is invalid 133 * @return {boolean} validation result 134 */ 135 function validateKeyword(definition, throwError) { 136 validateKeyword.errors = null; 137 var v = this._validateKeyword = this._validateKeyword 138 || this.compile(definitionSchema, true); 139 140 if (v(definition)) return true; 141 validateKeyword.errors = v.errors; 142 if (throwError) 143 throw new Error('custom keyword definition is invalid: ' + this.errorsText(v.errors)); 144 else 145 return false; 146 }