z-schema 7.0.0 → 7.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/README.md +7 -11
  2. package/bin/z-schema +0 -0
  3. package/cjs/index.d.ts +192 -117
  4. package/cjs/index.js +949 -998
  5. package/{src/FormatValidators.ts → dist/format-validators.js} +97 -65
  6. package/dist/index.js +1 -1
  7. package/dist/json-schema.js +40 -0
  8. package/dist/{JsonValidation.js → json-validation.js} +75 -69
  9. package/dist/{Report.js → report.js} +35 -45
  10. package/dist/schema-cache.js +109 -0
  11. package/dist/schema-compiler.js +255 -0
  12. package/dist/{SchemaValidation.js → schema-validator.js} +153 -149
  13. package/dist/types/{Errors.d.ts → errors.d.ts} +2 -0
  14. package/dist/types/format-validators.d.ts +10 -0
  15. package/dist/types/index.d.ts +10 -1
  16. package/dist/types/json-schema.d.ts +50 -0
  17. package/dist/types/json-validation.d.ts +7 -0
  18. package/dist/types/{Report.d.ts → report.d.ts} +22 -23
  19. package/dist/types/schema-cache.d.ts +17 -0
  20. package/dist/types/schema-compiler.d.ts +16 -0
  21. package/dist/types/schema-validator.d.ts +10 -0
  22. package/dist/types/utils/array.d.ts +2 -0
  23. package/dist/types/utils/clone.d.ts +2 -0
  24. package/dist/types/utils/json.d.ts +7 -0
  25. package/dist/types/utils/symbols.d.ts +2 -0
  26. package/dist/types/utils/unicode.d.ts +14 -0
  27. package/dist/types/utils/uri.d.ts +4 -0
  28. package/dist/types/utils/what-is.d.ts +3 -0
  29. package/dist/types/z-schema.d.ts +75 -0
  30. package/dist/utils/array.js +27 -0
  31. package/dist/utils/clone.js +61 -0
  32. package/dist/utils/json.js +59 -0
  33. package/dist/utils/symbols.js +2 -0
  34. package/dist/utils/unicode.js +45 -0
  35. package/dist/utils/uri.js +15 -0
  36. package/dist/utils/what-is.js +29 -0
  37. package/dist/{ZSchema.js → z-schema.js} +66 -77
  38. package/package.json +8 -4
  39. package/src/{Errors.ts → errors.ts} +4 -0
  40. package/src/format-validators.ts +191 -0
  41. package/src/index.ts +12 -1
  42. package/src/json-schema.ts +97 -0
  43. package/src/{JsonValidation.ts → json-validation.ts} +137 -127
  44. package/src/{Report.ts → report.ts} +60 -70
  45. package/src/schema-cache.ts +122 -0
  46. package/src/schema-compiler.ts +300 -0
  47. package/src/{SchemaValidation.ts → schema-validator.ts} +213 -215
  48. package/src/utils/array.ts +29 -0
  49. package/src/utils/clone.ts +63 -0
  50. package/src/utils/json.ts +74 -0
  51. package/src/utils/symbols.ts +3 -0
  52. package/src/utils/unicode.ts +43 -0
  53. package/src/utils/uri.ts +18 -0
  54. package/src/utils/what-is.ts +46 -0
  55. package/src/{ZSchema.ts → z-schema.ts} +108 -113
  56. package/umd/ZSchema.js +949 -998
  57. package/umd/ZSchema.min.js +1 -1
  58. package/dist/FormatValidators.js +0 -136
  59. package/dist/SchemaCache.js +0 -173
  60. package/dist/SchemaCompilation.js +0 -259
  61. package/dist/Utils.js +0 -266
  62. package/dist/types/FormatValidators.d.ts +0 -12
  63. package/dist/types/JsonValidation.d.ts +0 -37
  64. package/dist/types/SchemaCache.d.ts +0 -26
  65. package/dist/types/SchemaCompilation.d.ts +0 -1
  66. package/dist/types/SchemaValidation.d.ts +0 -6
  67. package/dist/types/Utils.d.ts +0 -64
  68. package/dist/types/ZSchema.d.ts +0 -97
  69. package/src/SchemaCache.ts +0 -189
  70. package/src/SchemaCompilation.ts +0 -293
  71. package/src/Utils.ts +0 -286
  72. /package/dist/{Errors.js → errors.js} +0 -0
  73. /package/dist/schemas/{hyper-schema.json → draft-04-hyper-schema.json} +0 -0
  74. /package/dist/schemas/{schema.json → draft-04-schema.json} +0 -0
  75. /package/src/schemas/{hyper-schema.json → draft-04-hyper-schema.json} +0 -0
  76. /package/src/schemas/{schema.json → draft-04-schema.json} +0 -0
@@ -1,7 +1,10 @@
1
1
  import get from 'lodash.get';
2
- import { Errors } from './Errors.js';
3
- import * as Utils from './Utils.js';
4
- import { ZSchemaOptions } from './ZSchema.js';
2
+ import { ErrorCode, ErrorParam, Errors } from './errors.js';
3
+ import { whatIs } from './utils/what-is.js';
4
+ import { schemaSymbol, jsonSymbol } from './utils/symbols.js';
5
+ import { ValidateCallback, ZSchemaOptions } from './z-schema.js';
6
+ import { JsonSchema, JsonSchemaInternal } from './json-schema.js';
7
+ import { isAbsoluteUri } from './utils/uri.js';
5
8
 
6
9
  export interface SchemaError extends Error {
7
10
  /**
@@ -36,12 +39,12 @@ export interface SchemaErrorDetail {
36
39
  * Format parameters that can be used to format a custom error message.
37
40
  * Example: ["string","array"]
38
41
  */
39
- params: Array<string>;
42
+ params: ErrorParam[];
40
43
  /**
41
44
  * A JSON path indicating the location of the error.
42
45
  * Example: "#/projects/1"
43
46
  */
44
- path: string | string[];
47
+ path: string | Array<string | number>;
45
48
  /**
46
49
  * The schema rule description, which is included for certain errors where
47
50
  * this information is useful (e.g. to describe a constraint).
@@ -70,35 +73,24 @@ type TaskProcessFn = (result: ReturnType<TaskFn>) => void;
70
73
  type AsyncTask = [TaskFn, TaskFnArgs, TaskProcessFn];
71
74
 
72
75
  export class Report {
73
- errors: SchemaErrorDetail[];
76
+ asyncTasks: AsyncTask[] = [];
77
+ commonErrorMessage?: string;
78
+ errors: SchemaErrorDetail[] = [];
79
+ json?: unknown;
80
+ path: Array<number | string> = [];
81
+ rootSchema?: JsonSchemaInternal;
82
+
74
83
  parentReport?: Report;
75
84
  options: ZSchemaOptions;
76
85
  reportOptions: ReportOptions;
77
- path: string[];
78
- asyncTasks: AsyncTask[];
79
- rootSchema?: {
80
- id?: string;
81
- };
82
- commonErrorMessage?: string;
83
- json?: unknown;
84
86
 
85
- constructor(parentOrOptions, reportOptions?) {
87
+ constructor(zschemaOptions: ZSchemaOptions); // primary
88
+ constructor(parentReport: Report); // subreport
89
+ constructor(parentReport: Report, reportOptions: ReportOptions); // subreport with options
90
+ constructor(parentOrOptions: ZSchemaOptions | Report, reportOptions?: ReportOptions) {
86
91
  this.parentReport = parentOrOptions instanceof Report ? parentOrOptions : undefined;
87
-
88
92
  this.options = parentOrOptions instanceof Report ? parentOrOptions.options : parentOrOptions || {};
89
-
90
93
  this.reportOptions = reportOptions || {};
91
-
92
- this.errors = [];
93
- /**
94
- * @type {string[]}
95
- */
96
- this.path = [];
97
- this.asyncTasks = [];
98
-
99
- this.rootSchema = undefined;
100
- this.commonErrorMessage = undefined;
101
- this.json = undefined;
102
94
  }
103
95
 
104
96
  isValid(): boolean {
@@ -108,11 +100,15 @@ export class Report {
108
100
  return this.errors.length === 0;
109
101
  }
110
102
 
111
- addAsyncTask(fn, args, asyncTaskResultProcessFn) {
112
- this.asyncTasks.push([fn, args, asyncTaskResultProcessFn]);
103
+ addAsyncTask<FV, FN extends (...args: any[]) => FV>(
104
+ fn: FN,
105
+ args: Parameters<FN>,
106
+ asyncTaskResultProcessFn: (result: ReturnType<FN>) => void
107
+ ) {
108
+ this.asyncTasks.push([fn, args, asyncTaskResultProcessFn as TaskProcessFn]);
113
109
  }
114
110
 
115
- getAncestor(id) {
111
+ getAncestor(id: string): Report | undefined {
116
112
  if (!this.parentReport) {
117
113
  return undefined;
118
114
  }
@@ -122,7 +118,7 @@ export class Report {
122
118
  return this.parentReport.getAncestor(id);
123
119
  }
124
120
 
125
- processAsyncTasks(timeout, callback) {
121
+ processAsyncTasks(timeout: number | undefined, callback: ValidateCallback) {
126
122
  const validationTimeout = timeout || 2000;
127
123
  let tasksCount = this.asyncTasks.length;
128
124
  let idx = tasksCount;
@@ -136,17 +132,15 @@ export class Report {
136
132
  }, 0);
137
133
  };
138
134
 
139
- function respond(asyncTaskResultProcessFn: TaskProcessFn) {
140
- return function (asyncTaskResult) {
141
- if (timedOut) {
142
- return;
143
- }
144
- asyncTaskResultProcessFn(asyncTaskResult);
145
- if (--tasksCount === 0) {
146
- finish();
147
- }
148
- };
149
- }
135
+ const respond = (asyncTaskResultProcessFn: TaskProcessFn) => (asyncTaskResult: TaskResult) => {
136
+ if (timedOut) {
137
+ return;
138
+ }
139
+ asyncTaskResultProcessFn(asyncTaskResult);
140
+ if (--tasksCount === 0) {
141
+ finish();
142
+ }
143
+ };
150
144
 
151
145
  // finish if tasks are completed or there are any errors and breaking on first error was requested
152
146
  if (tasksCount === 0 || (this.errors.length > 0 && this.options.breakOnFirstError)) {
@@ -169,11 +163,8 @@ export class Report {
169
163
  }, validationTimeout);
170
164
  }
171
165
 
172
- getPath(returnPathAsString) {
173
- /**
174
- * @type {string[]|string}
175
- */
176
- let path = [];
166
+ getPath(returnPathAsString?: boolean) {
167
+ let path: Array<string | number> = [];
177
168
  if (this.parentReport) {
178
169
  path = path.concat(this.parentReport.path);
179
170
  }
@@ -187,7 +178,7 @@ export class Report {
187
178
  .map(function (segment) {
188
179
  segment = segment.toString();
189
180
 
190
- if (Utils.isAbsoluteUri(segment)) {
181
+ if (isAbsoluteUri(segment)) {
191
182
  return 'uri(' + segment + ')';
192
183
  }
193
184
 
@@ -199,13 +190,13 @@ export class Report {
199
190
  return path;
200
191
  }
201
192
 
202
- getSchemaId() {
193
+ getSchemaId(): string | undefined {
203
194
  if (!this.rootSchema) {
204
- return null;
195
+ return undefined;
205
196
  }
206
197
 
207
198
  // get the error path as an array
208
- let path = [];
199
+ let path: Array<string | number> = [];
209
200
  if (this.parentReport) {
210
201
  path = path.concat(this.parentReport.path);
211
202
  }
@@ -224,17 +215,17 @@ export class Report {
224
215
  return this.rootSchema.id;
225
216
  }
226
217
 
227
- hasError(errorCode, params) {
218
+ hasError(errCode: string, errParams: Array<any>) {
228
219
  let idx = this.errors.length;
229
220
  while (idx--) {
230
- if (this.errors[idx].code === errorCode) {
221
+ if (this.errors[idx].code === errCode) {
231
222
  // assume match
232
223
  let match = true;
233
224
 
234
225
  // check the params too
235
226
  let idx2 = this.errors[idx].params.length;
236
227
  while (idx2--) {
237
- if (this.errors[idx].params[idx2] !== params[idx2]) {
228
+ if (this.errors[idx].params[idx2] !== errParams[idx2]) {
238
229
  match = false;
239
230
  }
240
231
  }
@@ -248,14 +239,15 @@ export class Report {
248
239
  return false;
249
240
  }
250
241
 
251
- addError(errorCode, params, subReports?, schema?) {
252
- if (!errorCode) {
242
+ addError(errCode: ErrorCode, errParams?: ErrorParam[], subReports?: Report | Report[], schema?: JsonSchema) {
243
+ if (!errCode) {
253
244
  throw new Error('No errorCode passed into addError()');
254
245
  }
255
- this.addCustomError(errorCode, Errors[errorCode], params, subReports, schema);
246
+ this.addCustomError(errCode, Errors[errCode], errParams, subReports, schema);
256
247
  }
257
248
 
258
- getJson() {
249
+ // this returns the root object being validated (the one passed into validator.validate)
250
+ getJson(): unknown {
259
251
  if (this.json) {
260
252
  return this.json;
261
253
  }
@@ -268,14 +260,11 @@ export class Report {
268
260
  addCustomError(
269
261
  errorCode: string,
270
262
  errorMessage: string,
271
- params: string[],
272
- subReports?: Report[] | Report,
273
- schema?: {
274
- title?: string;
275
- description?: string;
276
- }
263
+ params?: ErrorParam[],
264
+ subReports?: Report | Report[],
265
+ schema?: JsonSchema
277
266
  ) {
278
- if (this.errors.length >= this.reportOptions.maxErrors) {
267
+ if (typeof this.reportOptions.maxErrors === 'number' && this.errors.length >= this.reportOptions.maxErrors) {
279
268
  return;
280
269
  }
281
270
 
@@ -287,9 +276,9 @@ export class Report {
287
276
 
288
277
  let idx = params.length;
289
278
  while (idx--) {
290
- const whatIs = Utils.whatIs(params[idx]);
291
- const param = whatIs === 'object' || whatIs === 'null' ? JSON.stringify(params[idx]) : params[idx];
292
- errorMessage = errorMessage.replace('{' + idx + '}', param);
279
+ const paramType = whatIs(params[idx]);
280
+ const param = paramType === 'object' || paramType === 'null' ? JSON.stringify(params[idx]) : params[idx];
281
+ errorMessage = errorMessage.replace('{' + idx + '}', param.toString());
293
282
  }
294
283
 
295
284
  const err: SchemaErrorDetail = {
@@ -300,8 +289,9 @@ export class Report {
300
289
  schemaId: this.getSchemaId(),
301
290
  };
302
291
 
303
- err[Utils.schemaSymbol] = schema;
304
- err[Utils.jsonSymbol] = this.getJson();
292
+ // TODO v8: remove Symbol usage
293
+ (err as any)[schemaSymbol] = schema;
294
+ (err as any)[jsonSymbol] = this.getJson();
305
295
 
306
296
  if (schema && typeof schema === 'string') {
307
297
  err.description = schema;
@@ -0,0 +1,122 @@
1
+ import type { ZSchema } from './z-schema.js';
2
+ import isequal from 'lodash.isequal';
3
+ import { Report } from './report.js';
4
+ import { findId, JsonSchemaInternal } from './json-schema.js';
5
+ import { getQueryPath, getRemotePath } from './utils/uri.js';
6
+ import { deepClone } from './utils/clone.js';
7
+ import { decodeJSONPointer } from './utils/json.js';
8
+
9
+ export type SchemaCacheStorage = Record<string, JsonSchemaInternal>;
10
+ export type ReferenceSchemaCacheStorage = Array<[JsonSchemaInternal, JsonSchemaInternal]>;
11
+
12
+ export class SchemaCache {
13
+ cache: SchemaCacheStorage = {};
14
+ referenceCache: ReferenceSchemaCacheStorage = [];
15
+
16
+ constructor(private validator: ZSchema) {}
17
+
18
+ cacheSchemaByUri(uri: string, schema: JsonSchemaInternal) {
19
+ const remotePath = getRemotePath(uri);
20
+ if (remotePath) {
21
+ this.cache[remotePath] = schema;
22
+ }
23
+ }
24
+
25
+ removeFromCacheByUri(uri: string) {
26
+ const remotePath = getRemotePath(uri);
27
+ if (remotePath) {
28
+ delete this.cache[remotePath];
29
+ }
30
+ }
31
+
32
+ checkCacheForUri(uri: string) {
33
+ const remotePath = getRemotePath(uri);
34
+ return remotePath ? this.cache[remotePath] != null : false;
35
+ }
36
+
37
+ getSchema(report: Report, refOrSchema: string | JsonSchemaInternal) {
38
+ if (typeof refOrSchema === 'string') {
39
+ // ref input
40
+ return this.getSchemaByUri(report, refOrSchema);
41
+ }
42
+ if (typeof refOrSchema === 'object') {
43
+ // schema obj input
44
+ return this.getSchemaByReference(report, refOrSchema);
45
+ }
46
+ throw new Error(`unexpected code reached`);
47
+ }
48
+
49
+ getSchemaByUri(report: Report, uri: string, root?: JsonSchemaInternal) {
50
+ const remotePath = getRemotePath(uri);
51
+ const queryPath = getQueryPath(uri);
52
+ let result = remotePath ? this.cache[remotePath] : root;
53
+
54
+ if (result && remotePath) {
55
+ // we need to avoid compiling schemas in a recursive loop
56
+ const compileRemote = result !== root;
57
+ // now we need to compile and validate resolved schema (in case it's not already)
58
+ if (compileRemote) {
59
+ report.path.push(remotePath);
60
+
61
+ let remoteReport;
62
+
63
+ const anscestorReport = result.id ? report.getAncestor(result.id) : undefined;
64
+ if (anscestorReport) {
65
+ remoteReport = anscestorReport;
66
+ } else {
67
+ remoteReport = new Report(report);
68
+ if (this.validator.sc.compileSchema(remoteReport, result)) {
69
+ const savedOptions = this.validator.options;
70
+ try {
71
+ // If custom validationOptions were provided to setRemoteReference(),
72
+ // use them instead of the default options
73
+ this.validator.options = result.__$validationOptions || this.validator.options;
74
+ this.validator.sv.validateSchema(remoteReport, result);
75
+ } finally {
76
+ this.validator.options = savedOptions;
77
+ }
78
+ }
79
+ }
80
+ const remoteReportIsValid = remoteReport.isValid();
81
+ if (!remoteReportIsValid) {
82
+ report.addError('REMOTE_NOT_VALID', [uri], remoteReport);
83
+ }
84
+
85
+ report.path.pop();
86
+
87
+ if (!remoteReportIsValid) {
88
+ return undefined;
89
+ }
90
+ }
91
+ }
92
+
93
+ if (result && queryPath) {
94
+ const parts = queryPath.split('/');
95
+ for (let idx = 0, lim = parts.length; result && idx < lim; idx++) {
96
+ const key = decodeJSONPointer(parts[idx]);
97
+ if (idx === 0) {
98
+ // it's an id
99
+ result = findId(result, key);
100
+ } else {
101
+ // it's a path behind id
102
+ result = result[key as keyof typeof result] as JsonSchemaInternal | undefined;
103
+ }
104
+ }
105
+ }
106
+
107
+ return result;
108
+ }
109
+
110
+ getSchemaByReference(report: Report, schema: JsonSchemaInternal) {
111
+ let i = this.referenceCache.length;
112
+ while (i--) {
113
+ if (isequal(this.referenceCache[i][0], schema)) {
114
+ return this.referenceCache[i][1];
115
+ }
116
+ }
117
+ // not found
118
+ const schemaClone = deepClone(schema);
119
+ this.referenceCache.push([schema, schemaClone]);
120
+ return schemaClone;
121
+ }
122
+ }
@@ -0,0 +1,300 @@
1
+ import type { ZSchema } from './z-schema.js';
2
+ import { JsonSchemaInternal } from './json-schema.js';
3
+ import { Report } from './report.js';
4
+ import { isAbsoluteUri, isRelativeUri } from './utils/uri.js';
5
+
6
+ export interface Reference {
7
+ ref: string;
8
+ key: '$ref' | '$schema';
9
+ obj: JsonSchemaInternal;
10
+ path: Array<string | number>;
11
+ }
12
+
13
+ const collectReferences = (
14
+ obj: JsonSchemaInternal,
15
+ results?: Reference[],
16
+ scope?: string[],
17
+ path?: Reference['path']
18
+ ) => {
19
+ results = results || [];
20
+ scope = scope || [];
21
+ path = path || [];
22
+
23
+ if (typeof obj !== 'object' || obj === null) {
24
+ return results;
25
+ }
26
+
27
+ if (typeof obj.id === 'string') {
28
+ scope.push(obj.id);
29
+ }
30
+
31
+ if (typeof obj.$ref === 'string' && typeof obj.__$refResolved === 'undefined') {
32
+ results.push({
33
+ ref: mergeReference(scope, obj.$ref),
34
+ key: '$ref',
35
+ obj: obj,
36
+ path: path.slice(0),
37
+ });
38
+ }
39
+ if (typeof obj.$schema === 'string' && typeof obj.__$schemaResolved === 'undefined') {
40
+ results.push({
41
+ ref: mergeReference(scope, obj.$schema),
42
+ key: '$schema',
43
+ obj: obj,
44
+ path: path.slice(0),
45
+ });
46
+ }
47
+
48
+ let idx;
49
+ if (Array.isArray(obj)) {
50
+ idx = obj.length;
51
+ while (idx--) {
52
+ path.push(idx);
53
+ collectReferences(obj[idx], results, scope, path);
54
+ path.pop();
55
+ }
56
+ } else {
57
+ const keys = Object.keys(obj);
58
+ idx = keys.length;
59
+ while (idx--) {
60
+ // do not recurse through resolved references and other z-schema props
61
+ if (keys[idx].indexOf('__$') === 0) {
62
+ continue;
63
+ }
64
+ path.push(keys[idx]);
65
+ collectReferences((obj as any)[keys[idx]], results, scope, path);
66
+ path.pop();
67
+ }
68
+ }
69
+
70
+ if (typeof obj.id === 'string') {
71
+ scope.pop();
72
+ }
73
+
74
+ return results;
75
+ };
76
+
77
+ const mergeReference = (scope: string[], ref: string) => {
78
+ if (isAbsoluteUri(ref)) {
79
+ return ref;
80
+ }
81
+
82
+ let joinedScope = scope.join('');
83
+ const isScopeAbsolute = isAbsoluteUri(joinedScope);
84
+ const isScopeRelative = isRelativeUri(joinedScope);
85
+ const isRefRelative = isRelativeUri(ref);
86
+ let toRemove;
87
+
88
+ if (isScopeAbsolute && isRefRelative) {
89
+ toRemove = joinedScope.match(/\/[^/]*$/);
90
+ if (toRemove) {
91
+ joinedScope = joinedScope.slice(0, toRemove.index! + 1);
92
+ }
93
+ } else if (isScopeRelative && isRefRelative) {
94
+ joinedScope = '';
95
+ } else {
96
+ toRemove = joinedScope.match(/[^#/]+$/);
97
+ if (toRemove) {
98
+ joinedScope = joinedScope.slice(0, toRemove.index);
99
+ }
100
+ }
101
+
102
+ let res = joinedScope + ref;
103
+ res = res.replace(/##/, '#');
104
+ return res;
105
+ };
106
+
107
+ export class SchemaCompiler {
108
+ constructor(private validator: ZSchema) {}
109
+
110
+ compileSchema(report: Report, schema: JsonSchemaInternal) {
111
+ report.commonErrorMessage = 'SCHEMA_COMPILATION_FAILED';
112
+
113
+ // if schema is a string, assume it's a uri
114
+ if (typeof schema === 'string') {
115
+ const loadedSchema = this.validator.scache.getSchemaByUri(report, schema);
116
+ if (!loadedSchema) {
117
+ report.addError('SCHEMA_NOT_REACHABLE', [schema]);
118
+ return false;
119
+ }
120
+ schema = loadedSchema;
121
+ }
122
+
123
+ // if schema is an array, assume it's an array of schemas
124
+ if (Array.isArray(schema)) {
125
+ return this.compileArrayOfSchemas(report, schema);
126
+ }
127
+
128
+ // if we have an id than it should be cached already (if this instance has compiled it)
129
+ if (schema.__$compiled && schema.id && this.validator.scache.checkCacheForUri(schema.id) === false) {
130
+ schema.__$compiled = undefined;
131
+ }
132
+
133
+ // do not re-compile schemas
134
+ if (schema.__$compiled) {
135
+ return true;
136
+ }
137
+
138
+ if (schema.id && typeof schema.id === 'string') {
139
+ // add this to our schemaCache (before compilation in case we have references including id)
140
+ this.validator.scache.cacheSchemaByUri(schema.id, schema);
141
+ }
142
+
143
+ // this method can be called recursively, so we need to remember our root
144
+ let isRoot = false;
145
+ if (!report.rootSchema) {
146
+ report.rootSchema = schema;
147
+ isRoot = true;
148
+ }
149
+
150
+ // delete all __$missingReferences from previous compilation attempts
151
+ const isValidExceptReferences = report.isValid();
152
+ delete schema.__$missingReferences;
153
+
154
+ // collect all references that need to be resolved - $ref and $schema
155
+ const refs = collectReferences(schema);
156
+ let idx = refs.length;
157
+ while (idx--) {
158
+ // resolve all the collected references into __xxxResolved pointer
159
+ const refObj = refs[idx];
160
+ let response = this.validator.scache.getSchemaByUri(report, refObj.ref, schema);
161
+
162
+ // we can try to use custom schemaReader if available
163
+ if (!response) {
164
+ const schemaReader = this.validator.getSchemaReader();
165
+ if (schemaReader) {
166
+ // it's supposed to return a valid schema
167
+ const s = schemaReader(refObj.ref);
168
+ if (s) {
169
+ // it needs to have the id
170
+ s.id = refObj.ref;
171
+ // try to compile the schema
172
+ const subreport = new Report(report);
173
+ if (!this.compileSchema(subreport, s)) {
174
+ // copy errors to report
175
+ report.errors = report.errors.concat(subreport.errors);
176
+ } else {
177
+ response = this.validator.scache.getSchemaByUri(report, refObj.ref, schema);
178
+ }
179
+ }
180
+ }
181
+ }
182
+
183
+ if (!response) {
184
+ const hasNotValid = report.hasError('REMOTE_NOT_VALID', [refObj.ref]);
185
+ const isAbsolute = isAbsoluteUri(refObj.ref);
186
+ let isDownloaded = false;
187
+ const ignoreUnresolvableRemotes = this.validator.options.ignoreUnresolvableReferences === true;
188
+
189
+ if (isAbsolute) {
190
+ // we shouldn't add UNRESOLVABLE_REFERENCE for schemas we already have downloaded
191
+ // and set through setRemoteReference method
192
+ isDownloaded = this.validator.scache.checkCacheForUri(refObj.ref);
193
+ }
194
+
195
+ if (hasNotValid) {
196
+ // already has REMOTE_NOT_VALID error for this one
197
+ } else if (ignoreUnresolvableRemotes && isAbsolute) {
198
+ // ignoreUnresolvableRemotes is on and remote isAbsolute
199
+ } else if (isDownloaded) {
200
+ // remote is downloaded, so no UNRESOLVABLE_REFERENCE
201
+ } else {
202
+ report.path.push(...refObj.path);
203
+ report.addError('UNRESOLVABLE_REFERENCE', [refObj.ref]);
204
+ report.path = report.path.slice(0, -refObj.path.length);
205
+
206
+ // pusblish unresolved references out
207
+ if (isValidExceptReferences) {
208
+ schema.__$missingReferences = schema.__$missingReferences || [];
209
+ schema.__$missingReferences.push(refObj);
210
+ }
211
+ }
212
+ }
213
+ // this might create circular references
214
+ refObj.obj[`__${refObj.key}Resolved`] = response;
215
+ }
216
+
217
+ const isValid = report.isValid();
218
+ if (isValid) {
219
+ schema.__$compiled = true;
220
+ } else {
221
+ if (schema.id && typeof schema.id === 'string') {
222
+ // remove this schema from schemaCache because it failed to compile
223
+ this.validator.scache.removeFromCacheByUri(schema.id);
224
+ }
225
+ }
226
+
227
+ // we don't need the root pointer anymore
228
+ if (isRoot) {
229
+ report.rootSchema = undefined;
230
+ }
231
+
232
+ return isValid;
233
+ }
234
+
235
+ compileArrayOfSchemas(report: Report, arr: JsonSchemaInternal[]) {
236
+ let compiled = 0,
237
+ lastLoopCompiled;
238
+
239
+ do {
240
+ // remove all UNRESOLVABLE_REFERENCE errors before compiling array again
241
+ let idx = report.errors.length;
242
+ while (idx--) {
243
+ if (report.errors[idx].code === 'UNRESOLVABLE_REFERENCE') {
244
+ report.errors.splice(idx, 1);
245
+ }
246
+ }
247
+
248
+ // remember how many were compiled in the last loop
249
+ lastLoopCompiled = compiled;
250
+
251
+ // count how many are compiled now
252
+ compiled = this.compileArrayOfSchemasLoop(report, arr);
253
+
254
+ // fix __$missingReferences if possible
255
+ idx = arr.length;
256
+ while (idx--) {
257
+ const sch = arr[idx];
258
+ if (sch.__$missingReferences) {
259
+ let idx2 = sch.__$missingReferences.length;
260
+ while (idx2--) {
261
+ const refObj = sch.__$missingReferences[idx2];
262
+ const response = arr.find((x) => x.id === refObj.ref);
263
+ if (response) {
264
+ // this might create circular references
265
+ refObj.obj[`__${refObj.key}Resolved`] = response;
266
+ // it's resolved now so delete it
267
+ sch.__$missingReferences.splice(idx2, 1);
268
+ }
269
+ }
270
+ if (sch.__$missingReferences.length === 0) {
271
+ delete sch.__$missingReferences;
272
+ }
273
+ }
274
+ }
275
+
276
+ // keep repeating if not all compiled and at least one more was compiled in the last loop
277
+ } while (compiled !== arr.length && compiled !== lastLoopCompiled);
278
+
279
+ return report.isValid();
280
+ }
281
+
282
+ compileArrayOfSchemasLoop(mainReport: Report, arr: JsonSchemaInternal[]) {
283
+ let idx = arr.length,
284
+ compiledCount = 0;
285
+
286
+ while (idx--) {
287
+ // try to compile each schema separately
288
+ const report = new Report(mainReport);
289
+ const isValid = this.compileSchema(report, arr[idx]);
290
+ if (isValid) {
291
+ compiledCount++;
292
+ }
293
+
294
+ // copy errors to report
295
+ mainReport.errors = mainReport.errors.concat(report.errors);
296
+ }
297
+
298
+ return compiledCount;
299
+ }
300
+ }