validate.js 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. Object.defineProperty(exports, "ValidationError", {
  6. enumerable: true,
  7. get: function () {
  8. return _ValidationError.default;
  9. }
  10. });
  11. exports.validate = validate;
  12. var _absolutePath = _interopRequireDefault(require("./keywords/absolutePath"));
  13. var _ValidationError = _interopRequireDefault(require("./ValidationError"));
  14. function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  15. /**
  16. * @template T
  17. * @param fn {(function(): any) | undefined}
  18. * @returns {function(): T}
  19. */
  20. const memoize = fn => {
  21. let cache = false;
  22. /** @type {T} */
  23. let result;
  24. return () => {
  25. if (cache) {
  26. return result;
  27. }
  28. result = /** @type {function(): any} */fn();
  29. cache = true;
  30. // Allow to clean up memory for fn
  31. // and all dependent resources
  32. // eslint-disable-next-line no-undefined, no-param-reassign
  33. fn = undefined;
  34. return result;
  35. };
  36. };
  37. const getAjv = memoize(() => {
  38. // Use CommonJS require for ajv libs so TypeScript consumers aren't locked into esModuleInterop (see #110).
  39. // eslint-disable-next-line global-require
  40. const Ajv = require("ajv").default;
  41. // eslint-disable-next-line global-require
  42. const ajvKeywords = require("ajv-keywords").default;
  43. // eslint-disable-next-line global-require
  44. const addFormats = require("ajv-formats").default;
  45. /**
  46. * @type {Ajv}
  47. */
  48. const ajv = new Ajv({
  49. strict: false,
  50. allErrors: true,
  51. verbose: true,
  52. $data: true
  53. });
  54. ajvKeywords(ajv, ["instanceof", "patternRequired"]);
  55. addFormats(ajv, {
  56. keywords: true
  57. });
  58. // Custom keywords
  59. (0, _absolutePath.default)(ajv);
  60. return ajv;
  61. });
  62. /** @typedef {import("json-schema").JSONSchema4} JSONSchema4 */
  63. /** @typedef {import("json-schema").JSONSchema6} JSONSchema6 */
  64. /** @typedef {import("json-schema").JSONSchema7} JSONSchema7 */
  65. /** @typedef {import("ajv").ErrorObject} ErrorObject */
  66. /**
  67. * @typedef {Object} Extend
  68. * @property {string=} formatMinimum
  69. * @property {string=} formatMaximum
  70. * @property {string=} formatExclusiveMinimum
  71. * @property {string=} formatExclusiveMaximum
  72. * @property {string=} link
  73. */
  74. /** @typedef {(JSONSchema4 | JSONSchema6 | JSONSchema7) & Extend} Schema */
  75. /** @typedef {ErrorObject & { children?: Array<ErrorObject>}} SchemaUtilErrorObject */
  76. /**
  77. * @callback PostFormatter
  78. * @param {string} formattedError
  79. * @param {SchemaUtilErrorObject} error
  80. * @returns {string}
  81. */
  82. /**
  83. * @typedef {Object} ValidationErrorConfiguration
  84. * @property {string=} name
  85. * @property {string=} baseDataPath
  86. * @property {PostFormatter=} postFormatter
  87. */
  88. /**
  89. * @param {Schema} schema
  90. * @param {Array<object> | object} options
  91. * @param {ValidationErrorConfiguration=} configuration
  92. * @returns {void}
  93. */
  94. function validate(schema, options, configuration) {
  95. let errors = [];
  96. if (Array.isArray(options)) {
  97. errors = Array.from(options, nestedOptions => validateObject(schema, nestedOptions));
  98. errors.forEach((list, idx) => {
  99. const applyPrefix =
  100. /**
  101. * @param {SchemaUtilErrorObject} error
  102. */
  103. error => {
  104. // eslint-disable-next-line no-param-reassign
  105. error.instancePath = `[${idx}]${error.instancePath}`;
  106. if (error.children) {
  107. error.children.forEach(applyPrefix);
  108. }
  109. };
  110. list.forEach(applyPrefix);
  111. });
  112. errors = errors.reduce((arr, items) => {
  113. arr.push(...items);
  114. return arr;
  115. }, []);
  116. } else {
  117. errors = validateObject(schema, options);
  118. }
  119. if (errors.length > 0) {
  120. throw new _ValidationError.default(errors, schema, configuration);
  121. }
  122. }
  123. /**
  124. * @param {Schema} schema
  125. * @param {Array<object> | object} options
  126. * @returns {Array<SchemaUtilErrorObject>}
  127. */
  128. function validateObject(schema, options) {
  129. const compiledSchema = getAjv().compile(schema);
  130. const valid = compiledSchema(options);
  131. if (valid) return [];
  132. return compiledSchema.errors ? filterErrors(compiledSchema.errors) : [];
  133. }
  134. /**
  135. * @param {Array<ErrorObject>} errors
  136. * @returns {Array<SchemaUtilErrorObject>}
  137. */
  138. function filterErrors(errors) {
  139. /** @type {Array<SchemaUtilErrorObject>} */
  140. let newErrors = [];
  141. for (const error of /** @type {Array<SchemaUtilErrorObject>} */errors) {
  142. const {
  143. instancePath
  144. } = error;
  145. /** @type {Array<SchemaUtilErrorObject>} */
  146. let children = [];
  147. newErrors = newErrors.filter(oldError => {
  148. if (oldError.instancePath.includes(instancePath)) {
  149. if (oldError.children) {
  150. children = children.concat(oldError.children.slice(0));
  151. }
  152. // eslint-disable-next-line no-undefined, no-param-reassign
  153. oldError.children = undefined;
  154. children.push(oldError);
  155. return false;
  156. }
  157. return true;
  158. });
  159. if (children.length) {
  160. error.children = children;
  161. }
  162. newErrors.push(error);
  163. }
  164. return newErrors;
  165. }