You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

146 lines
3.8 KiB

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