From f40249215be2126da3f82d99b5f46c1eba809ef3 Mon Sep 17 00:00:00 2001 From: Cyrille Tuzi Date: Sun, 27 Jan 2019 12:20:26 +0100 Subject: [PATCH 01/10] feat: new JSONSchem, and shortcuts and auto-inference for basic types --- .../src/lib/get-item-overloads.spec.ts | 322 ++++++++++++++++++ .../local-storage/src/lib/lib.service.spec.ts | 31 +- .../local-storage/src/lib/lib.service.ts | 70 +++- .../src/lib/validation/json-schema.ts | 197 ++++++++--- .../lib/validation/json-validation.spec.ts | 294 +++++++--------- .../src/lib/validation/json-validator.ts | 229 ++++--------- .../ngx-pwa/local-storage/src/public_api.ts | 4 +- 7 files changed, 762 insertions(+), 385 deletions(-) create mode 100644 projects/ngx-pwa/local-storage/src/lib/get-item-overloads.spec.ts diff --git a/projects/ngx-pwa/local-storage/src/lib/get-item-overloads.spec.ts b/projects/ngx-pwa/local-storage/src/lib/get-item-overloads.spec.ts new file mode 100644 index 00000000..c7228810 --- /dev/null +++ b/projects/ngx-pwa/local-storage/src/lib/get-item-overloads.spec.ts @@ -0,0 +1,322 @@ +import { LocalStorage } from './lib.service'; +import { IndexedDBDatabase } from './databases/indexeddb-database'; +import { JSONValidator } from './validation/json-validator'; +import { JSONSchemaString, JSONSchema, JSONSchemaArrayOf } from './validation/json-schema'; + +describe('getItem() overload signature', () => { + + let localStorageService: LocalStorage; + + beforeEach((done: DoneFn) => { + + localStorageService = new LocalStorage(new IndexedDBDatabase(), new JSONValidator()); + + localStorageService.clear().subscribe(() => { + + done(); + + }); + + }); + + it('should compile with no schema and without cast', (done: DoneFn) => { + + localStorageService.getItem('test').subscribe((_) => { + + expect().nothing(); + + done(); + + }); + + }); + + it('should compile with no schema and with cast', (done: DoneFn) => { + + localStorageService.getItem('test').subscribe((_) => { + + expect().nothing(); + + done(); + + }); + + }); + + it('should compile with literal basic schema and without type param', (done: DoneFn) => { + + localStorageService.getItem('test', { schema: { type: 'string' } }).subscribe((_) => { + + expect().nothing(); + + done(); + + }); + + }); + + it('should compile with literal basic schema and with type param', (done: DoneFn) => { + + localStorageService.getItem('test', { schema: { type: 'string' } }).subscribe((_) => { + + expect().nothing(); + + done(); + + }); + + }); + + it('should compile with shortcut type and without type param', (done: DoneFn) => { + + localStorageService.getItem('test', { type: 'string' }).subscribe((_) => { + + expect().nothing(); + + done(); + + }); + + }); + + it('should compile with shortcut type and with type param', (done: DoneFn) => { + + localStorageService.getItem('test', { type: 'string' }).subscribe((_) => { + + expect().nothing(); + + done(); + + }); + + }); + + it('should compile with literal basic schema and extra options', (done: DoneFn) => { + + localStorageService.getItem('test', { schema: { type: 'string', maxLength: 10 } }).subscribe((_) => { + + expect().nothing(); + + done(); + + }); + + }); + + it('should compile with prepared basic schema and with specific interface', (done: DoneFn) => { + + const schema: JSONSchemaString = { type: 'string' }; + + localStorageService.getItem('test', { schema }).subscribe((_) => { + + expect().nothing(); + + done(); + + }); + + }); + + it('should compile with prepared basic schema and with generic interface', (done: DoneFn) => { + + const schema: JSONSchema = { type: 'string' }; + + localStorageService.getItem('test', { schema }).subscribe((_) => { + + expect().nothing(); + + done(); + + }); + + }); + + it('should compile for string type', (done: DoneFn) => { + + localStorageService.getItem('test', { schema: { type: 'string' } }).subscribe((_) => { + + expect().nothing(); + + done(); + + }); + + }); + + it('should compile for number type', (done: DoneFn) => { + + localStorageService.getItem('test', { schema: { type: 'number' } }).subscribe((_) => { + + expect().nothing(); + + done(); + + }); + + }); + + it('should compile for integer type', (done: DoneFn) => { + + localStorageService.getItem('test', { schema: { type: 'integer' } }).subscribe((_) => { + + expect().nothing(); + + done(); + + }); + + }); + + it('should compile for boolean type', (done: DoneFn) => { + + localStorageService.getItem('test', { schema: { type: 'boolean' } }).subscribe((_) => { + + expect().nothing(); + + done(); + + }); + + }); + + it('should compile for array of strings', (done: DoneFn) => { + + localStorageService.getItem('test', { schema: { + type: 'array', + items: { type: 'string' } + } }).subscribe((_) => { + + expect().nothing(); + + done(); + + }); + + }); + + it('should compile for array of numbers', (done: DoneFn) => { + + localStorageService.getItem('test', { schema: { + type: 'array', + items: { type: 'number' } + } }).subscribe((_) => { + + expect().nothing(); + + done(); + + }); + + }); + + it('should compile for array of integers', (done: DoneFn) => { + + localStorageService.getItem('test', { schema: { + type: 'array', + items: { type: 'integer' } + } }).subscribe((_) => { + + expect().nothing(); + + done(); + + }); + + }); + + it('should compile for array of booleans', (done: DoneFn) => { + + localStorageService.getItem('test', { schema: { + type: 'array', + items: { type: 'boolean' } + } }).subscribe((_) => { + + expect().nothing(); + + done(); + + }); + + }); + + it('should compile for array with extra options', (done: DoneFn) => { + + const schema: JSONSchemaArrayOf = { + type: 'array', + items: { type: 'string' }, + maxItems: 5 + }; + + localStorageService.getItem('test', { schema }).subscribe((_) => { + + expect().nothing(); + + done(); + + }); + + }); + + it('should compile for array of objects', (done: DoneFn) => { + + interface Test { + test: string; + } + + localStorageService.getItem('test', { schema: { + type: 'array', + items: { + type: 'object', + properties: { + test: { type: 'string' } + } + } + } }).subscribe((_) => { + + expect().nothing(); + + done(); + + }); + + }); + + it('should compile for objects without param type', (done: DoneFn) => { + + localStorageService.getItem('test', { schema: { + type: 'object', + properties: { + test: { type: 'string' } + } + } }).subscribe((_) => { + + expect().nothing(); + + done(); + + }); + + }); + + it('should compile for objects with param type', (done: DoneFn) => { + + interface Test { + test: string; + } + + localStorageService.getItem('test', { schema: { + type: 'object', + properties: { + test: { type: 'string' } + } + } }).subscribe((_) => { + + expect().nothing(); + + done(); + + }); + + }); + +}); diff --git a/projects/ngx-pwa/local-storage/src/lib/lib.service.spec.ts b/projects/ngx-pwa/local-storage/src/lib/lib.service.spec.ts index bf75ddf0..98fc6e75 100755 --- a/projects/ngx-pwa/local-storage/src/lib/lib.service.spec.ts +++ b/projects/ngx-pwa/local-storage/src/lib/lib.service.spec.ts @@ -321,12 +321,12 @@ function tests(localStorageService: LocalStorage) { const value = { unexpected: 'value' }; - // TODO: delete cast when TS 3.2 issue is fixed const schema: JSONSchema = { + type: 'object', properties: { expected: { type: 'string' - } as JSONSchemaString + } }, required: ['expected'] }; @@ -357,8 +357,8 @@ function tests(localStorageService: LocalStorage) { const value = { expected: 'value' }; - // TODO: delete cast when TS 3.2 issue is fixed const schema: JSONSchema = { + type: 'object', properties: { expected: { type: 'string' @@ -391,6 +391,7 @@ function tests(localStorageService: LocalStorage) { // TODO: delete cast when TS 3.2 issue is fixed const schema: JSONSchema = { + type: 'object', properties: { expected: { type: 'string' @@ -895,7 +896,7 @@ describe('LocalStorage with IndexedDB', () => { } - function testGetCompatibilityWithNativeAPI(done: DoneFn, value: any, schema: JSONSchema) { + function testGetCompatibilityWithNativeAPI(done: DoneFn, value: any, schema?: JSONSchema) { const index = 'test'; @@ -948,7 +949,7 @@ describe('LocalStorage with IndexedDB', () => { } - const getTestValues: [any, JSONSchema][] = [ + const getTestValues: [any, JSONSchema | undefined][] = [ ['hello', { type: 'string' }], ['', { type: 'string' }], [0, { type: 'number' }], @@ -957,15 +958,13 @@ describe('LocalStorage with IndexedDB', () => { [false, { type: 'boolean' }], // TODO: delete cast when TS 3.2 issue is fixed [[1, 2, 3], { items: { type: 'number' } } as JSONSchema], - [{ test: 'value' }, { properties: { test: { type: 'string' } } } as JSONSchema], - [null, { type: 'null' }], - [undefined, { type: 'null' }], + [{ test: 'value' }, { type: 'object', properties: { test: { type: 'string' } } } as JSONSchema], ]; for (const [getTestValue, getTestSchema] of getTestValues) { it(`should get a value on an index previously used by another lib API - (will be pending in IE/Firefox private mode and 1 pending in Edge/IE because of null)`, (done: DoneFn) => { + (will be pending in IE/Firefox private mode)`, (done: DoneFn) => { testGetCompatibilityWithNativeAPI(done, getTestValue, getTestSchema); @@ -973,6 +972,20 @@ describe('LocalStorage with IndexedDB', () => { } + it(`should get a null value on an index previously used by another lib API + (will be pending in IE/Firefox private mode and in Edge/IE because of null)`, (done: DoneFn) => { + + testGetCompatibilityWithNativeAPI(done, null); + + }); + + it(`should get a value on an index previously used by another lib API + (will be pending in IE/Firefox private mode)`, (done: DoneFn) => { + + testGetCompatibilityWithNativeAPI(done, undefined); + + }); + }); describe('LocalStorage with localStorage and a prefix', () => { diff --git a/projects/ngx-pwa/local-storage/src/lib/lib.service.ts b/projects/ngx-pwa/local-storage/src/lib/lib.service.ts index ddaf18ec..6513f2fe 100755 --- a/projects/ngx-pwa/local-storage/src/lib/lib.service.ts +++ b/projects/ngx-pwa/local-storage/src/lib/lib.service.ts @@ -3,11 +3,15 @@ import { Observable, throwError, of } from 'rxjs'; import { mergeMap } from 'rxjs/operators'; import { LocalDatabase } from './databases/local-database'; -import { JSONSchema } from './validation/json-schema'; +import { + JSONSchema, JSONSchemaBoolean, JSONSchemaInteger, + JSONSchemaNumber, JSONSchemaString, JSONSchemaArrayOf +} from './validation/json-schema'; import { JSONValidator } from './validation/json-validator'; export interface LSGetItemOptions { schema?: JSONSchema | null; + type?: 'boolean' | 'integer' | 'number' | 'string' | 'boolean[]' | 'integer[]' | 'number[]' | 'string[]' | null; } @Injectable({ @@ -25,16 +29,31 @@ export class LocalStorage { } protected readonly getItemOptionsDefault: LSGetItemOptions = { - schema: null + schema: null, + type: null, }; constructor(protected database: LocalDatabase, protected jsonValidator: JSONValidator) {} /** - * Gets an item value in local storage + * Gets an item value in local storage. + * The signature has many overloads due to validation, please refer to the documentation. + * @see https://github.com/cyrilletuzi/angular-async-local-storage/blob/master/docs/VALIDATION.md * @param key The item's key * @returns The item's value if the key exists, null otherwise, wrapped in an RxJS Observable */ + getItem(key: string, options: LSGetItemOptions & + ({ schema: JSONSchemaBoolean } | { type: 'boolean' })): Observable; + getItem(key: string, options: LSGetItemOptions & + ({ schema: JSONSchemaInteger | JSONSchemaNumber } | { type: 'integer' | 'number' })): Observable; + getItem(key: string, options: LSGetItemOptions & + ({ schema: JSONSchemaString } | { type: 'string' })): Observable; + getItem(key: string, options: LSGetItemOptions & + ({ schema: JSONSchemaArrayOf } | { type: 'boolean[]' })): Observable; + getItem(key: string, options: LSGetItemOptions & + ({ schema: JSONSchemaArrayOf } | { type: 'integer[]' | 'number[]'})): Observable; + getItem(key: string, options: LSGetItemOptions & + ({ schema: JSONSchemaArrayOf } | { type: 'string[]' })): Observable; getItem(key: string, options: LSGetItemOptions & { schema: JSONSchema }): Observable; getItem(key: string, options?: LSGetItemOptions): Observable; getItem(key: string, options = this.getItemOptionsDefault) { @@ -48,7 +67,15 @@ export class LocalStorage { return of(null); - } else if (options.schema) { + } + + if (!options.schema && options.type) { + + options.schema = this.typeToSchema(options.type); + + } + + if (options.schema) { let validation = true; @@ -167,4 +194,39 @@ export class LocalStorage { } + private typeToSchema(type: LSGetItemOptions['type']): JSONSchema | null { + + switch (type) { + + case 'boolean': + return { type: 'boolean' }; + + case 'integer': + return { type: 'integer' }; + + case 'number': + return { type: 'number' }; + + case 'string': + return { type: 'string' }; + + case 'boolean[]': + return { type: 'array', items: { type: 'boolean' } }; + + case 'integer[]': + return { type: 'array', items: { type: 'integer' } }; + + case 'number[]': + return { type: 'array', items: { type: 'number' } }; + + case 'string[]': + return { type: 'array', items: { type: 'string' } }; + + default: + return null; + + } + + } + } diff --git a/projects/ngx-pwa/local-storage/src/lib/validation/json-schema.ts b/projects/ngx-pwa/local-storage/src/lib/validation/json-schema.ts index 497555b3..728108ef 100644 --- a/projects/ngx-pwa/local-storage/src/lib/validation/json-schema.ts +++ b/projects/ngx-pwa/local-storage/src/lib/validation/json-schema.ts @@ -1,41 +1,119 @@ -export interface JSONSchemaConst { +/** + * JSON Schema to describe a boolean value. + */ +export interface JSONSchemaBoolean { + + /** + * Type for a boolean value. + */ + type: 'boolean'; /** * Checks if a value is strictly equal to this. - * Can't be an object or array, as two objects or arrays are never equal. */ - const: string | number | boolean | null; + const?: boolean; } -export interface JSONSchemaEnum { +/** + * JSON Schema to describe a number value. + */ +export interface JSONSchemaNumber { + + /** + * Type for a numeric value. + */ + type: 'number'; + + /** + * Checks if a value is strictly equal to this. + */ + const?: number; /** * Checks if a value is strictly equal to one of the value of enum. - * Can't be an object or array, as two objects or arrays are never equal. */ - enum: (string | number | boolean | null)[]; + enum?: number[]; -} + /** + * Check if a number is a multiple of x. + * Must be strictly greater than 0. + */ + multipleOf?: number; -export interface JSONSchemaBoolean { + /** + * Check if a number is lower or equal than this maximum. + */ + maximum?: number; /** - * Type for a boolean value. + * Check if a number is strictly lower than this maximum. */ - type: 'boolean'; + exclusiveMaximum?: number; + + /** + * Check if a number is greater or equal than this minimum. + */ + minimum?: number; + + /** + * Check if a number is strictly greater than this minimum. + */ + exclusiveMinimum?: number; } -export interface JSONSchemaNull { +/** + * JSON Schema to describe an integer value. + */ +export interface JSONSchemaInteger { + + /** + * Type for an integer value. + */ + type: 'integer'; + + /** + * Checks if a value is strictly equal to this. + */ + const?: number; + + /** + * Checks if a value is strictly equal to one of the value of enum. + */ + enum?: number[]; /** - * Type for a null value. + * Check if a number is a multiple of x. + * Must be strictly greater than 0. */ - type: 'null'; + multipleOf?: number; + + /** + * Check if a number is lower or equal than this maximum. + */ + maximum?: number; + + /** + * Check if a number is strictly lower than this maximum. + */ + exclusiveMaximum?: number; + + /** + * Check if a number is greater or equal than this minimum. + */ + minimum?: number; + + /** + * Check if a number is strictly greater than this minimum. + */ + exclusiveMinimum?: number; } +/** + * JSON Schema to describe a string value. + */ export interface JSONSchemaString { /** @@ -43,6 +121,16 @@ export interface JSONSchemaString { */ type: 'string'; + /** + * Checks if a value is strictly equal to this. + */ + const?: string; + + /** + * Checks if a value is strictly equal to one of the value of enum. + */ + enum?: string[]; + /** * Maxium length for a string. * Must be a non-negative integer. @@ -57,59 +145,69 @@ export interface JSONSchemaString { /** * Pattern to match for a string. - * Must be a valid regular expression, WITHOUT the / delimiters. + * Must be a valid regular expression, *without* the `/` delimiters. */ pattern?: string; } -export interface JSONSchemaNumeric { - - type: 'number' | 'integer'; +/** + * JSON schema to describe an array of values. + * For arrays of primitive types (booleans, numbers and strings), + * prefer the more specific `JSONSchemaArrayOf` interface. + */ +export interface JSONSchemaArray { /** - * Check if a number is a multiple of x. - * Must be strictly greater than 0. + * Type for an array of values. */ - multipleOf?: number; + type: 'array'; /** - * Check if a number is less or equal than this maximum. + * Schema for the values of an array. */ - maximum?: number; + items: JSONSchema; /** - * Check if a number is strictly less than this maximum. + * Check if an array length is lower or equal to this value. + * Must be a non negative integer. */ - exclusiveMaximum?: number; + maxItems?: number; /** - * Check if a number is greater or equal than this minimum. + * Check if an array length is greater or equal to this value. + * Must be a non negative integer. */ - minimum?: number; + minItems?: number; /** - * Check if a number is strictly greater than this minimum. + * Check if an array only have unique values. */ - exclusiveMinimum?: number; + uniqueItems?: boolean; } -export interface JSONSchemaArray { +/** + * JSON Schema to describe an array of primitive values: + * - array of booleans: `JSONSchemaArrayOf`, + * - array of numbers: `JSONSchemaArrayOf`, + * - array of integers: `JSONSchemaArrayOf`, + * - array of strings: `JSONSchemaArrayOf`. + */ +export interface JSONSchemaArrayOf { /** - * Type for an array value. Avoid to explicit this, "items" is enough, + * Type for an array of values. */ - type?: 'array'; + type: 'array'; /** * Schema for the values of an array. - * 'type' of values should be a string (not an array of type). */ - items: JSONSchema | JSONSchema[]; + items: T; /** - * Check if an array length is less or equal to this value. + * Check if an array length is lower or equal to this value. * Must be a non negative integer. */ maxItems?: number; @@ -127,15 +225,18 @@ export interface JSONSchemaArray { } +/** + * JSON schema to describe an object. + */ export interface JSONSchemaObject { /** - * Type for an object value. Avoid to explicit this, "properties" is enough, + * Type for an object. */ - type?: 'object'; + type: 'object'; /** - * List of properties schemas for an object. + * List of properties of the object and their associated JSON schemas. */ properties: { [k: string]: JSONSchema; @@ -143,22 +244,26 @@ export interface JSONSchemaObject { /** * Array of names of the required properties for an object. - * Properties set as required should be present in 'properties' too. - * Note that in the last spec, booleans are not supported anymore. + * Properties set as required should be present in `properties` too. */ required?: string[]; } +/** + * Available for backward-compatibility only, + * please avoid `JSONSchemaNumeric` and prefer `JSONSchemaNumber` or `JSONSchemaInteger` + * @deprecated + * @ignore + */ +export type JSONSchemaNumeric = JSONSchemaNumber | JSONSchemaInteger; + /** * Subset of the JSON Schema. - * Types are enforced to validate everything: - * each value MUST have just ONE of either 'type' or 'properties' or 'items' or 'const' or 'enum'. - * Therefore, unlike the spec, booleans are not allowed as schemas. + * Types are enforced to validate everything: each value MUST have a `type`. * @see http://json-schema.org/latest/json-schema-validation.html * Not all validation features are supported: just follow the interface. */ -export type JSONSchema = (JSONSchemaConst | JSONSchemaEnum | - JSONSchemaBoolean | JSONSchemaNull | JSONSchemaString | JSONSchemaNumeric | - JSONSchemaArray | JSONSchemaObject) - & { [k: string]: any; }; +export type JSONSchema = + JSONSchemaBoolean | JSONSchemaString | JSONSchemaNumber | JSONSchemaInteger | + JSONSchemaArray | JSONSchemaObject; diff --git a/projects/ngx-pwa/local-storage/src/lib/validation/json-validation.spec.ts b/projects/ngx-pwa/local-storage/src/lib/validation/json-validation.spec.ts index 7ef831fa..9d79bce3 100644 --- a/projects/ngx-pwa/local-storage/src/lib/validation/json-validation.spec.ts +++ b/projects/ngx-pwa/local-storage/src/lib/validation/json-validation.spec.ts @@ -1,8 +1,4 @@ import { JSONValidator } from './json-validator'; -import { - JSONSchemaConst, JSONSchemaEnum, JSONSchemaBoolean, JSONSchemaNull, - JSONSchemaNumeric, JSONSchemaString, JSONSchemaObject, JSONSchemaArray -} from './json-schema'; describe(`JSONValidator`, () => { @@ -20,7 +16,11 @@ describe(`JSONValidator`, () => { expect(() => { - jsonValidator.validate({ test: 'test' }, { properties: { test: { type: 'string' } }, additionalProperties: true }); + jsonValidator.validate({ test: 'test' }, { + type: 'object', + properties: { test: { type: 'string' } }, + additionalProperties: true + } as any); }).not.toThrow(); @@ -32,7 +32,7 @@ describe(`JSONValidator`, () => { it(`should return true on a string equal to a string const`, () => { - const test = jsonValidator.validate('test', { const: 'test' } as JSONSchemaConst); + const test = jsonValidator.validate('test', { type: 'string', const: 'test' }); expect(test).toBe(true); @@ -40,7 +40,7 @@ describe(`JSONValidator`, () => { it(`should return false on a string not equal to a string const`, () => { - const test = jsonValidator.validate('test2', { const: 'test' } as JSONSchemaConst); + const test = jsonValidator.validate('test2', { type: 'string', const: 'test' }); expect(test).toBe(false); @@ -48,7 +48,7 @@ describe(`JSONValidator`, () => { it(`should return true on a number equal to a number const`, () => { - const test = jsonValidator.validate(1.5, { const: 1.5 } as JSONSchemaConst); + const test = jsonValidator.validate(1.5, { type: 'integer', const: 1.5 }); expect(test).toBe(true); @@ -56,7 +56,7 @@ describe(`JSONValidator`, () => { it(`should return false on a number not equal to a number const`, () => { - const test = jsonValidator.validate(2.5, { const: 1.5 } as JSONSchemaConst); + const test = jsonValidator.validate(2.5, { type: 'integer', const: 1.5 }); expect(test).toBe(false); @@ -64,7 +64,7 @@ describe(`JSONValidator`, () => { it(`should return true on an integer equal to an integer const`, () => { - const test = jsonValidator.validate(1, { const: 1 } as JSONSchemaConst); + const test = jsonValidator.validate(1, { type: 'number', const: 1 }); expect(test).toBe(true); @@ -72,7 +72,7 @@ describe(`JSONValidator`, () => { it(`should return false on an integer not equal to an integer const`, () => { - const test = jsonValidator.validate(2, { const: 1 } as JSONSchemaConst); + const test = jsonValidator.validate(2, { type: 'number', const: 1 }); expect(test).toBe(false); @@ -80,7 +80,7 @@ describe(`JSONValidator`, () => { it(`should return true on a boolean equal to a boolean const`, () => { - const test = jsonValidator.validate(true, { const: true } as JSONSchemaConst); + const test = jsonValidator.validate(true, { type: 'boolean', const: true }); expect(test).toBe(true); @@ -88,23 +88,7 @@ describe(`JSONValidator`, () => { it(`should return false on a boolean not equal to a boolean const`, () => { - const test = jsonValidator.validate(false, { const: true } as JSONSchemaConst); - - expect(test).toBe(false); - - }); - - it(`should return true on null equal to a null const`, () => { - - const test = jsonValidator.validate(null, { const: null } as JSONSchemaConst); - - expect(test).toBe(true); - - }); - - it(`should return false on a value not equal to a null const`, () => { - - const test = jsonValidator.validate('test', { const: null } as JSONSchemaConst); + const test = jsonValidator.validate(false, { type: 'boolean', const: true }); expect(test).toBe(false); @@ -112,7 +96,7 @@ describe(`JSONValidator`, () => { it(`should return false on an empty string with a false const`, () => { - const test = jsonValidator.validate('', { const: false } as JSONSchemaConst); + const test = jsonValidator.validate('', { type: 'boolean', const: false }); expect(test).toBe(false); @@ -120,23 +104,7 @@ describe(`JSONValidator`, () => { it(`should return false on 0 with a false const`, () => { - const test = jsonValidator.validate(0, { const: false } as JSONSchemaConst); - - expect(test).toBe(false); - - }); - - it(`should return false on an empty string with a null const`, () => { - - const test = jsonValidator.validate('', { const: null } as JSONSchemaConst); - - expect(test).toBe(false); - - }); - - it(`should return false on 0 with a null const`, () => { - - const test = jsonValidator.validate(0, { const: null } as JSONSchemaConst); + const test = jsonValidator.validate(0, { type: 'boolean', const: false }); expect(test).toBe(false); @@ -148,7 +116,7 @@ describe(`JSONValidator`, () => { it(`should return true on a value included in an enum`, () => { - const test = jsonValidator.validate('test', { enum: ['test', 'hello'] } as JSONSchemaEnum); + const test = jsonValidator.validate('test', { type: 'string', enum: ['test', 'hello'] }); expect(test).toBe(true); @@ -156,7 +124,7 @@ describe(`JSONValidator`, () => { it(`should return false on a value not included in an enum`, () => { - const test = jsonValidator.validate('test2', { enum: ['test', 'hello'] } as JSONSchemaEnum); + const test = jsonValidator.validate('test2', { type: 'boolean', enum: ['test', 'hello'] }); expect(test).toBe(false); @@ -164,7 +132,7 @@ describe(`JSONValidator`, () => { it(`should return true on an empty string included in an enum`, () => { - const test = jsonValidator.validate('', { enum: ['', 'hello'] } as JSONSchemaEnum); + const test = jsonValidator.validate('', { type: 'string', enum: ['', 'hello'] }); expect(test).toBe(true); @@ -172,7 +140,7 @@ describe(`JSONValidator`, () => { it(`should return true on 0 included in an enum`, () => { - const test = jsonValidator.validate(0, { enum: [0, 1] } as JSONSchemaEnum); + const test = jsonValidator.validate(0, { type: 'number', enum: [0, 1] }); expect(test).toBe(true); @@ -184,7 +152,7 @@ describe(`JSONValidator`, () => { it(`should return true on a true value with a boolean type`, () => { - const test = jsonValidator.validate(true, { type: 'boolean' } as JSONSchemaBoolean); + const test = jsonValidator.validate(true, { type: 'boolean' }); expect(test).toBe(true); @@ -192,15 +160,7 @@ describe(`JSONValidator`, () => { it(`should return true on a false value with a boolean type`, () => { - const test = jsonValidator.validate(false, { type: 'boolean' } as JSONSchemaBoolean); - - expect(test).toBe(true); - - }); - - it(`should return true on a null value with a null type`, () => { - - const test = jsonValidator.validate(null, { type: 'null' } as JSONSchemaNull); + const test = jsonValidator.validate(false, { type: 'boolean' }); expect(test).toBe(true); @@ -208,7 +168,7 @@ describe(`JSONValidator`, () => { it(`should return false on a primitive value with a mismatched type`, () => { - const test = jsonValidator.validate('test', { type: 'number' } as JSONSchemaNumeric); + const test = jsonValidator.validate('test', { type: 'number' }); expect(test).toBe(false); @@ -220,7 +180,7 @@ describe(`JSONValidator`, () => { it(`should return true on a string value with string type`, () => { - const test = jsonValidator.validate('test', { type: 'string' } as JSONSchemaString); + const test = jsonValidator.validate('test', { type: 'string' }); expect(test).toBe(true); @@ -229,7 +189,7 @@ describe(`JSONValidator`, () => { it(`should throw if maxLength is not an integer`, () => { expect(() => { - jsonValidator.validate('test', { type: 'string', maxLength: 10.5 } as JSONSchemaString); + jsonValidator.validate('test', { type: 'string', maxLength: 10.5 }); }).toThrowError(); }); @@ -237,14 +197,14 @@ describe(`JSONValidator`, () => { it(`should throw if maxLength is negative`, () => { expect(() => { - jsonValidator.validate('test', { type: 'string', maxLength: -1 } as JSONSchemaString); + jsonValidator.validate('test', { type: 'string', maxLength: -1 }); }).toThrowError(); }); it(`should return true with a string respecting maxLength`, () => { - const test = jsonValidator.validate('test', { type: 'string', maxLength: 10 } as JSONSchemaString); + const test = jsonValidator.validate('test', { type: 'string', maxLength: 10 }); expect(test).toBe(true); @@ -252,7 +212,7 @@ describe(`JSONValidator`, () => { it(`should return false with a string not respecting maxLength`, () => { - const test = jsonValidator.validate('test', { type: 'string', maxLength: 2 } as JSONSchemaString); + const test = jsonValidator.validate('test', { type: 'string', maxLength: 2 }); expect(test).toBe(false); @@ -261,7 +221,7 @@ describe(`JSONValidator`, () => { it(`should throw if minLength is not an integer`, () => { expect(() => { - jsonValidator.validate('test', { type: 'string', minLength: 10.5 } as JSONSchemaString); + jsonValidator.validate('test', { type: 'string', minLength: 10.5 }); }).toThrowError(); }); @@ -269,14 +229,14 @@ describe(`JSONValidator`, () => { it(`should throw if minLength is negative`, () => { expect(() => { - jsonValidator.validate('test', { type: 'string', minLength: -1 } as JSONSchemaString); + jsonValidator.validate('test', { type: 'string', minLength: -1 }); }).toThrowError(); }); it(`should return true with a string respecting minLength`, () => { - const test = jsonValidator.validate('test', { type: 'string', minLength: 2 } as JSONSchemaString); + const test = jsonValidator.validate('test', { type: 'string', minLength: 2 }); expect(test).toBe(true); @@ -284,7 +244,7 @@ describe(`JSONValidator`, () => { it(`should return false with a string not respecting minLength`, () => { - const test = jsonValidator.validate('t', { type: 'string', minLength: 2 } as JSONSchemaString); + const test = jsonValidator.validate('t', { type: 'string', minLength: 2 }); expect(test).toBe(false); @@ -293,14 +253,14 @@ describe(`JSONValidator`, () => { it(`should throw if pattern is not valid`, () => { expect(() => { - jsonValidator.validate('test', { type: 'string', pattern: '+++' } as JSONSchemaString); + jsonValidator.validate('test', { type: 'string', pattern: '+++' }); }).toThrowError(); }); it(`should return true with a string respecting a pattern`, () => { - const test = jsonValidator.validate('test', { type: 'string', pattern: '^test$' } as JSONSchemaString); + const test = jsonValidator.validate('test', { type: 'string', pattern: '^test$' }); expect(test).toBe(true); @@ -308,7 +268,7 @@ describe(`JSONValidator`, () => { it(`should return false with a string not respecting a pattern`, () => { - const test = jsonValidator.validate('t', { type: 'string', pattern: '^test$' } as JSONSchemaString); + const test = jsonValidator.validate('t', { type: 'string', pattern: '^test$' }); expect(test).toBe(false); @@ -320,7 +280,7 @@ describe(`JSONValidator`, () => { it(`should return true on an integer value with a number type`, () => { - const test = jsonValidator.validate(10, { type: 'number' } as JSONSchemaNumeric); + const test = jsonValidator.validate(10, { type: 'number' }); expect(test).toBe(true); @@ -328,7 +288,7 @@ describe(`JSONValidator`, () => { it(`should return true on a decimal value with a number type`, () => { - const test = jsonValidator.validate(10.1, { type: 'number' } as JSONSchemaNumeric); + const test = jsonValidator.validate(10.1, { type: 'number' }); expect(test).toBe(true); @@ -336,7 +296,7 @@ describe(`JSONValidator`, () => { it(`should return true on an integer value with an integer type`, () => { - const test = jsonValidator.validate(10, { type: 'integer' } as JSONSchemaNumeric); + const test = jsonValidator.validate(10, { type: 'integer' }); expect(test).toBe(true); @@ -344,7 +304,7 @@ describe(`JSONValidator`, () => { it(`should return false on a decimal value with an integer type`, () => { - const test = jsonValidator.validate(10.1, { type: 'integer' } as JSONSchemaNumeric); + const test = jsonValidator.validate(10.1, { type: 'integer' }); expect(test).toBe(false); @@ -353,14 +313,14 @@ describe(`JSONValidator`, () => { it(`should throw if multipleOf is not strictly greater than 0`, () => { expect(() => { - jsonValidator.validate(10, { type: 'number', multipleOf: 0 } as JSONSchemaNumeric); + jsonValidator.validate(10, { type: 'number', multipleOf: 0 }); }).toThrowError(); }); it(`should return true with a number is a multiple of x`, () => { - const test = jsonValidator.validate(5.2, { type: 'number', multipleOf: 2.6 } as JSONSchemaNumeric); + const test = jsonValidator.validate(5.2, { type: 'number', multipleOf: 2.6 }); expect(test).toBe(true); @@ -368,7 +328,7 @@ describe(`JSONValidator`, () => { it(`should return true with an integer is a multiple of x`, () => { - const test = jsonValidator.validate(10, { type: 'integer', multipleOf: 2 } as JSONSchemaNumeric); + const test = jsonValidator.validate(10, { type: 'integer', multipleOf: 2 }); expect(test).toBe(true); @@ -376,7 +336,7 @@ describe(`JSONValidator`, () => { it(`should return false with a number not a multiple of x`, () => { - const test = jsonValidator.validate(5.3, { type: 'number', multipleOf: 2.6 } as JSONSchemaNumeric); + const test = jsonValidator.validate(5.3, { type: 'number', multipleOf: 2.6 }); expect(test).toBe(false); @@ -384,7 +344,7 @@ describe(`JSONValidator`, () => { it(`should return false with an integer not a multiple of x`, () => { - const test = jsonValidator.validate(11, { type: 'integer', multipleOf: 2 } as JSONSchemaNumeric); + const test = jsonValidator.validate(11, { type: 'integer', multipleOf: 2 }); expect(test).toBe(false); @@ -392,7 +352,7 @@ describe(`JSONValidator`, () => { it(`should return true with a number less than a maximum`, () => { - const test = jsonValidator.validate(5.2, { type: 'number', maximum: 5.7 } as JSONSchemaNumeric); + const test = jsonValidator.validate(5.2, { type: 'number', maximum: 5.7 }); expect(test).toBe(true); @@ -400,7 +360,7 @@ describe(`JSONValidator`, () => { it(`should return true with a number equal to maximum`, () => { - const test = jsonValidator.validate(5.2, { type: 'number', maximum: 5.2 } as JSONSchemaNumeric); + const test = jsonValidator.validate(5.2, { type: 'number', maximum: 5.2 }); expect(test).toBe(true); @@ -408,7 +368,7 @@ describe(`JSONValidator`, () => { it(`should return false with a number greater than a maximum`, () => { - const test = jsonValidator.validate(5.3, { type: 'number', maximum: 5.2 } as JSONSchemaNumeric); + const test = jsonValidator.validate(5.3, { type: 'number', maximum: 5.2 }); expect(test).toBe(false); @@ -416,7 +376,7 @@ describe(`JSONValidator`, () => { it(`should return true with an integer less than a maximum`, () => { - const test = jsonValidator.validate(5, { type: 'integer', maximum: 6 } as JSONSchemaNumeric); + const test = jsonValidator.validate(5, { type: 'integer', maximum: 6 }); expect(test).toBe(true); @@ -424,7 +384,7 @@ describe(`JSONValidator`, () => { it(`should return true with an integer equal to maximum`, () => { - const test = jsonValidator.validate(5, { type: 'integer', maximum: 5 } as JSONSchemaNumeric); + const test = jsonValidator.validate(5, { type: 'integer', maximum: 5 }); expect(test).toBe(true); @@ -432,7 +392,7 @@ describe(`JSONValidator`, () => { it(`should return false with an integer greater than a maximum`, () => { - const test = jsonValidator.validate(6, { type: 'integer', maximum: 5 } as JSONSchemaNumeric); + const test = jsonValidator.validate(6, { type: 'integer', maximum: 5 }); expect(test).toBe(false); @@ -440,7 +400,7 @@ describe(`JSONValidator`, () => { it(`should return true with a number less than an exclusiveMaximum`, () => { - const test = jsonValidator.validate(5.2, { type: 'number', exclusiveMaximum: 5.7 } as JSONSchemaNumeric); + const test = jsonValidator.validate(5.2, { type: 'number', exclusiveMaximum: 5.7 }); expect(test).toBe(true); @@ -448,7 +408,7 @@ describe(`JSONValidator`, () => { it(`should return false with a number equal to exclusiveMaximum`, () => { - const test = jsonValidator.validate(5.2, { type: 'number', exclusiveMaximum: 5.2 } as JSONSchemaNumeric); + const test = jsonValidator.validate(5.2, { type: 'number', exclusiveMaximum: 5.2 }); expect(test).toBe(false); @@ -456,7 +416,7 @@ describe(`JSONValidator`, () => { it(`should return false with a number greater than an exclusiveMaximum`, () => { - const test = jsonValidator.validate(5.3, { type: 'number', exclusiveMaximum: 5.2 } as JSONSchemaNumeric); + const test = jsonValidator.validate(5.3, { type: 'number', exclusiveMaximum: 5.2 }); expect(test).toBe(false); @@ -464,7 +424,7 @@ describe(`JSONValidator`, () => { it(`should return true with an integer less than an exclusiveMaximum`, () => { - const test = jsonValidator.validate(5, { type: 'integer', exclusiveMaximum: 6 } as JSONSchemaNumeric); + const test = jsonValidator.validate(5, { type: 'integer', exclusiveMaximum: 6 }); expect(test).toBe(true); @@ -472,7 +432,7 @@ describe(`JSONValidator`, () => { it(`should return false with an integer equal to exclusiveMaximum`, () => { - const test = jsonValidator.validate(5, { type: 'integer', exclusiveMaximum: 5 } as JSONSchemaNumeric); + const test = jsonValidator.validate(5, { type: 'integer', exclusiveMaximum: 5 }); expect(test).toBe(false); @@ -480,7 +440,7 @@ describe(`JSONValidator`, () => { it(`should return false with an integer greater than an exclusiveMaximum`, () => { - const test = jsonValidator.validate(6, { type: 'integer', exclusiveMaximum: 5 } as JSONSchemaNumeric); + const test = jsonValidator.validate(6, { type: 'integer', exclusiveMaximum: 5 }); expect(test).toBe(false); @@ -488,7 +448,7 @@ describe(`JSONValidator`, () => { it(`should return true with a number greater than a minimum`, () => { - const test = jsonValidator.validate(5.8, { type: 'number', minimum: 5.7 } as JSONSchemaNumeric); + const test = jsonValidator.validate(5.8, { type: 'number', minimum: 5.7 }); expect(test).toBe(true); @@ -496,7 +456,7 @@ describe(`JSONValidator`, () => { it(`should return true with a number equal to minimum`, () => { - const test = jsonValidator.validate(5.2, { type: 'number', minimum: 5.2 } as JSONSchemaNumeric); + const test = jsonValidator.validate(5.2, { type: 'number', minimum: 5.2 }); expect(test).toBe(true); @@ -504,7 +464,7 @@ describe(`JSONValidator`, () => { it(`should return false with a number less than an minimum`, () => { - const test = jsonValidator.validate(5.1, { type: 'number', minimum: 5.2 } as JSONSchemaNumeric); + const test = jsonValidator.validate(5.1, { type: 'number', minimum: 5.2 }); expect(test).toBe(false); @@ -512,7 +472,7 @@ describe(`JSONValidator`, () => { it(`should return true with an integer greater than a minimum`, () => { - const test = jsonValidator.validate(7, { type: 'integer', minimum: 6 } as JSONSchemaNumeric); + const test = jsonValidator.validate(7, { type: 'integer', minimum: 6 }); expect(test).toBe(true); @@ -520,7 +480,7 @@ describe(`JSONValidator`, () => { it(`should return true with an integer equal to minimum`, () => { - const test = jsonValidator.validate(5, { type: 'integer', minimum: 5 } as JSONSchemaNumeric); + const test = jsonValidator.validate(5, { type: 'integer', minimum: 5 }); expect(test).toBe(true); @@ -528,7 +488,7 @@ describe(`JSONValidator`, () => { it(`should return false with an integer less than an minimum`, () => { - const test = jsonValidator.validate(4, { type: 'integer', minimum: 5 } as JSONSchemaNumeric); + const test = jsonValidator.validate(4, { type: 'integer', minimum: 5 }); expect(test).toBe(false); @@ -536,7 +496,7 @@ describe(`JSONValidator`, () => { it(`should return true with a number greater than an exclusiveMinimum`, () => { - const test = jsonValidator.validate(5.8, { type: 'number', exclusiveMinimum: 5.7 } as JSONSchemaNumeric); + const test = jsonValidator.validate(5.8, { type: 'number', exclusiveMinimum: 5.7 }); expect(test).toBe(true); @@ -544,7 +504,7 @@ describe(`JSONValidator`, () => { it(`should return false with a number equal to exclusiveMinimum`, () => { - const test = jsonValidator.validate(5.2, { type: 'number', exclusiveMinimum: 5.2 } as JSONSchemaNumeric); + const test = jsonValidator.validate(5.2, { type: 'number', exclusiveMinimum: 5.2 }); expect(test).toBe(false); @@ -552,7 +512,7 @@ describe(`JSONValidator`, () => { it(`should return false with a number less than an exclusiveMinimum`, () => { - const test = jsonValidator.validate(5.1, { type: 'number', exclusiveMinimum: 5.2 } as JSONSchemaNumeric); + const test = jsonValidator.validate(5.1, { type: 'number', exclusiveMinimum: 5.2 }); expect(test).toBe(false); @@ -560,7 +520,7 @@ describe(`JSONValidator`, () => { it(`should return true with an integer greater than an exclusiveMinimum`, () => { - const test = jsonValidator.validate(7, { type: 'integer', exclusiveMinimum: 6 } as JSONSchemaNumeric); + const test = jsonValidator.validate(7, { type: 'integer', exclusiveMinimum: 6 }); expect(test).toBe(true); @@ -568,7 +528,7 @@ describe(`JSONValidator`, () => { it(`should return false with an integer equal to exclusiveMinimum`, () => { - const test = jsonValidator.validate(5, { type: 'integer', exclusiveMinimum: 5 } as JSONSchemaNumeric); + const test = jsonValidator.validate(5, { type: 'integer', exclusiveMinimum: 5 }); expect(test).toBe(false); @@ -576,7 +536,7 @@ describe(`JSONValidator`, () => { it(`should return false with an integer less than an exclusiveMinimum`, () => { - const test = jsonValidator.validate(4, { type: 'integer', exclusiveMinimum: 5 } as JSONSchemaNumeric); + const test = jsonValidator.validate(4, { type: 'integer', exclusiveMinimum: 5 }); expect(test).toBe(false); @@ -588,7 +548,7 @@ describe(`JSONValidator`, () => { it(`should return false if data is not an object`, () => { - const test = jsonValidator.validate('', { required: [], properties: {} } as JSONSchemaObject); + const test = jsonValidator.validate('', { type: 'object', required: [], properties: {} }); expect(test).toBe(false); @@ -597,7 +557,11 @@ describe(`JSONValidator`, () => { it(`should throw if required properties are not described in 'properties'`, () => { expect(() => { - jsonValidator.validate({ test: 'test' }, { properties: { other: { type: 'string' } }, required: ['test'] } as JSONSchemaObject); + jsonValidator.validate({ test: 'test' }, { + type: 'object', + properties: { other: { type: 'string' } }, + required: ['test'] + }); }).toThrowError(); }); @@ -605,12 +569,13 @@ describe(`JSONValidator`, () => { it(`should return true on an object with the required properties`, () => { const test = jsonValidator.validate({ test1: '', test2: '' }, { + type: 'object', required: ['test1', 'test2'], properties: { test1: { type: 'string' }, test2: { type: 'string' } } - } as JSONSchemaObject); + }); expect(test).toBe(true); @@ -619,13 +584,14 @@ describe(`JSONValidator`, () => { it(`should return true on an object with the required properties and others`, () => { const test = jsonValidator.validate({ test1: '', test2: '', test3: '' }, { + type: 'object', required: ['test1', 'test2'], properties: { test1: { type: 'string' }, test2: { type: 'string' }, test3: { type: 'string' } } - } as JSONSchemaObject); + }); expect(test).toBe(true); @@ -634,13 +600,14 @@ describe(`JSONValidator`, () => { it(`should return true on an object with the required properties and missing optional property`, () => { const test = jsonValidator.validate({ test1: '', test2: '' }, { + type: 'object', required: ['test1', 'test2'], properties: { test1: { type: 'string' }, test2: { type: 'string' }, test3: { type: 'string' } } - } as JSONSchemaObject); + }); expect(test).toBe(true); @@ -648,12 +615,14 @@ describe(`JSONValidator`, () => { it(`should return false on an object with missing required properties`, () => { - const test = jsonValidator.validate({ test1: '' }, { required: ['test1', 'test2'], + const test = jsonValidator.validate({ test1: '' }, { + type: 'object', + required: ['test1', 'test2'], properties: { test1: { type: 'string' }, test2: { type: 'string' } } - } as JSONSchemaObject); + }); expect(test).toBe(false); @@ -665,7 +634,7 @@ describe(`JSONValidator`, () => { it(`should return false if data is not an object`, () => { - const test = jsonValidator.validate('', { properties: {} } as JSONSchemaObject); + const test = jsonValidator.validate('', { type: 'object', properties: {} }); expect(test).toBe(false); @@ -674,6 +643,7 @@ describe(`JSONValidator`, () => { it(`should return true on an object with valid properties`, () => { const test = jsonValidator.validate({ test1: '', test2: 10 }, { + type: 'object', properties: { test1: { type: 'string' @@ -682,7 +652,7 @@ describe(`JSONValidator`, () => { type: 'number' } } - } as JSONSchemaObject); + }); expect(test).toBe(true); @@ -691,6 +661,7 @@ describe(`JSONValidator`, () => { it(`should return false on an object with invalid properties`, () => { const test = jsonValidator.validate({ test1: 10, test2: 10 }, { + type: 'object', properties: { test1: { type: 'string' @@ -699,7 +670,7 @@ describe(`JSONValidator`, () => { type: 'number' } } - } as JSONSchemaObject); + }); expect(test).toBe(false); @@ -708,6 +679,7 @@ describe(`JSONValidator`, () => { it(`should return false on an object with too much properties`, () => { const test = jsonValidator.validate({ test1: 10, test2: 10, test3: '' }, { + type: 'object', properties: { test1: { type: 'string' @@ -716,7 +688,7 @@ describe(`JSONValidator`, () => { type: 'number' } } - } as JSONSchemaObject); + }); expect(test).toBe(false); @@ -725,11 +697,13 @@ describe(`JSONValidator`, () => { it(`should return true on nested objects with valid properties`, () => { const test = jsonValidator.validate({ test1: '', test2: { test3: 10 } }, { + type: 'object', properties: { test1: { type: 'string' }, test2: { + type: 'object', properties: { test3: { type: 'number' @@ -738,7 +712,7 @@ describe(`JSONValidator`, () => { required: ['test3'] } } - } as JSONSchemaObject); + }); expect(test).toBe(true); @@ -747,11 +721,13 @@ describe(`JSONValidator`, () => { it(`should return false on nested objects with invalid properties`, () => { const test = jsonValidator.validate({ test1: '', test2: { test3: '' } }, { + type: 'object', properties: { test1: { type: 'string' }, test2: { + type: 'object', properties: { test3: { type: 'number' @@ -760,7 +736,7 @@ describe(`JSONValidator`, () => { required: ['test3'] } } - } as JSONSchemaObject); + }); expect(test).toBe(false); @@ -768,7 +744,10 @@ describe(`JSONValidator`, () => { it(`should return true with valid arrays nested in objects`, () => { - const test = jsonValidator.validate({ test: ['', ''] }, { properties: { test: { items: { type: 'string' } } } } as JSONSchemaObject); + const test = jsonValidator.validate({ test: ['', ''] }, { + type: 'object', + properties: { test: { type: 'array', items: { type: 'string' } } } + }); expect(test).toBe(true); @@ -780,7 +759,7 @@ describe(`JSONValidator`, () => { it(`should return false if data is not an array`, () => { - const test = jsonValidator.validate('', { items: { type: 'string' } } as JSONSchemaArray); + const test = jsonValidator.validate('', { type: 'array', items: { type: 'string' } }); expect(test).toBe(false); @@ -788,7 +767,7 @@ describe(`JSONValidator`, () => { it(`should return true if array items are valid`, () => { - const test = jsonValidator.validate(['', ''], { items: { type: 'string' } } as JSONSchemaArray); + const test = jsonValidator.validate(['', ''], { type: 'array', items: { type: 'string' } }); expect(test).toBe(true); @@ -796,7 +775,7 @@ describe(`JSONValidator`, () => { it(`should return false if array items are invalid`, () => { - const test = jsonValidator.validate(['', ''], { items: { type: 'number' } } as JSONSchemaArray); + const test = jsonValidator.validate(['', ''], { type: 'array', items: { type: 'number' } }); expect(test).toBe(false); @@ -804,7 +783,10 @@ describe(`JSONValidator`, () => { it(`should return true with valid nested arrays`, () => { - const test = jsonValidator.validate([[''], ['']], { items: { items: { type: 'string' } } } as JSONSchemaArray); + const test = jsonValidator.validate([[''], ['']], { + type: 'array', + items: { type: 'array', items: { type: 'string' } } + }); expect(test).toBe(true); @@ -812,7 +794,10 @@ describe(`JSONValidator`, () => { it(`should return false with invalid nested arrays`, () => { - const test = jsonValidator.validate([[''], ['']], { items: { items: { type: 'number' } } } as JSONSchemaArray); + const test = jsonValidator.validate([[''], ['']], { + type: 'array', + items: { type: 'array', items: { type: 'number' } } + }); expect(test).toBe(false); @@ -821,8 +806,9 @@ describe(`JSONValidator`, () => { it(`should return true with valid objects nested in arrays`, () => { const test = jsonValidator.validate([{ test: 'test' }, [{ test: 'test' }]], { - items: { properties: { test: { type: 'string' } } } - } as JSONSchemaArray); + type: 'array', + items: {type: 'object', properties: { test: { type: 'string' } } } + }); expect(test).toBe(true); @@ -831,7 +817,7 @@ describe(`JSONValidator`, () => { it(`should throw if maxItems is not an integer`, () => { expect(() => { - jsonValidator.validate([], { items: { type: 'string' }, maxItems: 10.5 } as JSONSchemaArray); + jsonValidator.validate([], {type: 'array', items: { type: 'string' }, maxItems: 10.5 }); }).toThrowError(); }); @@ -839,14 +825,14 @@ describe(`JSONValidator`, () => { it(`should throw if maxItems is not positive`, () => { expect(() => { - jsonValidator.validate([], { items: { type: 'string' }, maxItems: -1 } as JSONSchemaArray); + jsonValidator.validate([], { type: 'array', items: { type: 'string' }, maxItems: -1 }); }).toThrowError(); }); it(`should return true with an array with less items than maxItems`, () => { - const test = jsonValidator.validate([1, 2], { items: { type: 'number' }, maxItems: 3 } as JSONSchemaArray); + const test = jsonValidator.validate([1, 2], { type: 'array', items: { type: 'number' }, maxItems: 3 }); expect(test).toBe(true); @@ -854,7 +840,7 @@ describe(`JSONValidator`, () => { it(`should return true with an array length equal to maxItems`, () => { - const test = jsonValidator.validate([1, 2], { items: { type: 'number' }, maxItems: 2 } as JSONSchemaArray); + const test = jsonValidator.validate([1, 2], { type: 'array', items: { type: 'number' }, maxItems: 2 }); expect(test).toBe(true); @@ -862,7 +848,7 @@ describe(`JSONValidator`, () => { it(`should return false with an array with more items than maxItems`, () => { - const test = jsonValidator.validate([1, 2, 4, 4], { items: { type: 'number' }, maxItems: 3 } as JSONSchemaArray); + const test = jsonValidator.validate([1, 2, 4, 4], { type: 'array', items: { type: 'number' }, maxItems: 3 }); expect(test).toBe(false); @@ -871,7 +857,7 @@ describe(`JSONValidator`, () => { it(`should throw if minItems is not an integer`, () => { expect(() => { - jsonValidator.validate([], { items: { type: 'string' }, minItems: 10.5 } as JSONSchemaArray); + jsonValidator.validate([], { type: 'array', items: { type: 'string' }, minItems: 10.5 }); }).toThrowError(); }); @@ -879,14 +865,14 @@ describe(`JSONValidator`, () => { it(`should throw if minItems is not positive`, () => { expect(() => { - jsonValidator.validate([], { items: { type: 'string' }, minItems: -1 } as JSONSchemaArray); + jsonValidator.validate([], { type: 'array', items: { type: 'string' }, minItems: -1 }); }).toThrowError(); }); it(`should return true with an array with more items than minItems`, () => { - const test = jsonValidator.validate([1, 2, 3, 4], { items: { type: 'number' }, minItems: 3 } as JSONSchemaArray); + const test = jsonValidator.validate([1, 2, 3, 4], { type: 'array', items: { type: 'number' }, minItems: 3 }); expect(test).toBe(true); @@ -894,7 +880,7 @@ describe(`JSONValidator`, () => { it(`should return true with an array length equal to minItems`, () => { - const test = jsonValidator.validate([1, 2], { items: { type: 'number' }, minItems: 2 } as JSONSchemaArray); + const test = jsonValidator.validate([1, 2], { type: 'array', items: { type: 'number' }, minItems: 2 }); expect(test).toBe(true); @@ -902,7 +888,7 @@ describe(`JSONValidator`, () => { it(`should return false with an array with less items than maxItems`, () => { - const test = jsonValidator.validate([1, 2], { items: { type: 'number' }, minItems: 3 } as JSONSchemaArray); + const test = jsonValidator.validate([1, 2], { type: 'array', items: { type: 'number' }, minItems: 3 }); expect(test).toBe(false); @@ -910,7 +896,7 @@ describe(`JSONValidator`, () => { it(`should return true with an array with uniqueItems`, () => { - const test = jsonValidator.validate([1, 2], { items: { type: 'number' }, uniqueItems: true } as JSONSchemaArray); + const test = jsonValidator.validate([1, 2], { type: 'array', items: { type: 'number' }, uniqueItems: true }); expect(test).toBe(true); @@ -918,35 +904,7 @@ describe(`JSONValidator`, () => { it(`should return false with an array with non uniqueItems`, () => { - const test = jsonValidator.validate([1, 1], { items: { type: 'number' }, uniqueItems: true } as JSONSchemaArray); - - expect(test).toBe(false); - - }); - - }); - - describe(`validateItemsList`, () => { - - it(`should return false if array length is not equel to schemas length`, () => { - - const test = jsonValidator.validate(['', 10], { items: [{ type: 'string' }] } as JSONSchemaArray); - - expect(test).toBe(false); - - }); - - it(`should return true if array values match schemas`, () => { - - const test = jsonValidator.validate(['', 10], { items: [{ type: 'string' }, { type: 'number' }] } as JSONSchemaArray); - - expect(test).toBe(true); - - }); - - it(`should return false if array values mismatch schemas`, () => { - - const test = jsonValidator.validate(['', 10], { items: [{ type: 'string' }, { type: 'boolean' }] } as JSONSchemaArray); + const test = jsonValidator.validate([1, 1], { type: 'array', items: { type: 'number' }, uniqueItems: true }); expect(test).toBe(false); diff --git a/projects/ngx-pwa/local-storage/src/lib/validation/json-validator.ts b/projects/ngx-pwa/local-storage/src/lib/validation/json-validator.ts index f4e6ed50..d60e2f24 100644 --- a/projects/ngx-pwa/local-storage/src/lib/validation/json-validator.ts +++ b/projects/ngx-pwa/local-storage/src/lib/validation/json-validator.ts @@ -1,5 +1,8 @@ import { Injectable } from '@angular/core'; -import { JSONSchema } from './json-schema'; +import { + JSONSchema, JSONSchemaString, JSONSchemaInteger, JSONSchemaNumber, JSONSchemaBoolean, + JSONSchemaArray, JSONSchemaObject +} from './json-schema'; /** * @todo Add other JSON Schema validation features @@ -21,60 +24,28 @@ export class JSONValidator { */ validate(data: any, schema: JSONSchema): boolean { - /** @todo When TS 2.8, explore if this is possible with conditional types */ - if (((!(schema.hasOwnProperty('const') && schema.const !== undefined) - && !(schema.hasOwnProperty('enum') && schema.enum != null) && !(schema.hasOwnProperty('type') && schema.type != null)) - || schema.type === 'array' || schema.type === 'object') - && !(schema.hasOwnProperty('properties') && schema.properties != null) && !(schema.hasOwnProperty('items') && schema.items != null)) { - - throw new Error(`Each value must have a 'type' or 'properties' or 'items' or 'const' or 'enum', to enforce strict types.`); - - } - - if (schema.hasOwnProperty('const') && schema.const !== undefined && (data !== schema.const)) { - return false; - } - - if (!this.validateEnum(data, schema)) { - return false; - } - - if (!this.validateType(data, schema)) { - return false; - } - - if (!this.validateItems(data, schema)) { - return false; - } + switch (schema.type) { - if (!this.validateProperties(data, schema)) { - return false; - } + case 'string': + return this.validateString(data, schema); + case 'number': + case 'integer': + return this.validateNumber(data, schema); + case 'boolean': + return this.validateBoolean(data, schema); + case 'array': + return this.validateArray(data, schema); + case 'object': + return this.validateObject(data, schema); - if (!this.validateRequired(data, schema)) { - return false; } - return true; - - } - - protected isObjectNotNull(value: any): boolean { - - return (value !== null) && (typeof value === 'object'); - } - protected validateProperties(data: { [k: string]: any; }, schema: JSONSchema): boolean { - - if (!schema.hasOwnProperty('properties') || (schema.properties == null)) { - return true; - } - - if (!this.isObjectNotNull(data)) { + protected validateObject(data: { [k: string]: any; }, schema: JSONSchemaObject): boolean { + if ((data === null) || (typeof data !== 'object')) { return false; - } /** @@ -82,9 +53,11 @@ export class JSONValidator { * Equivalent of additionalProperties: false */ if (Object.keys(schema.properties).length < Object.keys(data).length) { - return false; + } + if (!this.validateRequired(data, schema)) { + return false; } /* Recursively validate all properties */ @@ -106,32 +79,22 @@ export class JSONValidator { } - protected validateRequired(data: {}, schema: JSONSchema): boolean { + protected validateRequired(data: {}, schema: JSONSchemaObject): boolean { - if (!schema.hasOwnProperty('required') || (schema.required == null)) { + if (!schema.required) { return true; } - if (!this.isObjectNotNull(data)) { - - return false; - - } - for (const requiredProp of schema.required) { /* Checks if the property is present in the schema 'properties' */ - if (!schema.properties || !schema.properties.hasOwnProperty(requiredProp)) { - + if (!schema.properties.hasOwnProperty(requiredProp)) { throw new Error(`'required' properties must be described in 'properties' too.`); - } /* Checks if the property is present in the data */ if (!data.hasOwnProperty(requiredProp)) { - return false; - } } @@ -140,63 +103,37 @@ export class JSONValidator { } - protected validateEnum(data: any, schema: JSONSchema): boolean { + protected validateConst(data: any, schema: JSONSchemaBoolean | JSONSchemaInteger | JSONSchemaNumber | JSONSchemaString): boolean { - if (!schema.hasOwnProperty('enum') || (schema.enum == null)) { + if (!schema.const) { return true; } - /** @todo Move to ES2016 .includes() ? */ - return (schema.enum.indexOf(data) !== -1); + return (data === schema.const); } - protected validateType(data: any, schema: JSONSchema): boolean { + protected validateEnum(data: any, schema: JSONSchemaInteger | JSONSchemaNumber | JSONSchemaString): boolean { - if (!schema.hasOwnProperty('type') || (schema.type == null)) { + if (!schema.enum) { return true; } - switch (schema.type) { - - case 'null': - return data === null; - case 'string': - return this.validateString(data, schema); - case 'number': - case 'integer': - return this.validateNumber(data, schema); - case 'boolean': - return typeof data === 'boolean'; - case 'object': - return typeof data === 'object'; - case 'array': - return Array.isArray(data); - - } - - return true; + /** @todo Move to ES2016 .includes() ? */ + return ((schema.enum as any[]).indexOf(data) !== -1); } - protected validateItems(data: any[], schema: JSONSchema): boolean { - - if (!schema.hasOwnProperty('items') || (schema.items == null)) { - return true; - } + protected validateArray(data: any[], schema: JSONSchemaArray): boolean { if (!Array.isArray(data)) { - return false; - } - if (schema.hasOwnProperty('maxItems') && (schema.maxItems != null)) { + if (schema.hasOwnProperty('maxItems') && (schema.maxItems !== undefined)) { if (!Number.isInteger(schema.maxItems) || schema.maxItems < 0) { - throw new Error(`'maxItems' must be a non-negative integer.`); - } if (data.length > schema.maxItems) { @@ -205,12 +142,10 @@ export class JSONValidator { } - if (schema.hasOwnProperty('minItems') && (schema.minItems != null)) { + if (schema.hasOwnProperty('minItems') && (schema.minItems !== undefined)) { if (!Number.isInteger(schema.minItems) || schema.minItems < 0) { - throw new Error(`'minItems' must be a non-negative integer.`); - } if (data.length < schema.minItems) { @@ -219,7 +154,7 @@ export class JSONValidator { } - if (schema.hasOwnProperty('uniqueItems') && (schema.uniqueItems != null)) { + if (schema.hasOwnProperty('uniqueItems') && (schema.uniqueItems !== undefined)) { if (schema.uniqueItems) { @@ -233,12 +168,6 @@ export class JSONValidator { } - if (Array.isArray(schema.items)) { - - return this.validateItemsList(data, schema); - - } - for (const value of data) { if (!this.validate(value, schema.items)) { @@ -251,40 +180,24 @@ export class JSONValidator { } - protected validateItemsList(data: any, schema: JSONSchema): boolean { - - const items = schema.items as JSONSchema[]; - - if (data.length !== items.length) { + protected validateString(data: any, schema: JSONSchemaString): boolean { + if (typeof data !== 'string') { return false; - } - for (let i = 0; i < items.length; i += 1) { - - if (!this.validate(data[i], items[i])) { - return false; - } - + if (!this.validateConst(data, schema)) { + return false; } - return true; - - } - - protected validateString(data: any, schema: JSONSchema): boolean { - - if (typeof data !== 'string') { + if (!this.validateEnum(data, schema)) { return false; } - if (schema.hasOwnProperty('maxLength') && (schema.maxLength != null)) { + if (schema.hasOwnProperty('maxLength') && (schema.maxLength !== undefined)) { if (!Number.isInteger(schema.maxLength) || schema.maxLength < 0) { - throw new Error(`'maxLength' must be a non-negative integer.`); - } if (data.length > schema.maxLength) { @@ -293,12 +206,10 @@ export class JSONValidator { } - if (schema.hasOwnProperty('minLength') && (schema.minLength != null)) { + if (schema.hasOwnProperty('minLength') && (schema.minLength !== undefined)) { if (!Number.isInteger(schema.minLength) || schema.minLength < 0) { - throw new Error(`'minLength' must be a non-negative integer.`); - } if (data.length < schema.minLength) { @@ -307,7 +218,7 @@ export class JSONValidator { } - if (schema.hasOwnProperty('pattern') && (schema.pattern != null)) { + if (schema.pattern) { const regularExpression = new RegExp(schema.pattern); @@ -321,7 +232,21 @@ export class JSONValidator { } - protected validateNumber(data: any, schema: JSONSchema): boolean { + protected validateBoolean(data: any, schema: JSONSchemaBoolean): boolean { + + if (typeof data !== 'boolean') { + return false; + } + + if (!this.validateConst(data, schema)) { + return false; + } + + return true; + + } + + protected validateNumber(data: any, schema: JSONSchemaNumber | JSONSchemaInteger): boolean { if (typeof data !== 'number') { return false; @@ -331,12 +256,18 @@ export class JSONValidator { return false; } - if (schema.hasOwnProperty('multipleOf') && (schema.multipleOf != null)) { + if (!this.validateConst(data, schema)) { + return false; + } - if (schema.multipleOf <= 0) { + if (!this.validateEnum(data, schema)) { + return false; + } - throw new Error(`'multipleOf' must be a number strictly greater than 0.`); + if (schema.hasOwnProperty('multipleOf') && (schema.multipleOf !== undefined)) { + if (schema.multipleOf <= 0) { + throw new Error(`'multipleOf' must be a number strictly greater than 0.`); } if (!Number.isInteger(data / schema.multipleOf)) { @@ -345,36 +276,22 @@ export class JSONValidator { } - if (schema.hasOwnProperty('maximum') && (schema.maximum != null)) { - - if (data > schema.maximum) { + if (schema.hasOwnProperty('maximum') && (schema.maximum !== undefined) && (data > schema.maximum)) { return false; - } - } - if (schema.hasOwnProperty('exclusiveMaximum') && (schema.exclusiveMaximum != null)) { - - if (data >= schema.exclusiveMaximum) { - return false; - } + if (schema.hasOwnProperty('exclusiveMaximum') && (schema.exclusiveMaximum !== undefined) && (data >= schema.exclusiveMaximum)) { + return false; } - if (schema.hasOwnProperty('minimum') && (schema.minimum != null)) { - - if (data < schema.minimum) { - return false; - } + if (schema.hasOwnProperty('minimum') && (schema.minimum !== undefined) && (data < schema.minimum)) { + return false; } - if (schema.hasOwnProperty('exclusiveMinimum') && (schema.exclusiveMinimum != null)) { - - if (data <= schema.exclusiveMinimum) { + if (schema.hasOwnProperty('exclusiveMinimum') && (schema.exclusiveMinimum !== undefined) && (data <= schema.exclusiveMinimum)) { return false; - } - } return true; diff --git a/projects/ngx-pwa/local-storage/src/public_api.ts b/projects/ngx-pwa/local-storage/src/public_api.ts index a8ee1be8..a00ff6c3 100644 --- a/projects/ngx-pwa/local-storage/src/public_api.ts +++ b/projects/ngx-pwa/local-storage/src/public_api.ts @@ -3,8 +3,8 @@ */ export { - JSONSchema, JSONSchemaConst, JSONSchemaEnum, JSONSchemaBoolean, - JSONSchemaNumeric, JSONSchemaString, JSONSchemaArray, JSONSchemaObject + JSONSchema, JSONSchemaBoolean, JSONSchemaInteger, JSONSchemaNumber, + JSONSchemaNumeric, JSONSchemaString, JSONSchemaArray, JSONSchemaArrayOf, JSONSchemaObject } from './lib/validation/json-schema'; export { LocalDatabase } from './lib/databases/local-database'; export { IndexedDBDatabase } from './lib/databases/indexeddb-database'; From a88cab475c37ca490bcca5ca45576a196bc62127 Mon Sep 17 00:00:00 2001 From: Cyrille Tuzi Date: Sun, 27 Jan 2019 14:09:00 +0100 Subject: [PATCH 02/10] revert: remove shortcuts --- .../src/lib/get-item-overloads.spec.ts | 24 -------- .../local-storage/src/lib/lib.service.ts | 59 +++---------------- 2 files changed, 7 insertions(+), 76 deletions(-) diff --git a/projects/ngx-pwa/local-storage/src/lib/get-item-overloads.spec.ts b/projects/ngx-pwa/local-storage/src/lib/get-item-overloads.spec.ts index c7228810..f7fc3927 100644 --- a/projects/ngx-pwa/local-storage/src/lib/get-item-overloads.spec.ts +++ b/projects/ngx-pwa/local-storage/src/lib/get-item-overloads.spec.ts @@ -67,30 +67,6 @@ describe('getItem() overload signature', () => { }); - it('should compile with shortcut type and without type param', (done: DoneFn) => { - - localStorageService.getItem('test', { type: 'string' }).subscribe((_) => { - - expect().nothing(); - - done(); - - }); - - }); - - it('should compile with shortcut type and with type param', (done: DoneFn) => { - - localStorageService.getItem('test', { type: 'string' }).subscribe((_) => { - - expect().nothing(); - - done(); - - }); - - }); - it('should compile with literal basic schema and extra options', (done: DoneFn) => { localStorageService.getItem('test', { schema: { type: 'string', maxLength: 10 } }).subscribe((_) => { diff --git a/projects/ngx-pwa/local-storage/src/lib/lib.service.ts b/projects/ngx-pwa/local-storage/src/lib/lib.service.ts index 6513f2fe..1a95d5b6 100755 --- a/projects/ngx-pwa/local-storage/src/lib/lib.service.ts +++ b/projects/ngx-pwa/local-storage/src/lib/lib.service.ts @@ -11,7 +11,6 @@ import { JSONValidator } from './validation/json-validator'; export interface LSGetItemOptions { schema?: JSONSchema | null; - type?: 'boolean' | 'integer' | 'number' | 'string' | 'boolean[]' | 'integer[]' | 'number[]' | 'string[]' | null; } @Injectable({ @@ -30,7 +29,6 @@ export class LocalStorage { protected readonly getItemOptionsDefault: LSGetItemOptions = { schema: null, - type: null, }; constructor(protected database: LocalDatabase, protected jsonValidator: JSONValidator) {} @@ -43,17 +41,17 @@ export class LocalStorage { * @returns The item's value if the key exists, null otherwise, wrapped in an RxJS Observable */ getItem(key: string, options: LSGetItemOptions & - ({ schema: JSONSchemaBoolean } | { type: 'boolean' })): Observable; + { schema: JSONSchemaBoolean }): Observable; getItem(key: string, options: LSGetItemOptions & - ({ schema: JSONSchemaInteger | JSONSchemaNumber } | { type: 'integer' | 'number' })): Observable; + { schema: JSONSchemaInteger | JSONSchemaNumber }): Observable; getItem(key: string, options: LSGetItemOptions & - ({ schema: JSONSchemaString } | { type: 'string' })): Observable; + { schema: JSONSchemaString }): Observable; getItem(key: string, options: LSGetItemOptions & - ({ schema: JSONSchemaArrayOf } | { type: 'boolean[]' })): Observable; + { schema: JSONSchemaArrayOf }): Observable; getItem(key: string, options: LSGetItemOptions & - ({ schema: JSONSchemaArrayOf } | { type: 'integer[]' | 'number[]'})): Observable; + { schema: JSONSchemaArrayOf }): Observable; getItem(key: string, options: LSGetItemOptions & - ({ schema: JSONSchemaArrayOf } | { type: 'string[]' })): Observable; + { schema: JSONSchemaArrayOf }): Observable; getItem(key: string, options: LSGetItemOptions & { schema: JSONSchema }): Observable; getItem(key: string, options?: LSGetItemOptions): Observable; getItem(key: string, options = this.getItemOptionsDefault) { @@ -67,15 +65,7 @@ export class LocalStorage { return of(null); - } - - if (!options.schema && options.type) { - - options.schema = this.typeToSchema(options.type); - - } - - if (options.schema) { + } else if (options.schema) { let validation = true; @@ -194,39 +184,4 @@ export class LocalStorage { } - private typeToSchema(type: LSGetItemOptions['type']): JSONSchema | null { - - switch (type) { - - case 'boolean': - return { type: 'boolean' }; - - case 'integer': - return { type: 'integer' }; - - case 'number': - return { type: 'number' }; - - case 'string': - return { type: 'string' }; - - case 'boolean[]': - return { type: 'array', items: { type: 'boolean' } }; - - case 'integer[]': - return { type: 'array', items: { type: 'integer' } }; - - case 'number[]': - return { type: 'array', items: { type: 'number' } }; - - case 'string[]': - return { type: 'array', items: { type: 'string' } }; - - default: - return null; - - } - - } - } From 582f97283d3cf474565cf2f3b54f86bbfc05fc69 Mon Sep 17 00:00:00 2001 From: Cyrille Tuzi Date: Sun, 27 Jan 2019 14:22:57 +0100 Subject: [PATCH 03/10] tests: fix typo --- .../local-storage/src/lib/validation/json-validation.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/ngx-pwa/local-storage/src/lib/validation/json-validation.spec.ts b/projects/ngx-pwa/local-storage/src/lib/validation/json-validation.spec.ts index 9d79bce3..ccdb91cd 100644 --- a/projects/ngx-pwa/local-storage/src/lib/validation/json-validation.spec.ts +++ b/projects/ngx-pwa/local-storage/src/lib/validation/json-validation.spec.ts @@ -124,7 +124,7 @@ describe(`JSONValidator`, () => { it(`should return false on a value not included in an enum`, () => { - const test = jsonValidator.validate('test2', { type: 'boolean', enum: ['test', 'hello'] }); + const test = jsonValidator.validate('test2', { type: 'string', enum: ['test', 'hello'] }); expect(test).toBe(false); From 1b614faa4b89635c3f94d8e4d72168c14f867798 Mon Sep 17 00:00:00 2001 From: Cyrille Tuzi Date: Sun, 27 Jan 2019 14:23:18 +0100 Subject: [PATCH 04/10] test: test json schema standard support --- .../src/lib/get-item-overloads.spec.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/projects/ngx-pwa/local-storage/src/lib/get-item-overloads.spec.ts b/projects/ngx-pwa/local-storage/src/lib/get-item-overloads.spec.ts index f7fc3927..b913df43 100644 --- a/projects/ngx-pwa/local-storage/src/lib/get-item-overloads.spec.ts +++ b/projects/ngx-pwa/local-storage/src/lib/get-item-overloads.spec.ts @@ -295,4 +295,23 @@ describe('getItem() overload signature', () => { }); + it('should compile with schema with unsupported options coming from JSON schema standard', (done: DoneFn) => { + + // TODO: check this in TS >= 3.3 as it seems weird unknown properties are allowed + localStorageService.getItem('test', { schema: { + type: 'object', + properties: { + test: { type: 'string' } + }, + ddd: 'ddd' + } }).subscribe((_) => { + + expect().nothing(); + + done(); + + }); + + }); + }); From e3df46071c27de3624981525b10cc28bdb9699b1 Mon Sep 17 00:00:00 2001 From: Cyrille Tuzi Date: Sun, 27 Jan 2019 14:39:23 +0100 Subject: [PATCH 05/10] tests: remove unecessary casts --- .../ngx-pwa/local-storage/src/lib/lib.service.spec.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/projects/ngx-pwa/local-storage/src/lib/lib.service.spec.ts b/projects/ngx-pwa/local-storage/src/lib/lib.service.spec.ts index 98fc6e75..90ca3902 100755 --- a/projects/ngx-pwa/local-storage/src/lib/lib.service.spec.ts +++ b/projects/ngx-pwa/local-storage/src/lib/lib.service.spec.ts @@ -362,7 +362,7 @@ function tests(localStorageService: LocalStorage) { properties: { expected: { type: 'string' - } as JSONSchemaString + } }, required: ['expected'] }; @@ -395,7 +395,7 @@ function tests(localStorageService: LocalStorage) { properties: { expected: { type: 'string' - } as JSONSchemaString + } }, required: ['expected'] }; @@ -957,8 +957,8 @@ describe('LocalStorage with IndexedDB', () => { [true, { type: 'boolean' }], [false, { type: 'boolean' }], // TODO: delete cast when TS 3.2 issue is fixed - [[1, 2, 3], { items: { type: 'number' } } as JSONSchema], - [{ test: 'value' }, { type: 'object', properties: { test: { type: 'string' } } } as JSONSchema], + [[1, 2, 3], { type: 'array', items: { type: 'number' } }], + [{ test: 'value' }, { type: 'object', properties: { test: { type: 'string' } } }], ]; for (const [getTestValue, getTestSchema] of getTestValues) { From 199c57b144dee643d1b255736d64ce64192d4121 Mon Sep 17 00:00:00 2001 From: Cyrille Tuzi Date: Sun, 27 Jan 2019 20:52:16 +0100 Subject: [PATCH 06/10] doc: documentation for v8 validation --- README.md | 2 +- docs/VALIDATION.md | 46 +++--- docs/VALIDATION_BEFORE_V8.md | 288 +++++++++++++++++++++++++++++++++++ 3 files changed, 316 insertions(+), 20 deletions(-) create mode 100644 docs/VALIDATION_BEFORE_V8.md diff --git a/README.md b/README.md index d753a6cd..260ae42a 100644 --- a/README.md +++ b/README.md @@ -136,7 +136,7 @@ Starting with *version 5*, you can use a [JSON Schema](http://json-schema.org/) A [migration guide](./docs/MIGRATION_TO_V7.md) is available. ```typescript -this.localStorage.getItem('test', { schema: { type: 'string' } }) +this.localStorage.getItem('test', { schema: { type: 'string' } }) .subscribe((user) => { // Called if data is valid or null }, (error) => { diff --git a/docs/VALIDATION.md b/docs/VALIDATION.md index 78ca84b7..71080079 100644 --- a/docs/VALIDATION.md +++ b/docs/VALIDATION.md @@ -1,5 +1,10 @@ # Validation guide +## Version + +This is the up to date guide about validation for version >= 8. +The old guide for validation in versions < 8 is available [here](./VALIDATION_BEFORE_V8.md). + ## Why validation? Any client-side storage (cookies, `localStorage`, `indexedDb`...) is not secure by nature, @@ -27,52 +32,52 @@ as you'll see the more complex it is, the more complex is validation too. ### Boolean ```typescript -this.localStorage.getItem('test', { schema: { type: 'boolean' } }) +this.localStorage.getItem('test', { schema: { type: 'boolean' } }) ``` ### Integer ```typescript -this.localStorage.getItem('test', { schema: { type: 'integer' } }) +this.localStorage.getItem('test', { schema: { type: 'integer' } }) ``` ### Number ```typescript -this.localStorage.getItem('test', { schema: { type: 'number' } }) +this.localStorage.getItem('test', { schema: { type: 'number' } }) ``` ### String ```typescript -this.localStorage.getItem('test', { schema: { type: 'string' } }) +this.localStorage.getItem('test', { schema: { type: 'string' } }) ``` ### Arrays ```typescript -this.localStorage.getItem('test', { schema: { +this.localStorage.getItem('test', { schema: { type: 'array', items: { type: 'boolean' } } }) ``` ```typescript -this.localStorage.getItem('test', { schema: { +this.localStorage.getItem('test', { schema: { type: 'array', items: { type: 'integer' } } }) ``` ```typescript -this.localStorage.getItem('test', { schema: { +this.localStorage.getItem('test', { schema: { type: 'array', items: { type: 'number' } } }) ``` ```typescript -this.localStorage.getItem('test', { schema: { +this.localStorage.getItem('test', { schema: { type: 'array', items: { type: 'string' } } }) @@ -107,7 +112,7 @@ this.localStorage.getItem('test', { schema }) What's expected for each property is another JSON schema. -## Why a schema *and* a cast? +### Why a schema *and* a cast? You may ask why we have to define a TypeScript cast with `getItem()` *and* a JSON schema with `{ schema }`. @@ -117,22 +122,24 @@ It's because they happen at different steps: So they each serve a different purpose: - casting allow you to retrieve the data if the good type instead of `any` -- the schema allow the lib to validate the data at runtime +- the schema allow the lib to validate the data at + +For previous basic types, as they are static, we can infer automatically. +But as objects properties are dynamic, we can't do the same for objects. Be aware **you are responsible the casted type (`User`) describes the same structure as the JSON schema**. The lib can't check that. -## How to validate fixed values - -Temporarily, documentation for constants and enums is removed, -as there will be a breaking change in v8. - ## Additional validation -Some types have additional validation options since version >= 6. +### Options for booleans + +- `const` ### Options for integers and numbers +- `const` +- `enum` - `multipleOf` - `maximum` - `exclusiveMaximum` @@ -140,7 +147,6 @@ Some types have additional validation options since version >= 6. - `exclusiveMinimum` For example: - ```typescript this.localStorage.getItem('test', { schema: { type: 'number', @@ -150,6 +156,8 @@ this.localStorage.getItem('test', { schema: { ### Options for strings +- `const` +- `enum` - `maxLength` - `minLength` - `pattern` @@ -210,7 +218,7 @@ this.localStorage.getItem('test', { schema }) If validation fails, it'll go in the error callback: ```typescript -this.localStorage.getItem('existing', { schema: { type: 'string' } }) +this.localStorage.getItem('existing', { schema: { type: 'string' } }) .subscribe((result) => { // Called if data is valid or null }, (error) => { @@ -221,7 +229,7 @@ this.localStorage.getItem('existing', { schema: { type: 'string' } }) But as usual (like when you do a database request), not finding an item is not an error. It succeeds but returns `null`. ```typescript -this.localStorage.getItem('notExisting', { schema: { type: 'string' } }) +this.localStorage.getItem('notExisting', { schema: { type: 'string' } }) .subscribe((result) => { result; // null }, (error) => { diff --git a/docs/VALIDATION_BEFORE_V8.md b/docs/VALIDATION_BEFORE_V8.md new file mode 100644 index 00000000..0569a01f --- /dev/null +++ b/docs/VALIDATION_BEFORE_V8.md @@ -0,0 +1,288 @@ +# Validation guide + +## Version + +This is old guide for validation in versions < 8. +The up to date guide about validation for version >= 8 is available [here](./VALIDATION.md). + +## Why validation? + +Any client-side storage (cookies, `localStorage`, `indexedDb`...) is not secure by nature, +as the client can forge the value (intentionally to attack your app, or unintentionally because it is affected by a virus or a XSS attack). + +It can cause obvious **security issues**, but also **errors** and thus crashes (as the received data type may not be what you expected). + +Then, **any data coming from client-side storage should be checked before used**. + +It was allowed since v5 of the lib, and is **now required since v7** (see the [migration guide](./MIGRATION_TO_V7.md)). + +## Why JSON schemas? + +[JSON Schema](https://json-schema.org/) is a standard to describe the structure of a JSON data. +You can see this as an equivalent to the DTD in XML, the Doctype in HTML or interfaces in TypeScript. + +It can have many uses (it's why you have autocompletion in some JSON files in Visual Studio Code). +**In this lib, JSON schemas are used to validate the data retrieved from local storage.** + +## How to validate simple data + +As a general recommendation, we recommend to keep your data structures as simple as possible, +as you'll see the more complex it is, the more complex is validation too. + +### Boolean + +```typescript +this.localStorage.getItem('test', { schema: { type: 'boolean' } }) +``` + +### Integer + +```typescript +this.localStorage.getItem('test', { schema: { type: 'integer' } }) +``` + +### Number + +```typescript +this.localStorage.getItem('test', { schema: { type: 'number' } }) +``` + +### String + +```typescript +this.localStorage.getItem('test', { schema: { type: 'string' } }) +``` + +### Arrays + +```typescript +this.localStorage.getItem('test', { schema: { + type: 'array', + items: { type: 'boolean' } +} }) +``` + +```typescript +this.localStorage.getItem('test', { schema: { + type: 'array', + items: { type: 'integer' } +} }) +``` + +```typescript +this.localStorage.getItem('test', { schema: { + type: 'array', + items: { type: 'number' } +} }) +``` + +```typescript +this.localStorage.getItem('test', { schema: { + type: 'array', + items: { type: 'string' } +} }) +``` + +What's expected in `items` is another JSON schema. + +## How to validate objects + +For example: +```typescript +import { JSONSchema } from '@ngx-pwa/local-storage'; + +interface User { + firstName: string; + lastName: string; + age?: number; +} + +const schema: JSONSchema = { + type: 'object', + properties: { + firstName: { type: 'string' }, + lastName: { type: 'string' }, + age: { type: 'number' }, + }, + required: ['firstName', 'lastName'] +}; + +this.localStorage.getItem('test', { schema }) +``` + +What's expected for each property is another JSON schema. + +## Why a schema *and* a cast? + +You may ask why we have to define a TypeScript cast with `getItem()` *and* a JSON schema with `{ schema }`. + +It's because they happen at different steps: +- a cast (`getItem()`) just says "TypeScript, trust me, I'm telling you it will be a `User`", but it only happens at *compilation* time (it won't be checked at runtime) +- the JSON schema (`{ schema }`) will be used at *runtime* when getting data in local storage for real. + +So they each serve a different purpose: +- casting allow you to retrieve the data if the good type instead of `any` +- the schema allow the lib to validate the data at runtime + +Be aware **you are responsible the casted type (`User`) describes the same structure as the JSON schema**. +The lib can't check that. + +## How to validate fixed values + +Temporarily, documentation for constants and enums is removed, +as there will be a breaking change in v8. + +## Additional validation + +Some types have additional validation options since version >= 6. + +### Options for integers and numbers + +- `multipleOf` +- `maximum` +- `exclusiveMaximum` +- `minimum` +- `exclusiveMinimum` + +For example: + +```typescript +this.localStorage.getItem('test', { schema: { + type: 'number', + maximum: 5 +} }) +``` + +### Options for strings + +- `maxLength` +- `minLength` +- `pattern` + +For example: +```typescript +this.localStorage.getItem('test', { schema: { + type: 'string', + maxLength: 10 +} }) +``` + +### Options for arrays + +- `maxItems` +- `minItems` +- `uniqueItems` + +For example: +```typescript +this.localStorage.getItem('test', { schema: { + type: 'array', + items: { type: 'string' }, + maxItems: 5 +} }) +``` + +## How to validate nested types + +```typescript +import { JSONSchema } from '@ngx-pwa/local-storage'; + +interface User { + firstName: string; + lastName: string; +} + +const schema: JSONSchema = { + type: 'array', + items: { + type: 'object', + properties: { + firstName: { + type: 'string', + maxLength: 10 + }, + lastName: { type: 'string' } + }, + required: ['firstName', 'lastName'] + } +}; + +this.localStorage.getItem('test', { schema }) +``` + +## Errors vs. `null` + +If validation fails, it'll go in the error callback: + +```typescript +this.localStorage.getItem('existing', { schema: { type: 'string' } }) +.subscribe((result) => { + // Called if data is valid or null +}, (error) => { + // Called if data is invalid +}); +``` + +But as usual (like when you do a database request), not finding an item is not an error. It succeeds but returns `null`. + +```typescript +this.localStorage.getItem('notExisting', { schema: { type: 'string' } }) +.subscribe((result) => { + result; // null +}, (error) => { + // Not called +}); +``` + +## Differences from the standard + +The role of the validation feature in this lib is to check the data against corruption, +so it needs to be a strict checking. Then there are important differences with the JSON schema standards. + +### Restrictions + +Types are enforced: each value MUST have a `type`. + +### Unsupported features + +The following features available in the JSON schema standard +are *not* available in this lib: +- `additionalItems` +- `additionalProperties` +- `propertyNames` +- `maxProperties` +- `minProperties` +- `patternProperties` +- `not` +- `contains` +- `allOf` +- `anyOf` +- `oneOf` +- array for `type` + +## ES6 shortcut + +In EcmaScript >= 6, this: + +```typescript +const schema: JSONSchemaBoolean = { type: 'boolean' }; + +this.localStorage.getItem('test', { schema }); +``` + +is a shortcut for this: +```typescript +const schema: JSONSchemaBoolean = { type: 'boolean' }; + +this.localStorage.getItem('test', { schema: schema }); +``` + +which works only if the property and the variable have the same name. +So if your variable has another name, you can't use the shortcut: +```typescript +const customSchema: JSONSchemaBoolean = { type: 'boolean' }; + +this.localStorage.getItem('test', { schema: customSchema }); +``` + +[Back to general documentation](../README.md) From ce1ff456ab2debd6e11f7d0d16f5640acad7d2a6 Mon Sep 17 00:00:00 2001 From: Cyrille Tuzi Date: Sun, 27 Jan 2019 20:54:33 +0100 Subject: [PATCH 07/10] doc: delete old statement --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 260ae42a..dd2aff6b 100644 --- a/README.md +++ b/README.md @@ -114,8 +114,6 @@ this.localStorage.getItem('user').subscribe((user) => { }); ``` -As any data can be stored, you can type your data. - Not finding an item is not an error, it succeeds but returns `null`. ```typescript From 6e7077114281bbdc479cf10a2ddc9ce7250ecb6f Mon Sep 17 00:00:00 2001 From: Cyrille Tuzi Date: Sun, 27 Jan 2019 22:06:23 +0100 Subject: [PATCH 08/10] tests: fix const tests --- .../src/lib/validation/json-validation.spec.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/projects/ngx-pwa/local-storage/src/lib/validation/json-validation.spec.ts b/projects/ngx-pwa/local-storage/src/lib/validation/json-validation.spec.ts index 34fd0e2f..8c93a0ed 100644 --- a/projects/ngx-pwa/local-storage/src/lib/validation/json-validation.spec.ts +++ b/projects/ngx-pwa/local-storage/src/lib/validation/json-validation.spec.ts @@ -48,7 +48,7 @@ describe(`JSONValidator`, () => { it(`should return true on a number equal to a number const`, () => { - const test = jsonValidator.validate(1.5, { type: 'integer', const: 1.5 }); + const test = jsonValidator.validate(1.5, { type: 'number', const: 1.5 }); expect(test).toBe(true); @@ -56,7 +56,7 @@ describe(`JSONValidator`, () => { it(`should return false on a number not equal to a number const`, () => { - const test = jsonValidator.validate(2.5, { type: 'integer', const: 1.5 }); + const test = jsonValidator.validate(2.5, { type: 'number', const: 1.5 }); expect(test).toBe(false); @@ -64,7 +64,7 @@ describe(`JSONValidator`, () => { it(`should return true on an integer equal to an integer const`, () => { - const test = jsonValidator.validate(1, { type: 'number', const: 1 }); + const test = jsonValidator.validate(1, { type: 'integer', const: 1 }); expect(test).toBe(true); @@ -72,7 +72,7 @@ describe(`JSONValidator`, () => { it(`should return false on an integer not equal to an integer const`, () => { - const test = jsonValidator.validate(2, { type: 'number', const: 1 }); + const test = jsonValidator.validate(2, { type: 'integer', const: 1 }); expect(test).toBe(false); From 8ce0a87f26d8fda2edd097231ac73a8e86c12720 Mon Sep 17 00:00:00 2001 From: Cyrille Tuzi Date: Sun, 27 Jan 2019 22:10:31 +0100 Subject: [PATCH 09/10] feat: update to TS 3.2 --- package-lock.json | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 85ffed68..9ec29b29 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12045,9 +12045,9 @@ "dev": true }, "typescript": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.1.6.tgz", - "integrity": "sha512-tDMYfVtvpb96msS1lDX9MEdHrW4yOuZ4Kdc4Him9oU796XldPYF/t2+uKoX0BBa0hXXwDlqYQbXY5Rzjzc5hBA==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.2.4.tgz", + "integrity": "sha512-0RNDbSdEokBeEAkgNbxJ+BLwSManFy9TeXz8uW+48j/xhEXv1ePME60olyzw2XzUqUBNAYFeJadIqAgNqIACwg==", "dev": true }, "uglify-es": { diff --git a/package.json b/package.json index 2a86181e..2e514095 100644 --- a/package.json +++ b/package.json @@ -55,6 +55,6 @@ "tsickle": "^0.34.0", "tslib": "^1.7.1", "tslint": "^5.12.1", - "typescript": "^3.1.6" + "typescript": "^3.2.4" } } From c576497265a1651f8ad0fb8175919d9000502322 Mon Sep 17 00:00:00 2001 From: Cyrille Tuzi Date: Mon, 4 Feb 2019 19:39:27 +0100 Subject: [PATCH 10/10] docs: flag specific JSONSchema interfaces as to be ignored --- .../local-storage/src/lib/lib.service.ts | 45 +++++++++---------- .../src/lib/validation/json-schema.ts | 25 +++++------ 2 files changed, 34 insertions(+), 36 deletions(-) diff --git a/projects/ngx-pwa/local-storage/src/lib/lib.service.ts b/projects/ngx-pwa/local-storage/src/lib/lib.service.ts index 1a95d5b6..7d63ac27 100755 --- a/projects/ngx-pwa/local-storage/src/lib/lib.service.ts +++ b/projects/ngx-pwa/local-storage/src/lib/lib.service.ts @@ -38,20 +38,20 @@ export class LocalStorage { * The signature has many overloads due to validation, please refer to the documentation. * @see https://github.com/cyrilletuzi/angular-async-local-storage/blob/master/docs/VALIDATION.md * @param key The item's key - * @returns The item's value if the key exists, null otherwise, wrapped in an RxJS Observable + * @returns The item's value if the key exists, `null` otherwise, wrapped in an RxJS `Observable` */ - getItem(key: string, options: LSGetItemOptions & - { schema: JSONSchemaBoolean }): Observable; - getItem(key: string, options: LSGetItemOptions & - { schema: JSONSchemaInteger | JSONSchemaNumber }): Observable; getItem(key: string, options: LSGetItemOptions & { schema: JSONSchemaString }): Observable; - getItem(key: string, options: LSGetItemOptions & - { schema: JSONSchemaArrayOf }): Observable; - getItem(key: string, options: LSGetItemOptions & - { schema: JSONSchemaArrayOf }): Observable; + getItem(key: string, options: LSGetItemOptions & + { schema: JSONSchemaInteger | JSONSchemaNumber }): Observable; + getItem(key: string, options: LSGetItemOptions & + { schema: JSONSchemaBoolean }): Observable; getItem(key: string, options: LSGetItemOptions & { schema: JSONSchemaArrayOf }): Observable; + getItem(key: string, options: LSGetItemOptions & + { schema: JSONSchemaArrayOf }): Observable; + getItem(key: string, options: LSGetItemOptions & + { schema: JSONSchemaArrayOf }): Observable; getItem(key: string, options: LSGetItemOptions & { schema: JSONSchema }): Observable; getItem(key: string, options?: LSGetItemOptions): Observable; getItem(key: string, options = this.getItemOptionsDefault) { @@ -88,14 +88,13 @@ export class LocalStorage { } /** - * Gets an item value in local storage WITHOUT any validation. - * It is a convenience method for development only: do NOT use it in production code, - * as it can cause security issues and errors and may be removed in future versions. - * Use the normal .getItem() method instead. - * @ignore - * @deprecated + * Gets an item value in local storage *without* any validation. + * It is a convenience method for development only: **do not use it in production code**, + * as it can cause security issues and errors. + * @ignore Use the `.getItem()` method instead. + * @deprecated May be removed in future versions. * @param key The item's key - * @returns The item's value if the key exists, null otherwise, wrapped in an RxJS Observable + * @returns The item's value if the key exists, `null` otherwise, wrapped in an RxJS `Observable` */ getUnsafeItem(key: string): Observable { @@ -106,8 +105,8 @@ export class LocalStorage { /** * Sets an item in local storage * @param key The item's key - * @param data The item's value, must NOT be null or undefined - * @returns An RxJS Observable to wait the end of the operation + * @param data The item's value, **must not be `null` or `undefined`** + * @returns An RxJS `Observable` to wait the end of the operation */ setItem(key: string, data: any): Observable { @@ -118,7 +117,7 @@ export class LocalStorage { /** * Deletes an item in local storage * @param key The item's key - * @returns An RxJS Observable to wait the end of the operation + * @returns An RxJS `Observable` to wait the end of the operation */ removeItem(key: string): Observable { @@ -128,7 +127,7 @@ export class LocalStorage { /** * Deletes all items from local storage - * @returns An RxJS Observable to wait the end of the operation + * @returns An RxJS `Observable` to wait the end of the operation */ clear(): Observable { @@ -138,7 +137,7 @@ export class LocalStorage { /** * Get all keys stored in local storage - * @returns A RxJS Observable returning an array of the indexes + * @returns A RxJS `Observable` returning an array of the indexes */ keys(): Observable { @@ -148,7 +147,7 @@ export class LocalStorage { /** * Tells if a key exists in storage - * @returns A RxJS Observable telling if the key exists + * @returns A RxJS `Observable` telling if the key exists */ has(key: string): Observable { @@ -159,7 +158,7 @@ export class LocalStorage { /** * Sets an item in local storage, and auto-subscribes * @param key The item's key - * @param data The item's value, must NOT be null or undefined + * @param data The item's value, **must not be `null` or `undefined`** */ setItemSubscribe(key: string, data: any): void { diff --git a/projects/ngx-pwa/local-storage/src/lib/validation/json-schema.ts b/projects/ngx-pwa/local-storage/src/lib/validation/json-schema.ts index 728108ef..7155487d 100644 --- a/projects/ngx-pwa/local-storage/src/lib/validation/json-schema.ts +++ b/projects/ngx-pwa/local-storage/src/lib/validation/json-schema.ts @@ -1,5 +1,6 @@ /** * JSON Schema to describe a boolean value. + * @ignore Internal type, **do not use**, use `JSONSchema` instead */ export interface JSONSchemaBoolean { @@ -17,6 +18,7 @@ export interface JSONSchemaBoolean { /** * JSON Schema to describe a number value. + * @ignore Internal type, **do not use**, use `JSONSchema` instead */ export interface JSONSchemaNumber { @@ -65,6 +67,7 @@ export interface JSONSchemaNumber { /** * JSON Schema to describe an integer value. + * @ignore Internal type, **do not use**, use `JSONSchema` instead */ export interface JSONSchemaInteger { @@ -113,6 +116,7 @@ export interface JSONSchemaInteger { /** * JSON Schema to describe a string value. + * @ignore Internal type, **do not use**, use `JSONSchema` instead */ export interface JSONSchemaString { @@ -153,8 +157,7 @@ export interface JSONSchemaString { /** * JSON schema to describe an array of values. - * For arrays of primitive types (booleans, numbers and strings), - * prefer the more specific `JSONSchemaArrayOf` interface. + * @ignore Internal type, **do not use**, use `JSONSchema` instead */ export interface JSONSchemaArray { @@ -193,6 +196,7 @@ export interface JSONSchemaArray { * - array of numbers: `JSONSchemaArrayOf`, * - array of integers: `JSONSchemaArrayOf`, * - array of strings: `JSONSchemaArrayOf`. + * @ignore Internal type, **do not use**, use `JSONSchema` instead */ export interface JSONSchemaArrayOf { @@ -227,6 +231,7 @@ export interface JSONSchemaArrayOf