PageRenderTime 49ms CodeModel.GetById 17ms RepoModel.GetById 1ms app.codeStats 0ms

/Website3Hydra/Scripts/tv4/source/validate.js

https://gitlab.com/encore-un/hydra
JavaScript | 397 lines | 381 code | 13 blank | 3 comment | 138 complexity | adf54dd40a76c3e95bfb0a1b73a166f9 MD5 | raw file
  1. var ValidatorContext = function ValidatorContext(parent, collectMultiple, errorReporter, checkRecursive, trackUnknownProperties) {
  2. this.missing = [];
  3. this.missingMap = {};
  4. this.formatValidators = parent ? Object.create(parent.formatValidators) : {};
  5. this.schemas = parent ? Object.create(parent.schemas) : {};
  6. this.collectMultiple = collectMultiple;
  7. this.errors = [];
  8. this.handleError = collectMultiple ? this.collectError : this.returnError;
  9. if (checkRecursive) {
  10. this.checkRecursive = true;
  11. this.scanned = [];
  12. this.scannedFrozen = [];
  13. this.scannedFrozenSchemas = [];
  14. this.scannedFrozenValidationErrors = [];
  15. this.validatedSchemasKey = 'tv4_validation_id';
  16. this.validationErrorsKey = 'tv4_validation_errors_id';
  17. }
  18. if (trackUnknownProperties) {
  19. this.trackUnknownProperties = true;
  20. this.knownPropertyPaths = {};
  21. this.unknownPropertyPaths = {};
  22. }
  23. this.errorReporter = errorReporter || defaultErrorReporter('en');
  24. if (typeof this.errorReporter === 'string') {
  25. throw new Error('debug');
  26. }
  27. this.definedKeywords = {};
  28. if (parent) {
  29. for (var key in parent.definedKeywords) {
  30. this.definedKeywords[key] = parent.definedKeywords[key].slice(0);
  31. }
  32. }
  33. };
  34. ValidatorContext.prototype.defineKeyword = function (keyword, keywordFunction) {
  35. this.definedKeywords[keyword] = this.definedKeywords[keyword] || [];
  36. this.definedKeywords[keyword].push(keywordFunction);
  37. };
  38. ValidatorContext.prototype.createError = function (code, messageParams, dataPath, schemaPath, subErrors, data, schema) {
  39. var error = new ValidationError(code, messageParams, dataPath, schemaPath, subErrors);
  40. error.message = this.errorReporter(error, data, schema);
  41. return error;
  42. };
  43. ValidatorContext.prototype.returnError = function (error) {
  44. return error;
  45. };
  46. ValidatorContext.prototype.collectError = function (error) {
  47. if (error) {
  48. this.errors.push(error);
  49. }
  50. return null;
  51. };
  52. ValidatorContext.prototype.prefixErrors = function (startIndex, dataPath, schemaPath) {
  53. for (var i = startIndex; i < this.errors.length; i++) {
  54. this.errors[i] = this.errors[i].prefixWith(dataPath, schemaPath);
  55. }
  56. return this;
  57. };
  58. ValidatorContext.prototype.banUnknownProperties = function (data, schema) {
  59. for (var unknownPath in this.unknownPropertyPaths) {
  60. var error = this.createError(ErrorCodes.UNKNOWN_PROPERTY, {path: unknownPath}, unknownPath, "", null, data, schema);
  61. var result = this.handleError(error);
  62. if (result) {
  63. return result;
  64. }
  65. }
  66. return null;
  67. };
  68. ValidatorContext.prototype.addFormat = function (format, validator) {
  69. if (typeof format === 'object') {
  70. for (var key in format) {
  71. this.addFormat(key, format[key]);
  72. }
  73. return this;
  74. }
  75. this.formatValidators[format] = validator;
  76. };
  77. ValidatorContext.prototype.resolveRefs = function (schema, urlHistory) {
  78. if (schema['$ref'] !== undefined) {
  79. urlHistory = urlHistory || {};
  80. if (urlHistory[schema['$ref']]) {
  81. return this.createError(ErrorCodes.CIRCULAR_REFERENCE, {urls: Object.keys(urlHistory).join(', ')}, '', '', null, undefined, schema);
  82. }
  83. urlHistory[schema['$ref']] = true;
  84. schema = this.getSchema(schema['$ref'], urlHistory);
  85. }
  86. return schema;
  87. };
  88. ValidatorContext.prototype.getSchema = function (url, urlHistory) {
  89. var schema;
  90. if (this.schemas[url] !== undefined) {
  91. schema = this.schemas[url];
  92. return this.resolveRefs(schema, urlHistory);
  93. }
  94. var baseUrl = url;
  95. var fragment = "";
  96. if (url.indexOf('#') !== -1) {
  97. fragment = url.substring(url.indexOf("#") + 1);
  98. baseUrl = url.substring(0, url.indexOf("#"));
  99. }
  100. if (typeof this.schemas[baseUrl] === 'object') {
  101. schema = this.schemas[baseUrl];
  102. var pointerPath = decodeURIComponent(fragment);
  103. if (pointerPath === "") {
  104. return this.resolveRefs(schema, urlHistory);
  105. } else if (pointerPath.charAt(0) !== "/") {
  106. return undefined;
  107. }
  108. var parts = pointerPath.split("/").slice(1);
  109. for (var i = 0; i < parts.length; i++) {
  110. var component = parts[i].replace(/~1/g, "/").replace(/~0/g, "~");
  111. if (schema[component] === undefined) {
  112. schema = undefined;
  113. break;
  114. }
  115. schema = schema[component];
  116. }
  117. if (schema !== undefined) {
  118. return this.resolveRefs(schema, urlHistory);
  119. }
  120. }
  121. if (this.missing[baseUrl] === undefined) {
  122. this.missing.push(baseUrl);
  123. this.missing[baseUrl] = baseUrl;
  124. this.missingMap[baseUrl] = baseUrl;
  125. }
  126. };
  127. ValidatorContext.prototype.searchSchemas = function (schema, url) {
  128. if (Array.isArray(schema)) {
  129. for (var i = 0; i < schema.length; i++) {
  130. this.searchSchemas(schema[i], url);
  131. }
  132. } else if (schema && typeof schema === "object") {
  133. if (typeof schema.id === "string") {
  134. if (isTrustedUrl(url, schema.id)) {
  135. if (this.schemas[schema.id] === undefined) {
  136. this.schemas[schema.id] = schema;
  137. }
  138. }
  139. }
  140. for (var key in schema) {
  141. if (key !== "enum") {
  142. if (typeof schema[key] === "object") {
  143. this.searchSchemas(schema[key], url);
  144. } else if (key === "$ref") {
  145. var uri = getDocumentUri(schema[key]);
  146. if (uri && this.schemas[uri] === undefined && this.missingMap[uri] === undefined) {
  147. this.missingMap[uri] = uri;
  148. }
  149. }
  150. }
  151. }
  152. }
  153. };
  154. ValidatorContext.prototype.addSchema = function (url, schema) {
  155. //overload
  156. if (typeof url !== 'string' || typeof schema === 'undefined') {
  157. if (typeof url === 'object' && typeof url.id === 'string') {
  158. schema = url;
  159. url = schema.id;
  160. }
  161. else {
  162. return;
  163. }
  164. }
  165. if (url === getDocumentUri(url) + "#") {
  166. // Remove empty fragment
  167. url = getDocumentUri(url);
  168. }
  169. this.schemas[url] = schema;
  170. delete this.missingMap[url];
  171. normSchema(schema, url);
  172. this.searchSchemas(schema, url);
  173. };
  174. ValidatorContext.prototype.getSchemaMap = function () {
  175. var map = {};
  176. for (var key in this.schemas) {
  177. map[key] = this.schemas[key];
  178. }
  179. return map;
  180. };
  181. ValidatorContext.prototype.getSchemaUris = function (filterRegExp) {
  182. var list = [];
  183. for (var key in this.schemas) {
  184. if (!filterRegExp || filterRegExp.test(key)) {
  185. list.push(key);
  186. }
  187. }
  188. return list;
  189. };
  190. ValidatorContext.prototype.getMissingUris = function (filterRegExp) {
  191. var list = [];
  192. for (var key in this.missingMap) {
  193. if (!filterRegExp || filterRegExp.test(key)) {
  194. list.push(key);
  195. }
  196. }
  197. return list;
  198. };
  199. ValidatorContext.prototype.dropSchemas = function () {
  200. this.schemas = {};
  201. this.reset();
  202. };
  203. ValidatorContext.prototype.reset = function () {
  204. this.missing = [];
  205. this.missingMap = {};
  206. this.errors = [];
  207. };
  208. ValidatorContext.prototype.validateAll = function (data, schema, dataPathParts, schemaPathParts, dataPointerPath) {
  209. var topLevel;
  210. schema = this.resolveRefs(schema);
  211. if (!schema) {
  212. return null;
  213. } else if (schema instanceof ValidationError) {
  214. this.errors.push(schema);
  215. return schema;
  216. }
  217. var startErrorCount = this.errors.length;
  218. var frozenIndex, scannedFrozenSchemaIndex = null, scannedSchemasIndex = null;
  219. if (this.checkRecursive && data && typeof data === 'object') {
  220. topLevel = !this.scanned.length;
  221. if (data[this.validatedSchemasKey]) {
  222. var schemaIndex = data[this.validatedSchemasKey].indexOf(schema);
  223. if (schemaIndex !== -1) {
  224. this.errors = this.errors.concat(data[this.validationErrorsKey][schemaIndex]);
  225. return null;
  226. }
  227. }
  228. if (Object.isFrozen(data)) {
  229. frozenIndex = this.scannedFrozen.indexOf(data);
  230. if (frozenIndex !== -1) {
  231. var frozenSchemaIndex = this.scannedFrozenSchemas[frozenIndex].indexOf(schema);
  232. if (frozenSchemaIndex !== -1) {
  233. this.errors = this.errors.concat(this.scannedFrozenValidationErrors[frozenIndex][frozenSchemaIndex]);
  234. return null;
  235. }
  236. }
  237. }
  238. this.scanned.push(data);
  239. if (Object.isFrozen(data)) {
  240. if (frozenIndex === -1) {
  241. frozenIndex = this.scannedFrozen.length;
  242. this.scannedFrozen.push(data);
  243. this.scannedFrozenSchemas.push([]);
  244. }
  245. scannedFrozenSchemaIndex = this.scannedFrozenSchemas[frozenIndex].length;
  246. this.scannedFrozenSchemas[frozenIndex][scannedFrozenSchemaIndex] = schema;
  247. this.scannedFrozenValidationErrors[frozenIndex][scannedFrozenSchemaIndex] = [];
  248. } else {
  249. if (!data[this.validatedSchemasKey]) {
  250. try {
  251. Object.defineProperty(data, this.validatedSchemasKey, {
  252. value: [],
  253. configurable: true
  254. });
  255. Object.defineProperty(data, this.validationErrorsKey, {
  256. value: [],
  257. configurable: true
  258. });
  259. } catch (e) {
  260. //IE 7/8 workaround
  261. data[this.validatedSchemasKey] = [];
  262. data[this.validationErrorsKey] = [];
  263. }
  264. }
  265. scannedSchemasIndex = data[this.validatedSchemasKey].length;
  266. data[this.validatedSchemasKey][scannedSchemasIndex] = schema;
  267. data[this.validationErrorsKey][scannedSchemasIndex] = [];
  268. }
  269. }
  270. var errorCount = this.errors.length;
  271. var error = this.validateBasic(data, schema, dataPointerPath)
  272. || this.validateNumeric(data, schema, dataPointerPath)
  273. || this.validateString(data, schema, dataPointerPath)
  274. || this.validateArray(data, schema, dataPointerPath)
  275. || this.validateObject(data, schema, dataPointerPath)
  276. || this.validateCombinations(data, schema, dataPointerPath)
  277. || this.validateHypermedia(data, schema, dataPointerPath)
  278. || this.validateFormat(data, schema, dataPointerPath)
  279. || this.validateDefinedKeywords(data, schema, dataPointerPath)
  280. || null;
  281. if (topLevel) {
  282. while (this.scanned.length) {
  283. var item = this.scanned.pop();
  284. delete item[this.validatedSchemasKey];
  285. }
  286. this.scannedFrozen = [];
  287. this.scannedFrozenSchemas = [];
  288. }
  289. if (error || errorCount !== this.errors.length) {
  290. while ((dataPathParts && dataPathParts.length) || (schemaPathParts && schemaPathParts.length)) {
  291. var dataPart = (dataPathParts && dataPathParts.length) ? "" + dataPathParts.pop() : null;
  292. var schemaPart = (schemaPathParts && schemaPathParts.length) ? "" + schemaPathParts.pop() : null;
  293. if (error) {
  294. error = error.prefixWith(dataPart, schemaPart);
  295. }
  296. this.prefixErrors(errorCount, dataPart, schemaPart);
  297. }
  298. }
  299. if (scannedFrozenSchemaIndex !== null) {
  300. this.scannedFrozenValidationErrors[frozenIndex][scannedFrozenSchemaIndex] = this.errors.slice(startErrorCount);
  301. } else if (scannedSchemasIndex !== null) {
  302. data[this.validationErrorsKey][scannedSchemasIndex] = this.errors.slice(startErrorCount);
  303. }
  304. return this.handleError(error);
  305. };
  306. ValidatorContext.prototype.validateFormat = function (data, schema) {
  307. if (typeof schema.format !== 'string' || !this.formatValidators[schema.format]) {
  308. return null;
  309. }
  310. var errorMessage = this.formatValidators[schema.format].call(null, data, schema);
  311. if (typeof errorMessage === 'string' || typeof errorMessage === 'number') {
  312. return this.createError(ErrorCodes.FORMAT_CUSTOM, {message: errorMessage}, '', '/format', null, data, schema);
  313. } else if (errorMessage && typeof errorMessage === 'object') {
  314. return this.createError(ErrorCodes.FORMAT_CUSTOM, {message: errorMessage.message || "?"}, errorMessage.dataPath || '', errorMessage.schemaPath || "/format", null, data, schema);
  315. }
  316. return null;
  317. };
  318. ValidatorContext.prototype.validateDefinedKeywords = function (data, schema, dataPointerPath) {
  319. for (var key in this.definedKeywords) {
  320. if (typeof schema[key] === 'undefined') {
  321. continue;
  322. }
  323. var validationFunctions = this.definedKeywords[key];
  324. for (var i = 0; i < validationFunctions.length; i++) {
  325. var func = validationFunctions[i];
  326. var result = func(data, schema[key], schema, dataPointerPath);
  327. if (typeof result === 'string' || typeof result === 'number') {
  328. return this.createError(ErrorCodes.KEYWORD_CUSTOM, {key: key, message: result}, '', '', null, data, schema).prefixWith(null, key);
  329. } else if (result && typeof result === 'object') {
  330. var code = result.code;
  331. if (typeof code === 'string') {
  332. if (!ErrorCodes[code]) {
  333. throw new Error('Undefined error code (use defineError): ' + code);
  334. }
  335. code = ErrorCodes[code];
  336. } else if (typeof code !== 'number') {
  337. code = ErrorCodes.KEYWORD_CUSTOM;
  338. }
  339. var messageParams = (typeof result.message === 'object') ? result.message : {key: key, message: result.message || "?"};
  340. var schemaPath = result.schemaPath || ("/" + key.replace(/~/g, '~0').replace(/\//g, '~1'));
  341. return this.createError(code, messageParams, result.dataPath || null, schemaPath, null, data, schema);
  342. }
  343. }
  344. }
  345. return null;
  346. };
  347. function recursiveCompare(A, B) {
  348. if (A === B) {
  349. return true;
  350. }
  351. if (A && B && typeof A === "object" && typeof B === "object") {
  352. if (Array.isArray(A) !== Array.isArray(B)) {
  353. return false;
  354. } else if (Array.isArray(A)) {
  355. if (A.length !== B.length) {
  356. return false;
  357. }
  358. for (var i = 0; i < A.length; i++) {
  359. if (!recursiveCompare(A[i], B[i])) {
  360. return false;
  361. }
  362. }
  363. } else {
  364. var key;
  365. for (key in A) {
  366. if (B[key] === undefined && A[key] !== undefined) {
  367. return false;
  368. }
  369. }
  370. for (key in B) {
  371. if (A[key] === undefined && B[key] !== undefined) {
  372. return false;
  373. }
  374. }
  375. for (key in A) {
  376. if (!recursiveCompare(A[key], B[key])) {
  377. return false;
  378. }
  379. }
  380. }
  381. return true;
  382. }
  383. return false;
  384. }