keyword.js 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  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. };
  10. var definitionSchema = {
  11. definitions: {
  12. simpleTypes: metaSchema.definitions.simpleTypes
  13. },
  14. type: 'object',
  15. dependencies: {
  16. schema: ['validate'],
  17. $data: ['validate'],
  18. statements: ['inline'],
  19. valid: {not: {required: ['macro']}}
  20. },
  21. properties: {
  22. type: metaSchema.properties.type,
  23. schema: {type: 'boolean'},
  24. statements: {type: 'boolean'},
  25. dependencies: {
  26. type: 'array',
  27. items: {type: 'string'}
  28. },
  29. metaSchema: {type: 'object'},
  30. modifying: {type: 'boolean'},
  31. valid: {type: 'boolean'},
  32. $data: {type: 'boolean'},
  33. async: {type: 'boolean'},
  34. errors: {
  35. anyOf: [
  36. {type: 'boolean'},
  37. {const: 'full'}
  38. ]
  39. }
  40. }
  41. };
  42. var validateDefinition;
  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. validateDefinition = validateDefinition || this.compile(definitionSchema);
  60. if (!validateDefinition(definition))
  61. throw new Error('custom keyword definition is invalid: ' + this.errorsText(validateDefinition.errors));
  62. var dataType = definition.type;
  63. if (Array.isArray(dataType)) {
  64. for (var i=0; i<dataType.length; i++)
  65. _addRule(keyword, dataType[i], definition);
  66. } else {
  67. _addRule(keyword, dataType, definition);
  68. }
  69. var metaSchema = definition.metaSchema;
  70. if (metaSchema) {
  71. if (definition.$data && this._opts.$data) {
  72. metaSchema = {
  73. anyOf: [
  74. metaSchema,
  75. { '$ref': 'https://raw.githubusercontent.com/epoberezkin/ajv/master/lib/refs/data.json#' }
  76. ]
  77. };
  78. }
  79. definition.validateSchema = this.compile(metaSchema, true);
  80. }
  81. }
  82. RULES.keywords[keyword] = RULES.all[keyword] = true;
  83. function _addRule(keyword, dataType, definition) {
  84. var ruleGroup;
  85. for (var i=0; i<RULES.length; i++) {
  86. var rg = RULES[i];
  87. if (rg.type == dataType) {
  88. ruleGroup = rg;
  89. break;
  90. }
  91. }
  92. if (!ruleGroup) {
  93. ruleGroup = { type: dataType, rules: [] };
  94. RULES.push(ruleGroup);
  95. }
  96. var rule = {
  97. keyword: keyword,
  98. definition: definition,
  99. custom: true,
  100. code: customRuleCode,
  101. implements: definition.implements
  102. };
  103. ruleGroup.rules.push(rule);
  104. RULES.custom[keyword] = rule;
  105. }
  106. return this;
  107. }
  108. /**
  109. * Get keyword
  110. * @this Ajv
  111. * @param {String} keyword pre-defined or custom keyword.
  112. * @return {Object|Boolean} custom keyword definition, `true` if it is a predefined keyword, `false` otherwise.
  113. */
  114. function getKeyword(keyword) {
  115. /* jshint validthis: true */
  116. var rule = this.RULES.custom[keyword];
  117. return rule ? rule.definition : this.RULES.keywords[keyword] || false;
  118. }
  119. /**
  120. * Remove keyword
  121. * @this Ajv
  122. * @param {String} keyword pre-defined or custom keyword.
  123. * @return {Ajv} this for method chaining
  124. */
  125. function removeKeyword(keyword) {
  126. /* jshint validthis: true */
  127. var RULES = this.RULES;
  128. delete RULES.keywords[keyword];
  129. delete RULES.all[keyword];
  130. delete RULES.custom[keyword];
  131. for (var i=0; i<RULES.length; i++) {
  132. var rules = RULES[i].rules;
  133. for (var j=0; j<rules.length; j++) {
  134. if (rules[j].keyword == keyword) {
  135. rules.splice(j, 1);
  136. break;
  137. }
  138. }
  139. }
  140. return this;
  141. }