147 lines
		
	
	
		
			3.8 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
		
		
			
		
	
	
			147 lines
		
	
	
		
			3.8 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
|  | 'use strict'; | ||
|  | 
 | ||
|  | var IDENTIFIER = /^[a-z_$][a-z0-9_$-]*$/i; | ||
|  | var customRuleCode = require('./dotjs/custom'); | ||
|  | var definitionSchema = require('./definition_schema'); | ||
|  | 
 | ||
|  | module.exports = { | ||
|  |   add: addKeyword, | ||
|  |   get: getKeyword, | ||
|  |   remove: removeKeyword, | ||
|  |   validate: validateKeyword | ||
|  | }; | ||
|  | 
 | ||
|  | 
 | ||
|  | /** | ||
|  |  * Define custom keyword | ||
|  |  * @this  Ajv | ||
|  |  * @param {String} keyword custom keyword, should be unique (including different from all standard, custom and macro keywords). | ||
|  |  * @param {Object} definition keyword definition object with properties `type` (type(s) which the keyword applies to), `validate` or `compile`. | ||
|  |  * @return {Ajv} this for method chaining | ||
|  |  */ | ||
|  | function addKeyword(keyword, definition) { | ||
|  |   /* jshint validthis: true */ | ||
|  |   /* eslint no-shadow: 0 */ | ||
|  |   var RULES = this.RULES; | ||
|  |   if (RULES.keywords[keyword]) | ||
|  |     throw new Error('Keyword ' + keyword + ' is already defined'); | ||
|  | 
 | ||
|  |   if (!IDENTIFIER.test(keyword)) | ||
|  |     throw new Error('Keyword ' + keyword + ' is not a valid identifier'); | ||
|  | 
 | ||
|  |   if (definition) { | ||
|  |     this.validateKeyword(definition, true); | ||
|  | 
 | ||
|  |     var dataType = definition.type; | ||
|  |     if (Array.isArray(dataType)) { | ||
|  |       for (var i=0; i<dataType.length; i++) | ||
|  |         _addRule(keyword, dataType[i], definition); | ||
|  |     } else { | ||
|  |       _addRule(keyword, dataType, definition); | ||
|  |     } | ||
|  | 
 | ||
|  |     var metaSchema = definition.metaSchema; | ||
|  |     if (metaSchema) { | ||
|  |       if (definition.$data && this._opts.$data) { | ||
|  |         metaSchema = { | ||
|  |           anyOf: [ | ||
|  |             metaSchema, | ||
|  |             { '$ref': 'https://raw.githubusercontent.com/ajv-validator/ajv/master/lib/refs/data.json#' } | ||
|  |           ] | ||
|  |         }; | ||
|  |       } | ||
|  |       definition.validateSchema = this.compile(metaSchema, true); | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   RULES.keywords[keyword] = RULES.all[keyword] = true; | ||
|  | 
 | ||
|  | 
 | ||
|  |   function _addRule(keyword, dataType, definition) { | ||
|  |     var ruleGroup; | ||
|  |     for (var i=0; i<RULES.length; i++) { | ||
|  |       var rg = RULES[i]; | ||
|  |       if (rg.type == dataType) { | ||
|  |         ruleGroup = rg; | ||
|  |         break; | ||
|  |       } | ||
|  |     } | ||
|  | 
 | ||
|  |     if (!ruleGroup) { | ||
|  |       ruleGroup = { type: dataType, rules: [] }; | ||
|  |       RULES.push(ruleGroup); | ||
|  |     } | ||
|  | 
 | ||
|  |     var rule = { | ||
|  |       keyword: keyword, | ||
|  |       definition: definition, | ||
|  |       custom: true, | ||
|  |       code: customRuleCode, | ||
|  |       implements: definition.implements | ||
|  |     }; | ||
|  |     ruleGroup.rules.push(rule); | ||
|  |     RULES.custom[keyword] = rule; | ||
|  |   } | ||
|  | 
 | ||
|  |   return this; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | /** | ||
|  |  * Get keyword | ||
|  |  * @this  Ajv | ||
|  |  * @param {String} keyword pre-defined or custom keyword. | ||
|  |  * @return {Object|Boolean} custom keyword definition, `true` if it is a predefined keyword, `false` otherwise. | ||
|  |  */ | ||
|  | function getKeyword(keyword) { | ||
|  |   /* jshint validthis: true */ | ||
|  |   var rule = this.RULES.custom[keyword]; | ||
|  |   return rule ? rule.definition : this.RULES.keywords[keyword] || false; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | /** | ||
|  |  * Remove keyword | ||
|  |  * @this  Ajv | ||
|  |  * @param {String} keyword pre-defined or custom keyword. | ||
|  |  * @return {Ajv} this for method chaining | ||
|  |  */ | ||
|  | function removeKeyword(keyword) { | ||
|  |   /* jshint validthis: true */ | ||
|  |   var RULES = this.RULES; | ||
|  |   delete RULES.keywords[keyword]; | ||
|  |   delete RULES.all[keyword]; | ||
|  |   delete RULES.custom[keyword]; | ||
|  |   for (var i=0; i<RULES.length; i++) { | ||
|  |     var rules = RULES[i].rules; | ||
|  |     for (var j=0; j<rules.length; j++) { | ||
|  |       if (rules[j].keyword == keyword) { | ||
|  |         rules.splice(j, 1); | ||
|  |         break; | ||
|  |       } | ||
|  |     } | ||
|  |   } | ||
|  |   return this; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | /** | ||
|  |  * Validate keyword definition | ||
|  |  * @this  Ajv | ||
|  |  * @param {Object} definition keyword definition object. | ||
|  |  * @param {Boolean} throwError true to throw exception if definition is invalid | ||
|  |  * @return {boolean} validation result | ||
|  |  */ | ||
|  | function validateKeyword(definition, throwError) { | ||
|  |   validateKeyword.errors = null; | ||
|  |   var v = this._validateKeyword = this._validateKeyword | ||
|  |                                   || this.compile(definitionSchema, true); | ||
|  | 
 | ||
|  |   if (v(definition)) return true; | ||
|  |   validateKeyword.errors = v.errors; | ||
|  |   if (throwError) | ||
|  |     throw new Error('custom keyword definition is invalid: '  + this.errorsText(v.errors)); | ||
|  |   else | ||
|  |     return false; | ||
|  | } |