z-schema 6.0.2 → 7.0.0-beta.2

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 (51) hide show
  1. package/README.md +154 -134
  2. package/bin/z-schema +128 -124
  3. package/cjs/ZSchema.d.ts +227 -0
  4. package/cjs/ZSchema.js +13785 -0
  5. package/dist/Errors.js +50 -0
  6. package/dist/FormatValidators.js +136 -0
  7. package/{src → dist}/JsonValidation.js +184 -213
  8. package/dist/Report.js +220 -0
  9. package/{src → dist}/SchemaCache.js +67 -82
  10. package/{src → dist}/SchemaCompilation.js +89 -129
  11. package/dist/SchemaValidation.js +631 -0
  12. package/{src → dist}/Utils.js +96 -104
  13. package/dist/ZSchema.js +365 -0
  14. package/dist/index.js +2 -0
  15. package/dist/schemas/hyper-schema.json +156 -0
  16. package/dist/schemas/schema.json +151 -0
  17. package/dist/types/Errors.d.ts +44 -0
  18. package/dist/types/FormatValidators.d.ts +12 -0
  19. package/dist/types/JsonValidation.d.ts +37 -0
  20. package/dist/types/Report.d.ts +87 -0
  21. package/dist/types/SchemaCache.d.ts +26 -0
  22. package/dist/types/SchemaCompilation.d.ts +1 -0
  23. package/dist/types/SchemaValidation.d.ts +6 -0
  24. package/dist/types/Utils.d.ts +64 -0
  25. package/dist/types/ZSchema.d.ts +97 -0
  26. package/dist/types/index.d.ts +2 -0
  27. package/package.json +59 -45
  28. package/src/Errors.ts +56 -0
  29. package/src/FormatValidators.ts +136 -0
  30. package/src/JsonValidation.ts +624 -0
  31. package/src/Report.ts +337 -0
  32. package/src/SchemaCache.ts +189 -0
  33. package/src/SchemaCompilation.ts +293 -0
  34. package/src/SchemaValidation.ts +629 -0
  35. package/src/Utils.ts +286 -0
  36. package/src/ZSchema.ts +467 -0
  37. package/src/index.ts +3 -0
  38. package/src/schemas/_ +0 -0
  39. package/umd/ZSchema.js +13791 -0
  40. package/umd/ZSchema.min.js +1 -0
  41. package/dist/ZSchema-browser-min.js +0 -2
  42. package/dist/ZSchema-browser-min.js.map +0 -1
  43. package/dist/ZSchema-browser-test.js +0 -32247
  44. package/dist/ZSchema-browser.js +0 -12745
  45. package/index.d.ts +0 -175
  46. package/src/Errors.js +0 -60
  47. package/src/FormatValidators.js +0 -129
  48. package/src/Polyfills.js +0 -16
  49. package/src/Report.js +0 -299
  50. package/src/SchemaValidation.js +0 -619
  51. package/src/ZSchema.js +0 -409
package/src/ZSchema.ts ADDED
@@ -0,0 +1,467 @@
1
+ import get from 'lodash.get';
2
+ import { Report, SchemaError, SchemaErrorDetail } from './Report.js';
3
+ import { FormatValidators } from './FormatValidators.js';
4
+ import * as JsonValidation from './JsonValidation.js';
5
+ import * as SchemaCache from './SchemaCache.js';
6
+ import * as SchemaCompilation from './SchemaCompilation.js';
7
+ import * as SchemaValidation from './SchemaValidation.js';
8
+ import * as Utils from './Utils.js';
9
+ import Draft4Schema from './schemas/schema.json' with { type: 'json' };
10
+ import Draft4HyperSchema from './schemas/hyper-schema.json' with { type: 'json' };
11
+ import type { Errors } from './Errors.js';
12
+
13
+ /**
14
+ * default options
15
+ */
16
+ const defaultOptions = {
17
+ // default timeout for all async tasks
18
+ asyncTimeout: 2000,
19
+ // force additionalProperties and additionalItems to be defined on "object" and "array" types
20
+ forceAdditional: false,
21
+ // assume additionalProperties and additionalItems are defined as "false" where appropriate
22
+ assumeAdditional: false,
23
+ // do case insensitive comparison for enums
24
+ enumCaseInsensitiveComparison: false,
25
+ // force items to be defined on "array" types
26
+ forceItems: false,
27
+ // force minItems to be defined on "array" types
28
+ forceMinItems: false,
29
+ // force maxItems to be defined on "array" types
30
+ forceMaxItems: false,
31
+ // force minLength to be defined on "string" types
32
+ forceMinLength: false,
33
+ // force maxLength to be defined on "string" types
34
+ forceMaxLength: false,
35
+ // force properties or patternProperties to be defined on "object" types
36
+ forceProperties: false,
37
+ // ignore references that cannot be resolved (remote schemas) // TODO: make sure this is only for remote schemas, not local ones
38
+ ignoreUnresolvableReferences: false,
39
+ // disallow usage of keywords that this validator can't handle
40
+ noExtraKeywords: false,
41
+ // disallow usage of schema's without "type" defined
42
+ noTypeless: false,
43
+ // disallow zero length strings in validated objects
44
+ noEmptyStrings: false,
45
+ // disallow zero length arrays in validated objects
46
+ noEmptyArrays: false,
47
+ // forces "uri" format to be in fully rfc3986 compliant
48
+ strictUris: false,
49
+ // turn on some of the above
50
+ strictMode: false,
51
+ // report error paths as an array of path segments to get to the offending node
52
+ reportPathAsArray: false,
53
+ // stop validation as soon as an error is found
54
+ breakOnFirstError: false,
55
+ // check if schema follows best practices and common sense
56
+ pedanticCheck: false,
57
+ // ignore unknown formats (do not report them as an error)
58
+ ignoreUnknownFormats: false,
59
+ // function to be called on every schema
60
+ customValidator: null,
61
+ };
62
+
63
+ function normalizeOptions(options) {
64
+ let normalized;
65
+
66
+ // options
67
+ if (typeof options === 'object') {
68
+ let keys = Object.keys(options),
69
+ idx = keys.length,
70
+ key;
71
+
72
+ // check that the options are correctly configured
73
+ while (idx--) {
74
+ key = keys[idx];
75
+ if (defaultOptions[key] === undefined) {
76
+ throw new Error('Unexpected option passed to constructor: ' + key);
77
+ }
78
+ }
79
+
80
+ // copy the default options into passed options
81
+ keys = Object.keys(defaultOptions);
82
+ idx = keys.length;
83
+ while (idx--) {
84
+ key = keys[idx];
85
+ if (options[key] === undefined) {
86
+ options[key] = Utils.clone(defaultOptions[key]);
87
+ }
88
+ }
89
+
90
+ normalized = options;
91
+ } else {
92
+ normalized = Utils.clone(defaultOptions);
93
+ }
94
+
95
+ if (normalized.strictMode === true) {
96
+ normalized.forceAdditional = true;
97
+ normalized.forceItems = true;
98
+ normalized.forceMaxLength = true;
99
+ normalized.forceProperties = true;
100
+ normalized.noExtraKeywords = true;
101
+ normalized.noTypeless = true;
102
+ normalized.noEmptyStrings = true;
103
+ normalized.noEmptyArrays = true;
104
+ }
105
+
106
+ return normalized;
107
+ }
108
+
109
+ export interface ZSchemaOptions {
110
+ asyncTimeout?: number;
111
+ forceAdditional?: boolean;
112
+ assumeAdditional?: boolean;
113
+ forceItems?: boolean;
114
+ forceMinItems?: boolean;
115
+ forceMaxItems?: boolean;
116
+ forceMinLength?: boolean;
117
+ forceMaxLength?: boolean;
118
+ forceProperties?: boolean;
119
+ ignoreUnresolvableReferences?: boolean;
120
+ noExtraKeywords?: boolean;
121
+ noTypeless?: boolean;
122
+ noEmptyStrings?: boolean;
123
+ noEmptyArrays?: boolean;
124
+ strictUris?: boolean;
125
+ strictMode?: boolean;
126
+ reportPathAsArray?: boolean;
127
+ breakOnFirstError?: boolean;
128
+ pedanticCheck?: boolean;
129
+ ignoreUnknownFormats?: boolean;
130
+ customValidator?: (report: Report, schema: unknown, json: unknown) => void;
131
+ }
132
+
133
+ export interface ValidateOptions {
134
+ schemaPath?: string;
135
+ includeErrors?: Array<keyof typeof Errors>;
136
+ }
137
+
138
+ type ValidateCallback = (e: Error, valid: boolean) => void;
139
+
140
+ // a sync function that loads schemas for future use, for example from schemas directory, during server startup
141
+ type SchemaReader = (uri: string) => unknown;
142
+
143
+ export class ZSchema {
144
+ public lastReport: Report | undefined;
145
+
146
+ /**
147
+ * Register a custom format.
148
+ *
149
+ * @param name - name of the custom format
150
+ * @param validatorFunction - custom format validator function.
151
+ * Returns `true` if `value` matches the custom format.
152
+ */
153
+ public static registerFormat(formatName: string, validatorFunction: (value: unknown) => boolean): void {
154
+ FormatValidators[formatName] = validatorFunction;
155
+ }
156
+
157
+ /**
158
+ * Unregister a format.
159
+ *
160
+ * @param name - name of the custom format
161
+ */
162
+ public static unregisterFormat(name: string): void {
163
+ delete FormatValidators[name];
164
+ }
165
+
166
+ /**
167
+ * Get the list of all registered formats.
168
+ *
169
+ * Both the names of the burned-in formats and the custom format names are
170
+ * returned by this function.
171
+ *
172
+ * @returns {string[]} the list of all registered format names.
173
+ */
174
+ public static getRegisteredFormats(): string[] {
175
+ return Object.keys(FormatValidators);
176
+ }
177
+
178
+ public static getDefaultOptions(): ZSchemaOptions {
179
+ return Utils.cloneDeep(defaultOptions);
180
+ }
181
+
182
+ private cache: Record<string, string>;
183
+ private referenceCache: Array<string>;
184
+ private validateOptions: ValidateOptions;
185
+ options: ZSchemaOptions;
186
+
187
+ constructor(options?: ZSchemaOptions) {
188
+ this.cache = {};
189
+ this.referenceCache = [];
190
+ this.validateOptions = {};
191
+
192
+ this.options = normalizeOptions(options);
193
+
194
+ // Disable strict validation for the built-in schemas
195
+ const metaschemaOptions = normalizeOptions({});
196
+
197
+ this.setRemoteReference('http://json-schema.org/draft-04/schema', Draft4Schema, metaschemaOptions);
198
+ this.setRemoteReference('http://json-schema.org/draft-04/hyper-schema', Draft4HyperSchema, metaschemaOptions);
199
+ }
200
+
201
+ /**
202
+ * @param schema - JSON object representing schema
203
+ * @returns {boolean} true if schema is valid.
204
+ */
205
+ validateSchema(schema: unknown): boolean {
206
+ if (Array.isArray(schema) && schema.length === 0) {
207
+ throw new Error('.validateSchema was called with an empty array');
208
+ }
209
+
210
+ const report = new Report(this.options);
211
+
212
+ schema = SchemaCache.getSchema.call(this, report, schema);
213
+
214
+ const compiled = SchemaCompilation.compileSchema.call(this, report, schema);
215
+ if (compiled) {
216
+ SchemaValidation.validateSchema.call(this, report, schema);
217
+ }
218
+
219
+ this.lastReport = report;
220
+ return report.isValid();
221
+ }
222
+
223
+ /**
224
+ * @param json - either a JSON string or a parsed JSON object
225
+ * @param schema - the JSON object representing the schema
226
+ * @returns true if json matches schema
227
+ */
228
+ validate(json, schema, options?: ValidateOptions, callback?: ValidateCallback): boolean;
229
+ validate(json, schema, callback?): boolean;
230
+ validate(json, schema): boolean;
231
+ validate(json, schema, options?: ValidateOptions | ValidateCallback, callback?: ValidateCallback): boolean {
232
+ if (typeof options === 'function') {
233
+ callback = options;
234
+ options = {};
235
+ }
236
+ if (!options) {
237
+ options = {};
238
+ }
239
+
240
+ this.validateOptions = options;
241
+
242
+ const whatIs = Utils.whatIs(schema);
243
+ if (whatIs !== 'string' && whatIs !== 'object') {
244
+ const e = new Error('Invalid .validate call - schema must be a string or object but ' + whatIs + ' was passed!');
245
+ if (callback) {
246
+ setTimeout(function () {
247
+ callback(e, false);
248
+ }, 0);
249
+ return;
250
+ }
251
+ throw e;
252
+ }
253
+
254
+ let foundError = false;
255
+ const report = new Report(this.options);
256
+ report.json = json;
257
+
258
+ if (typeof schema === 'string') {
259
+ const schemaName = schema;
260
+ schema = SchemaCache.getSchema.call(this, report, schemaName);
261
+ if (!schema) {
262
+ throw new Error("Schema with id '" + schemaName + "' wasn't found in the validator cache!");
263
+ }
264
+ } else {
265
+ schema = SchemaCache.getSchema.call(this, report, schema);
266
+ }
267
+
268
+ let compiled = false;
269
+ if (!foundError) {
270
+ compiled = SchemaCompilation.compileSchema.call(this, report, schema);
271
+ }
272
+ if (!compiled) {
273
+ this.lastReport = report;
274
+ foundError = true;
275
+ }
276
+
277
+ let validated = false;
278
+ if (!foundError) {
279
+ validated = SchemaValidation.validateSchema.call(this, report, schema);
280
+ }
281
+ if (!validated) {
282
+ this.lastReport = report;
283
+ foundError = true;
284
+ }
285
+
286
+ if (options.schemaPath) {
287
+ report.rootSchema = schema;
288
+ schema = get(schema, options.schemaPath);
289
+ if (!schema) {
290
+ throw new Error("Schema path '" + options.schemaPath + "' wasn't found in the schema!");
291
+ }
292
+ }
293
+
294
+ if (!foundError) {
295
+ JsonValidation.validate.call(this, report, schema, json);
296
+ }
297
+
298
+ if (callback) {
299
+ report.processAsyncTasks(this.options.asyncTimeout, callback);
300
+ return;
301
+ } else if (report.asyncTasks.length > 0) {
302
+ throw new Error(
303
+ 'This validation has async tasks and cannot be done in sync mode, please provide callback argument.'
304
+ );
305
+ }
306
+
307
+ // assign lastReport so errors are retrievable in sync mode
308
+ this.lastReport = report;
309
+ return report.isValid();
310
+ }
311
+
312
+ /**
313
+ * Returns an Error object for the most recent failed validation, or null if the validation was successful.
314
+ */
315
+ getLastError(): SchemaError {
316
+ if (this.lastReport.errors.length === 0) {
317
+ return null;
318
+ }
319
+ const e: SchemaError = new Error();
320
+ e.name = 'z-schema validation error';
321
+ e.message = this.lastReport.commonErrorMessage;
322
+ e.details = this.lastReport.errors;
323
+ return e;
324
+ }
325
+
326
+ /**
327
+ * Returns the error details for the most recent validation, or undefined if the validation was successful.
328
+ * This is the same list as the SchemaError.details property.
329
+ */
330
+ getLastErrors(): SchemaErrorDetail[] {
331
+ return this.lastReport && this.lastReport.errors.length > 0 ? this.lastReport.errors : null;
332
+ }
333
+
334
+ setRemoteReference(uri, schema, validationOptions) {
335
+ if (typeof schema === 'string') {
336
+ schema = JSON.parse(schema);
337
+ } else {
338
+ schema = Utils.cloneDeep(schema);
339
+ }
340
+
341
+ if (validationOptions) {
342
+ schema.__$validationOptions = normalizeOptions(validationOptions);
343
+ }
344
+
345
+ SchemaCache.cacheSchemaByUri.call(this, uri, schema);
346
+ }
347
+
348
+ compileSchema(schema) {
349
+ const report = new Report(this.options);
350
+
351
+ schema = SchemaCache.getSchema.call(this, report, schema);
352
+
353
+ SchemaCompilation.compileSchema.call(this, report, schema);
354
+
355
+ this.lastReport = report;
356
+ return report.isValid();
357
+ }
358
+
359
+ getMissingReferences(arr?) {
360
+ arr = arr || this.lastReport.errors;
361
+ let res = [],
362
+ idx = arr.length;
363
+ while (idx--) {
364
+ const error = arr[idx];
365
+ if (error.code === 'UNRESOLVABLE_REFERENCE') {
366
+ const reference = error.params[0];
367
+ if (res.indexOf(reference) === -1) {
368
+ res.push(reference);
369
+ }
370
+ }
371
+ if (error.inner) {
372
+ res = res.concat(this.getMissingReferences(error.inner));
373
+ }
374
+ }
375
+ return res;
376
+ }
377
+
378
+ getMissingRemoteReferences() {
379
+ const missingReferences = this.getMissingReferences();
380
+ const missingRemoteReferences = [];
381
+ let idx = missingReferences.length;
382
+ while (idx--) {
383
+ const remoteReference = SchemaCache.getRemotePath(missingReferences[idx]);
384
+ if (remoteReference && missingRemoteReferences.indexOf(remoteReference) === -1) {
385
+ missingRemoteReferences.push(remoteReference);
386
+ }
387
+ }
388
+ return missingRemoteReferences;
389
+ }
390
+
391
+ getResolvedSchema(schema) {
392
+ const report = new Report(this.options);
393
+ schema = SchemaCache.getSchema.call(this, report, schema);
394
+
395
+ // clone before making any modifications
396
+ schema = Utils.cloneDeep(schema);
397
+
398
+ const visited = [];
399
+
400
+ // clean-up the schema and resolve references
401
+ const cleanup = function (schema) {
402
+ let key;
403
+ const typeOf = Utils.whatIs(schema);
404
+ if (typeOf !== 'object' && typeOf !== 'array') {
405
+ return;
406
+ }
407
+
408
+ if (schema.___$visited) {
409
+ return;
410
+ }
411
+
412
+ schema.___$visited = true;
413
+ visited.push(schema);
414
+
415
+ if (schema.$ref && schema.__$refResolved) {
416
+ const from = schema.__$refResolved;
417
+ const to = schema;
418
+ delete schema.$ref;
419
+ delete schema.__$refResolved;
420
+ for (key in from) {
421
+ if (Object.prototype.hasOwnProperty.call(from, key)) {
422
+ to[key] = from[key];
423
+ }
424
+ }
425
+ }
426
+ for (key in schema) {
427
+ if (Object.prototype.hasOwnProperty.call(schema, key)) {
428
+ if (key.indexOf('__$') === 0) {
429
+ delete schema[key];
430
+ } else {
431
+ cleanup(schema[key]);
432
+ }
433
+ }
434
+ }
435
+ };
436
+
437
+ cleanup(schema);
438
+ visited.forEach(function (s) {
439
+ delete s.___$visited;
440
+ });
441
+
442
+ this.lastReport = report;
443
+ if (report.isValid()) {
444
+ return schema;
445
+ } else {
446
+ throw this.getLastError();
447
+ }
448
+ }
449
+
450
+ static schemaReader: SchemaReader;
451
+
452
+ setSchemaReader(schemaReader) {
453
+ return ZSchema.setSchemaReader(schemaReader);
454
+ }
455
+
456
+ getSchemaReader() {
457
+ return ZSchema.schemaReader;
458
+ }
459
+
460
+ static setSchemaReader(schemaReader) {
461
+ ZSchema.schemaReader = schemaReader;
462
+ }
463
+
464
+ static schemaSymbol = Utils.schemaSymbol;
465
+
466
+ static jsonSymbol = Utils.jsonSymbol;
467
+ }
package/src/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ import { ZSchema } from './ZSchema.js';
2
+
3
+ export default ZSchema;
package/src/schemas/_ ADDED
File without changes