keyword.js 4.5 KB

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