diff --git a/src/custom/types.ts b/src/custom/types.ts index b0d2e73..e2a419a 100644 --- a/src/custom/types.ts +++ b/src/custom/types.ts @@ -28,8 +28,9 @@ import {Schema} from '../schema'; import {type SchemaData} from '../schema/data'; import {type CustomTypesInit} from './types/init'; import {type CustomTypesData} from './types/data'; -import {CustomTypeVerifier} from './type/verifier'; +import {type CustomTypeVerifier} from './type/verifier'; import {Fate} from '@toreda/fate'; +import {schemaError} from '../schema/error'; /** * @category Schemas - Custom Types @@ -138,18 +139,25 @@ export class CustomTypes, VerifiedT = In return schema; } - public async verifyValue(type: string, value: unknown): Promise> { + public async verifyValue(type: string, value: unknown, base: Log): Promise> { const fate = new Fate(); - +zzs + const verifier = this.getVerifier(type); return fate; } public async verifySchema( type: string, - value: unknown | SchemaData + value: SchemaData, + base: Log ): Promise>> { const fate = new Fate>(); + const schema = this.getSchema(type); - return fate; + if (!schema) { + return fate.setErrorCode(schemaError('missing_custom_type_schema', type)); + } + + return schema.verify(value, base); } } diff --git a/src/custom/types/data.ts b/src/custom/types/data.ts index 7a571f5..d455c0a 100644 --- a/src/custom/types/data.ts +++ b/src/custom/types/data.ts @@ -24,7 +24,6 @@ */ import {Schema} from '../../schema'; -import {type SchemaData} from '../../schema/data'; /** * @category Schemas - Custom Types diff --git a/src/index.ts b/src/index.ts index 1edd8ed..6c4b798 100644 --- a/src/index.ts +++ b/src/index.ts @@ -102,6 +102,7 @@ export {simpleOutputTransform} from './simple/output/transform'; export {Statement} from './statement'; export {stringIdVerify} from './string/id/verify'; export {urlVerify} from './url/verify'; +export {valueTypeLabel} from './value/type/label'; export {Verified} from './verified'; export {Verifier} from './verifier'; export {VerifierCounts} from './verifier/counts'; diff --git a/src/schema.ts b/src/schema.ts index ab5e547..c3b049d 100644 --- a/src/schema.ts +++ b/src/schema.ts @@ -39,6 +39,7 @@ import {isInt} from './is/int'; import {type SchemaFieldData} from './schema/field/data'; import {CustomTypes} from './custom/types'; import {schemaBuiltIns} from './schema/built/ins'; +import {valueTypeLabel} from './value/type/label'; /** * @category Schemas @@ -85,7 +86,8 @@ export class Schema, VerifiedT = InputT> public async verifyField( name: string, field: SchemaField, - value: unknown + value: unknown, + base: Log ): Promise | null>> { const fate = new Fate | null>(); @@ -97,7 +99,7 @@ export class Schema, VerifiedT = InputT> return fate.setErrorCode(schemaError('missing_field_value', `${this.schemaName}.${name}`)); } - const verified = await this.fieldSupportsValue(field, value); + const verified = await this.fieldSupportsValue(field, value, base); if (!verified.ok()) { return fate.setErrorCode(verified.errorCode()); } @@ -109,24 +111,25 @@ export class Schema, VerifiedT = InputT> public async fieldSupportsValue( field: SchemaField, - value: unknown + value: unknown, + base: Log ): Promise>> { const fate = new Fate>(); if (value === null && !field.types.includes('null')) { return fate.setErrorCode( - schemaError('unsupported_type:null', `${this.schemaName}.${field.name}`) + schemaError('field_does_not_support_type:null', `${this.schemaName}.${field.name}`) ); } for (const type of field.types) { if (!this.schemaSupportsType(type)) { return fate.setErrorCode( - schemaError(`unsupported_schema_type:${type}`, `${this.schemaName}.${field.name}`) + schemaError(`field_does_not_support_type:${type}`, `${this.schemaName}.${field.name}`) ); } - const result = await this.verifyValue(type, value); + const result = await this.verifyValue(type, value, base); if (result.ok() === true) { fate.data = result.data; @@ -135,7 +138,10 @@ export class Schema, VerifiedT = InputT> } return fate.setErrorCode( - schemaError(`unsupported_type:${typeof value}`, `${this.schemaName}.${field.name}`) + schemaError( + `field_does_not_support_type:${valueTypeLabel(value)}`, + `${this.schemaName}.${field.name}` + ) ); } @@ -203,32 +209,38 @@ export class Schema, VerifiedT = InputT> */ public async verifyValue( type: SchemaFieldType, - value: unknown | SchemaData + value: unknown | SchemaData, + base: Log ): Promise>> { const fate = new Fate>(); - - if (this.valueIsBuiltInType(type, value)) { - // TODO: Add validation here. Type match does not automatically prove valid content. - fate.data = value; - return fate.setSuccess(true); - } - - if (!this.customTypes.has(type)) { - return fate.setErrorCode( - schemaError(`unsupported_type:${typeof value}`, `${this.schemaName}.verifyValue`) - ); + if (this.isBuiltIn(type)) { + if (this.valueIsBuiltInType(type, value)) { + // TODO: Add validation here. Type match does not automatically prove valid content. + fate.data = value; + return fate.setSuccess(true); + } else { + return fate.setErrorCode( + schemaError( + `field_does_not_support_value_type:${valueTypeLabel(value)}`, + `${this.schemaName}.verifyValue` + ) + ); + } } - if (this.customTypes.hasSchema(type)) { - return this.customTypes.verifySchema(type, value); + base.debug(`@@@@@@@@@@@@@@@ TYPE: ${type} // TYPEOF VAL: ${valueTypeLabel(value)}`); + if (this.customTypes.hasSchema(type) && typeof value === 'object') { + return this.customTypes.verifySchema(type, value as SchemaData, base); } //const custom = await this.customTypes.verify(type, value, this.base); if (this.customTypes.hasVerifier(type)) { - return this.customTypes.verifyValue(type, value); + return this.customTypes.verifyValue(type, value, base); } - return fate.setErrorCode(`field_cant_verify_custom_type:${type?.toString()}`); + return fate.setErrorCode( + schemaError(`field_does_not_support_type:${type}`, `${this.schemaName}.verifyValue`) + ); } public async verify(data: SchemaData, base: Log): Promise> { @@ -269,7 +281,7 @@ export class Schema, VerifiedT = InputT> for (const [id, field] of this.fields.entries()) { const name = id.toString(); - const verified = await this.verifyField(name, field, data[name]); + const verified = await this.verifyField(name, field, data[name], base); if (!verified.ok()) { return fate.setErrorCode(verified.errorCode()); diff --git a/src/schema/data.ts b/src/schema/data.ts index b2118a3..5641913 100644 --- a/src/schema/data.ts +++ b/src/schema/data.ts @@ -27,5 +27,5 @@ * @category Schemas */ export interface SchemaData { - [k: string]: DataT | null; + [k: string]: DataT | SchemaData | null; } diff --git a/src/schema/field/data.ts b/src/schema/field/data.ts index f067c22..860e689 100644 --- a/src/schema/field/data.ts +++ b/src/schema/field/data.ts @@ -28,8 +28,8 @@ import type {SchemaFieldType} from './type'; /** * @category Schemas */ -export interface SchemaFieldData { +export interface SchemaFieldData { name: keyof InputT; - types: SchemaFieldType | SchemaFieldType[]; + types: SchemaFieldType | (SchemaFieldType | keyof InputT)[] | keyof InputT[]; defaultValue?: unknown; } diff --git a/src/schema/field/type.ts b/src/schema/field/type.ts index 0e6b9dd..7a43d06 100644 --- a/src/schema/field/type.ts +++ b/src/schema/field/type.ts @@ -26,7 +26,7 @@ /** * @category Schemas */ -export type SchemaFieldType = +export type SchemaFieldType = | 'array' | 'bigint' | 'BigInt' @@ -46,4 +46,5 @@ export type SchemaFieldType = | 'uint' | 'undefined' | 'url' - | 'time'; + | 'time' + | keyof CustomT; diff --git a/src/value/type/label.ts b/src/value/type/label.ts new file mode 100644 index 0000000..bfc6041 --- /dev/null +++ b/src/value/type/label.ts @@ -0,0 +1,11 @@ +export function valueTypeLabel(value: unknown): string { + if (value === null) { + return 'null'; + } + + if (Array.isArray(value)) { + return 'array'; + } + + return typeof value; +} diff --git a/tests/schehma/types.spec.ts b/tests/schehma/types.spec.ts new file mode 100644 index 0000000..81b7de9 --- /dev/null +++ b/tests/schehma/types.spec.ts @@ -0,0 +1,37 @@ +import {schemaFieldTypes} from '../../src/schema/field/types'; + +const EXPECTED_TYPES: string[] = [ + 'array', + 'bigint', + 'boolean', + 'datetime', + 'dbl', + 'double', + 'float', + 'int', + 'iterable', + 'json-serialized', + 'json', + 'null', + 'number', + 'real', + 'string', + 'time', + 'uint', + 'undefined', + 'url' +]; + +describe('Schema Field Types (built-in)', () => { + let fieldTypes: Set; + + beforeAll(() => { + fieldTypes = new Set(schemaFieldTypes); + }); + + for (const expectedType of EXPECTED_TYPES) { + it(`should include built-in type '${expectedType}'`, () => { + expect(fieldTypes.has(expectedType)).toBe(true); + }); + } +}); diff --git a/tests/schema.spec.ts b/tests/schema.spec.ts index f0c942f..14108af 100644 --- a/tests/schema.spec.ts +++ b/tests/schema.spec.ts @@ -5,10 +5,28 @@ import {stringValue} from '@toreda/strong-types'; import {type SchemaData} from '../src/schema/data'; import {type Primitive} from '@toreda/types'; import {SchemaField} from '../src'; +import {valueTypeLabel} from '../src/value/type/label'; const EMPTY_OBJECT = {}; const EMPTY_STRING = ''; +interface SampleData extends SchemaData { + str1: string; + int1: number; + bool1: boolean; +} + +interface SampleBData extends SchemaData { + str2b: string; + int2b: number; +} + +interface SampleAData extends SchemaData { + str1a: string; + int1a: number; + subValue: SampleBData; +} + class SampleSchemaSubB extends Schema { constructor(base: Log) { super({ @@ -29,10 +47,10 @@ class SampleSchemaSubB extends Schema { } } -class SampleSchemaSubA extends Schema { +class SampleSchemaSubA extends Schema { constructor(schemaB: SampleSchemaSubB, base: Log) { super({ - name: 'SampleSchema', + name: 'SampleSchemaSubA', fields: [ { name: 'str1a', @@ -43,13 +61,13 @@ class SampleSchemaSubA extends Schema { types: ['number'] }, { - name: 'bool1a', - types: ['boolean', 'null'] + name: 'subValue', + types: ['ct2', 'null'] } ], options: {}, customTypes: { - customtype2: schemaB + ct2: schemaB }, base: base }); @@ -80,17 +98,6 @@ class SampleSchema extends Schema { } } -interface SampleData extends SchemaData { - str1: string; - int1: number; - bool1: boolean; -} - -interface SampleBData extends SchemaData { - str2b: string; - int2b: number; -} - describe('schemaVerify', () => { let sampleData: SampleData; let schema: SampleSchema; @@ -187,7 +194,7 @@ describe('schemaVerify', () => { expect(result.errorCode()).toBe( schemaError( - `unsupported_schema_type:${fieldType}`, + `field_does_not_support_type:${fieldType}`, `${schema.schemaName}.${field.name}` ) ); @@ -286,7 +293,7 @@ describe('schemaVerify', () => { const result = await customSchema.verify(sampleData, base); expect(result.errorCode()).toBe( - schemaError('unsupported_type:null', `${schema.schemaName}.${field.name}`) + schemaError('field_does_not_support_type:null', `${schema.schemaName}.${field.name}`) ); expect(result.ok()).toBe(false); }); @@ -333,7 +340,7 @@ describe('schemaVerify', () => { throw new Error(`Missing bool1 field in schema '${customSchema.schemaName}`); } - const result = await customSchema.verifyField(field.name, undefined as any, null); + const result = await customSchema.verifyField(field.name, undefined as any, null, base); expect(result.errorCode()).toBe( schemaError('missing_field', `${customSchema.schemaName}.${field.name}`) @@ -352,10 +359,10 @@ describe('schemaVerify', () => { field.types.length = 0; field.types.push('boolean'); - const result = await customSchema.verifyField(field.name, field, null); + const result = await customSchema.verifyField(field.name, field, null, base); expect(result.errorCode()).toBe( - schemaError('unsupported_type:null', `${customSchema.schemaName}.${field.name}`) + schemaError('field_does_not_support_type:null', `${customSchema.schemaName}.${field.name}`) ); expect(result.ok()).toBe(false); }); @@ -371,7 +378,7 @@ describe('schemaVerify', () => { field.types?.push('null'); const fieldName = stringValue(field?.name, '__field_name__'); - const result = await customSchema.verifyField(fieldName, field!, null); + const result = await customSchema.verifyField(fieldName, field!, null, base); expect(result.errorCode()).toBe(EMPTY_STRING); expect(result.ok()).toBe(true); @@ -382,7 +389,7 @@ describe('schemaVerify', () => { describe('boolean', () => { it(`should succeed and return value when type is 'boolean' and value is true`, async () => { const value = true; - const result = await schema.verifyValue('boolean', value); + const result = await schema.verifyValue('boolean', value, base); expect(result.errorCode()).toBe(EMPTY_STRING); expect(result.data).toBe(value); @@ -391,7 +398,7 @@ describe('schemaVerify', () => { it(`should succeed and return value when type is 'boolean' and value is false`, async () => { const value = false; - const result = await schema.verifyValue('boolean', value); + const result = await schema.verifyValue('boolean', value, base); expect(result.errorCode()).toBe(EMPTY_STRING); expect(result.data).toBe(value); @@ -400,50 +407,65 @@ describe('schemaVerify', () => { it(`should fail when type is 'boolean' and value is 0`, async () => { const value = 0; - const result = await schema.verifyValue('boolean', value); + const result = await schema.verifyValue('boolean', value, base); expect(result.errorCode()).toBe( - schemaError(`unsupported_type:${typeof value}`, `${schema.schemaName}.verifyValue`) + schemaError( + `field_does_not_support_value_type:${valueTypeLabel(value)}`, + `${schema.schemaName}.verifyValue` + ) ); expect(result.ok()).toBe(false); }); it(`should fail when type is 'boolean' and value is 1`, async () => { const value = 1; - const result = await schema.verifyValue('boolean', value); + const result = await schema.verifyValue('boolean', value, base); expect(result.errorCode()).toBe( - schemaError(`unsupported_type:${typeof value}`, `${schema.schemaName}.verifyValue`) + schemaError( + `field_does_not_support_value_type:${valueTypeLabel(value)}`, + `${schema.schemaName}.verifyValue` + ) ); expect(result.ok()).toBe(false); }); it(`should fail when type is 'boolean' and value is an empty object`, async () => { const value = {}; - const result = await schema.verifyValue('boolean', value); + const result = await schema.verifyValue('boolean', value, base); expect(result.errorCode()).toBe( - schemaError(`unsupported_type:${typeof value}`, `${schema.schemaName}.verifyValue`) + schemaError( + `field_does_not_support_value_type:${valueTypeLabel(value)}`, + `${schema.schemaName}.verifyValue` + ) ); expect(result.ok()).toBe(false); }); it(`should fail when type is 'boolean' and value is null`, async () => { const value = null; - const result = await schema.verifyValue('boolean', value); + const result = await schema.verifyValue('boolean', value, base); expect(result.errorCode()).toBe( - schemaError(`unsupported_type:${typeof value}`, `${schema.schemaName}.verifyValue`) + schemaError( + `field_does_not_support_value_type:${valueTypeLabel(value)}`, + `${schema.schemaName}.verifyValue` + ) ); expect(result.ok()).toBe(false); }); it(`should fail when type is 'boolean' and value is undefined`, async () => { const value = undefined; - const result = await schema.verifyValue('boolean', value); + const result = await schema.verifyValue('boolean', value, base); expect(result.errorCode()).toBe( - schemaError(`unsupported_type:${typeof value}`, `${schema.schemaName}.verifyValue`) + schemaError( + `field_does_not_support_value_type:${valueTypeLabel(value)}`, + `${schema.schemaName}.verifyValue` + ) ); expect(result.ok()).toBe(false); }); @@ -451,7 +473,7 @@ describe('schemaVerify', () => { describe('string', () => { it(`should return value when type is 'string' and value is empty string`, async () => { - const result = await schema.verifyValue('string', EMPTY_STRING); + const result = await schema.verifyValue('string', EMPTY_STRING, base); expect(result.errorCode()).toBe(EMPTY_STRING); expect(result.data).toBe(EMPTY_STRING); @@ -460,7 +482,7 @@ describe('schemaVerify', () => { it(`should validate successfully and return value when type is 'string' and value is a single char`, async () => { const value = 'a'; - const result = await schema.verifyValue('string', value); + const result = await schema.verifyValue('string', value, base); expect(result.data).toBe(value); expect(result.errorCode()).toBe(EMPTY_STRING); @@ -469,7 +491,7 @@ describe('schemaVerify', () => { it(`should return true when type is 'string' and value is a short string`, async () => { const value = '19714-9194714'; - const result = await schema.verifyValue('string', value); + const result = await schema.verifyValue('string', value, base); expect(result.errorCode()).toBe(EMPTY_STRING); expect(result.data).toBe(value); @@ -478,61 +500,79 @@ describe('schemaVerify', () => { it(`should fail when type is 'string' and value is undefined`, async () => { const value = undefined; - const result = await schema.verifyValue('string', value); + const result = await schema.verifyValue('string', value, base); expect(result.errorCode()).toBe( - schemaError(`unsupported_type:${typeof value}`, `${schema.schemaName}.verifyValue`) + schemaError( + `field_does_not_support_value_type:${valueTypeLabel(value)}`, + `${schema.schemaName}.verifyValue` + ) ); expect(result.ok()).toBe(false); }); it(`should fail when type is 'string' and value is null`, async () => { const value = null; - const result = await schema.verifyValue('string', value); + const result = await schema.verifyValue('string', value, base); expect(result.errorCode()).toBe( - schemaError(`unsupported_type:${typeof value}`, `${schema.schemaName}.verifyValue`) + schemaError( + `field_does_not_support_value_type:${valueTypeLabel(value)}`, + `${schema.schemaName}.verifyValue` + ) ); expect(result.ok()).toBe(false); }); it(`should fail when type is 'string' and value is 0`, async () => { const value = 0; - const result = await schema.verifyValue('string', value); + const result = await schema.verifyValue('string', value, base); expect(result.errorCode()).toBe( - schemaError(`unsupported_type:${typeof value}`, `${schema.schemaName}.verifyValue`) + schemaError( + `field_does_not_support_value_type:${valueTypeLabel(value)}`, + `${schema.schemaName}.verifyValue` + ) ); expect(result.ok()).toBe(false); }); it(`should fail when type is 'string' and value is 1`, async () => { const value = 1; - const result = await schema.verifyValue('string', value); + const result = await schema.verifyValue('string', value, base); expect(result.ok()).toBe(false); expect(result.errorCode()).toBe( - schemaError(`unsupported_type:${typeof value}`, `${schema.schemaName}.verifyValue`) + schemaError( + `field_does_not_support_value_type:${valueTypeLabel(value)}`, + `${schema.schemaName}.verifyValue` + ) ); }); it(`should fail when type is 'string' and value is an empty array`, async () => { const value = [] as unknown[]; - const result = await schema.verifyValue('string', value); + const result = await schema.verifyValue('string', value, base); expect(result.ok()).toBe(false); expect(result.errorCode()).toBe( - schemaError(`unsupported_type:${typeof value}`, `${schema.schemaName}.verifyValue`) + schemaError( + `field_does_not_support_value_type:${valueTypeLabel(value)}`, + `${schema.schemaName}.verifyValue` + ) ); }); it(`should fail when type is 'string' and value is an empty object`, async () => { const value = {}; - const result = await schema.verifyValue('string', value); + const result = await schema.verifyValue('string', value, base); expect(result.ok()).toBe(false); expect(result.errorCode()).toBe( - schemaError(`unsupported_type:${typeof value}`, `${schema.schemaName}.verifyValue`) + schemaError( + `field_does_not_support_value_type:${valueTypeLabel(value)}`, + `${schema.schemaName}.verifyValue` + ) ); }); }); @@ -540,7 +580,7 @@ describe('schemaVerify', () => { describe('bigint', () => { it(`should succeed and return value when type is 'bigint' and value is a BigInt of value 0`, async () => { const value = BigInt('0'); - const result = await schema.verifyValue('bigint', value); + const result = await schema.verifyValue('bigint', value, base); expect(result.errorCode()).toBe(EMPTY_STRING); expect(result.data).toBe(value); @@ -549,7 +589,7 @@ describe('schemaVerify', () => { it(`should succeed and return value when type is 'bigint' and value is a BigInt of value 1`, async () => { const value = BigInt('1'); - const result = await schema.verifyValue('bigint', value); + const result = await schema.verifyValue('bigint', value, base); expect(result.data).toBe(value); expect(result.errorCode()).toBe(EMPTY_STRING); @@ -558,7 +598,7 @@ describe('schemaVerify', () => { it(`should succeed and return value when type is 'bigint' and value is a BigInt with a large number`, async () => { const value = BigInt('9007199254740991'); - const result = await schema.verifyValue('bigint', value); + const result = await schema.verifyValue('bigint', value, base); expect(result.data).toBe(value); expect(result.errorCode()).toBe(EMPTY_STRING); @@ -567,7 +607,7 @@ describe('schemaVerify', () => { it(`should succeed and return value when type is 'bigint' and value is a BigInt with a huge octal`, async () => { const value = BigInt('0o377777777777777777'); - const result = await schema.verifyValue('bigint', value); + const result = await schema.verifyValue('bigint', value, base); expect(result.errorCode()).toBe(EMPTY_STRING); expect(result.data).toStrictEqual(value); @@ -576,60 +616,75 @@ describe('schemaVerify', () => { it(`should fail when type is 'bigint' and value is undefined`, async () => { const value = undefined; - const result = await schema.verifyValue('bigint', value); + const result = await schema.verifyValue('bigint', value, base); expect(result.errorCode()).toBe( - schemaError(`unsupported_type:${typeof value}`, `${schema.schemaName}.verifyValue`) + schemaError( + `field_does_not_support_value_type:undefined`, + `${schema.schemaName}.verifyValue` + ) ); expect(result.ok()).toBe(false); }); it(`should fail when type is 'bigint' and value is null`, async () => { const value = null; - const result = await schema.verifyValue('bigint', value); + const result = await schema.verifyValue('bigint', value, base); expect(result.errorCode()).toBe( - schemaError(`unsupported_type:${typeof value}`, `${schema.schemaName}.verifyValue`) + schemaError(`field_does_not_support_value_type:null`, `${schema.schemaName}.verifyValue`) ); expect(result.ok()).toBe(false); }); it(`should fail when type is 'bigint' and value is 0`, async () => { const value = 0; - const result = await schema.verifyValue('bigint', value); + const result = await schema.verifyValue('bigint', value, base); expect(result.errorCode()).toBe( - schemaError(`unsupported_type:${typeof value}`, `${schema.schemaName}.verifyValue`) + schemaError( + `field_does_not_support_value_type:${valueTypeLabel(value)}`, + `${schema.schemaName}.verifyValue` + ) ); expect(result.ok()).toBe(false); }); it(`should fail when type is 'bigint' and value is 1`, async () => { const value = 1; - const result = await schema.verifyValue('bigint', value); + const result = await schema.verifyValue('bigint', value, base); expect(result.errorCode()).toBe( - schemaError(`unsupported_type:${typeof value}`, `${schema.schemaName}.verifyValue`) + schemaError( + `field_does_not_support_value_type:${valueTypeLabel(value)}`, + `${schema.schemaName}.verifyValue` + ) ); expect(result.ok()).toBe(false); }); it(`should fail when type is 'bigint' and value is an empty array`, async () => { const value: string[] = []; - const result = await schema.verifyValue('bigint', value); + const result = await schema.verifyValue('bigint', value, base); expect(result.errorCode()).toBe( - schemaError(`unsupported_type:${typeof value}`, `${schema.schemaName}.verifyValue`) + schemaError( + `field_does_not_support_value_type:${valueTypeLabel(value)}`, + `${schema.schemaName}.verifyValue` + ) ); expect(result.ok()).toBe(false); }); it(`should fail when type is 'bigint' and value is an empty object`, async () => { const value = {}; - const result = await schema.verifyValue('bigint', value); + const result = await schema.verifyValue('bigint', value, base); expect(result.errorCode()).toBe( - schemaError(`unsupported_type:${typeof value}`, `${schema.schemaName}.verifyValue`) + schemaError( + `field_does_not_support_value_type:${valueTypeLabel(value)}`, + `${schema.schemaName}.verifyValue` + ) ); expect(result.ok()).toBe(false); }); @@ -638,7 +693,7 @@ describe('schemaVerify', () => { describe('undefined', () => { it(`should succeed when type is 'undefined' when value is undefined`, async () => { const value = undefined; - const result = await schema.verifyValue('undefined', value); + const result = await schema.verifyValue('undefined', value, base); expect(result.errorCode()).toBe(EMPTY_STRING); expect(result.ok()).toBe(true); @@ -646,69 +701,90 @@ describe('schemaVerify', () => { it(`should fail when type is 'undefined' with null value`, async () => { const value = null; - const result = await schema.verifyValue('undefined', value); + const result = await schema.verifyValue('undefined', value, base); expect(result.errorCode()).toBe( - schemaError(`unsupported_type:${typeof value}`, `${schema.schemaName}.verifyValue`) + schemaError( + `field_does_not_support_value_type:${valueTypeLabel(value)}`, + `${schema.schemaName}.verifyValue` + ) ); expect(result.ok()).toBe(false); }); it(`should fail when type is 'undefined' with value 'undefined'`, async () => { const value = 'undefined'; - const result = await schema.verifyValue('undefined', value); + const result = await schema.verifyValue('undefined', value, base); expect(result.errorCode()).toBe( - schemaError(`unsupported_type:${typeof value}`, `${schema.schemaName}.verifyValue`) + schemaError( + `field_does_not_support_value_type:${valueTypeLabel(value)}`, + `${schema.schemaName}.verifyValue` + ) ); expect(result.ok()).toBe(false); }); it(`should fail when type is 'undefined' with value 'null'`, async () => { const value = 'null'; - const result = await schema.verifyValue('undefined', value); + const result = await schema.verifyValue('undefined', value, base); expect(result.errorCode()).toBe( - schemaError(`unsupported_type:${typeof value}`, `${schema.schemaName}.verifyValue`) + schemaError( + `field_does_not_support_value_type:${valueTypeLabel(value)}`, + `${schema.schemaName}.verifyValue` + ) ); expect(result.ok()).toBe(false); }); it(`should fail when type is 'undefined' with value is 0`, async () => { const value = 0; - const result = await schema.verifyValue('undefined', value); + const result = await schema.verifyValue('undefined', value, base); expect(result.errorCode()).toBe( - schemaError(`unsupported_type:${typeof value}`, `${schema.schemaName}.verifyValue`) + schemaError( + `field_does_not_support_value_type:${valueTypeLabel(value)}`, + `${schema.schemaName}.verifyValue` + ) ); expect(result.ok()).toBe(false); }); it(`should fail when type is 'undefined' and value is EMPTY_STRING`, async () => { const value = EMPTY_STRING; - const result = await schema.verifyValue('undefined', value); + const result = await schema.verifyValue('undefined', value, base); expect(result.errorCode()).toBe( - schemaError(`unsupported_type:${typeof value}`, `${schema.schemaName}.verifyValue`) + schemaError( + `field_does_not_support_value_type:${valueTypeLabel(value)}`, + `${schema.schemaName}.verifyValue` + ) ); expect(result.ok()).toBe(false); }); - it(`should fail when type is 'undefined' with value is true`, async () => { + it(`should fail when type is 'undefined' with true value`, async () => { const value = true; - const result = await schema.verifyValue('undefined', value); + const result = await schema.verifyValue('undefined', value, base); expect(result.errorCode()).toBe( - schemaError(`unsupported_type:${typeof value}`, `${schema.schemaName}.verifyValue`) + schemaError( + `field_does_not_support_value_type:${valueTypeLabel(value)}`, + `${schema.schemaName}.verifyValue` + ) ); expect(result.ok()).toBe(false); }); it(`should fail when type is 'undefined' with value is false`, async () => { const value = false; - const result = await schema.verifyValue('undefined', value); + const result = await schema.verifyValue('undefined', value, base); expect(result.errorCode()).toBe( - schemaError(`unsupported_type:${typeof value}`, `${schema.schemaName}.verifyValue`) + schemaError( + `field_does_not_support_value_type:${valueTypeLabel(value)}`, + `${schema.schemaName}.verifyValue` + ) ); expect(result.ok()).toBe(false); }); @@ -717,7 +793,7 @@ describe('schemaVerify', () => { describe('uint', () => { it(`should succeed and return value when type is 'uint' and value is 0`, async () => { const value = 0; - const result = await schema.verifyValue('uint', value); + const result = await schema.verifyValue('uint', value, base); expect(result.errorCode()).toBe(EMPTY_STRING); expect(result.data).toBe(value); @@ -726,7 +802,7 @@ describe('schemaVerify', () => { it(`should succeed and return value when type is 'uint' and value is 1`, async () => { const value = 1; - const result = await schema.verifyValue('uint', value); + const result = await schema.verifyValue('uint', value, base); expect(result.errorCode()).toBe(EMPTY_STRING); expect(result.data).toBe(value); @@ -735,7 +811,7 @@ describe('schemaVerify', () => { it(`should succeed and return value when type is 'uint' and value is 100`, async () => { const value = 100; - const result = await schema.verifyValue('uint', value); + const result = await schema.verifyValue('uint', value, base); expect(result.errorCode()).toBe(EMPTY_STRING); expect(result.data).toBe(value); @@ -744,7 +820,7 @@ describe('schemaVerify', () => { it(`should succeed and return value when type is 'uint' and value is MAX_SAFE_INTEGER`, async () => { const value = Number.MAX_SAFE_INTEGER; - const result = await schema.verifyValue('uint', value); + const result = await schema.verifyValue('uint', value, base); expect(result.errorCode()).toBe(EMPTY_STRING); expect(result.data).toBe(value); @@ -753,20 +829,26 @@ describe('schemaVerify', () => { it(`should fail when type is 'uint' and value is -1`, async () => { const value = -1; - const result = await schema.verifyValue('uint', value); + const result = await schema.verifyValue('uint', value, base); expect(result.errorCode()).toBe( - schemaError(`unsupported_type:${typeof value}`, `${schema.schemaName}.verifyValue`) + schemaError( + `field_does_not_support_value_type:${valueTypeLabel(value)}`, + `${schema.schemaName}.verifyValue` + ) ); expect(result.ok()).toBe(false); }); it(`should fail when type is 'uint' and value is 1.1`, async () => { const value = 1.1; - const result = await schema.verifyValue('uint', value); + const result = await schema.verifyValue('uint', value, base); expect(result.errorCode()).toBe( - schemaError(`unsupported_type:${typeof value}`, `${schema.schemaName}.verifyValue`) + schemaError( + `field_does_not_support_value_type:${valueTypeLabel(value)}`, + `${schema.schemaName}.verifyValue` + ) ); expect(result.ok()).toBe(false); }); @@ -775,7 +857,7 @@ describe('schemaVerify', () => { describe('null', () => { it(`should succeed when value is null and null is allowed`, async () => { const value = null; - const result = await schema.verifyValue('null', value); + const result = await schema.verifyValue('null', value, base); expect(result.errorCode()).toBe(EMPTY_STRING); expect(result.data).toBeNull(); @@ -784,60 +866,78 @@ describe('schemaVerify', () => { it(`should fail when type is 'undefined' with value 'null'`, async () => { const value = 'null'; - const result = await schema.verifyValue('undefined', 'null'); + const result = await schema.verifyValue('undefined', 'null', base); expect(result.errorCode()).toBe( - schemaError(`unsupported_type:${typeof value}`, `${schema.schemaName}.verifyValue`) + schemaError( + `field_does_not_support_value_type:${valueTypeLabel(value)}`, + `${schema.schemaName}.verifyValue` + ) ); expect(result.ok()).toBe(false); }); it(`should fail when type is 'null' with value is undefined`, async () => { const value = undefined; - const result = await schema.verifyValue('null', value); + const result = await schema.verifyValue('null', value, base); expect(result.errorCode()).toBe( - schemaError(`unsupported_type:${typeof value}`, `${schema.schemaName}.verifyValue`) + schemaError( + `field_does_not_support_value_type:${valueTypeLabel(value)}`, + `${schema.schemaName}.verifyValue` + ) ); expect(result.ok()).toBe(false); }); it(`should fail when type is 'null' with value 0`, async () => { const value = 0; - const result = await schema.verifyValue('null', value); + const result = await schema.verifyValue('null', value, base); expect(result.errorCode()).toBe( - schemaError(`unsupported_type:${typeof value}`, `${schema.schemaName}.verifyValue`) + schemaError( + `field_does_not_support_value_type:${valueTypeLabel(value)}`, + `${schema.schemaName}.verifyValue` + ) ); expect(result.ok()).toBe(false); }); it(`should fail when type is 'null' with value is EMPTY_STRING`, async () => { const value = EMPTY_STRING; - const result = await schema.verifyValue('null', value); + const result = await schema.verifyValue('null', value, base); expect(result.errorCode()).toBe( - schemaError(`unsupported_type:${typeof value}`, `${schema.schemaName}.verifyValue`) + schemaError( + `field_does_not_support_value_type:${valueTypeLabel(value)}`, + `${schema.schemaName}.verifyValue` + ) ); expect(result.ok()).toBe(false); }); it(`should fail when type is 'null' with value is true`, async () => { const value = true; - const result = await schema.verifyValue('null', value); + const result = await schema.verifyValue('null', value, base); expect(result.errorCode()).toBe( - schemaError(`unsupported_type:${typeof value}`, `${schema.schemaName}.verifyValue`) + schemaError( + `field_does_not_support_value_type:${valueTypeLabel(value)}`, + `${schema.schemaName}.verifyValue` + ) ); expect(result.ok()).toBe(false); }); it(`should fail when type is 'null' with value is false`, async () => { const value = false; - const result = await schema.verifyValue('null', value); + const result = await schema.verifyValue('null', value, base); expect(result.errorCode()).toBe( - schemaError(`unsupported_type:${typeof value}`, `${schema.schemaName}.verifyValue`) + schemaError( + `field_does_not_support_value_type:${valueTypeLabel(value)}`, + `${schema.schemaName}.verifyValue` + ) ); expect(result.ok()).toBe(false); }); @@ -846,7 +946,7 @@ describe('schemaVerify', () => { describe('number', () => { it(`should succeed and return value when type is 'number' with value -10`, async () => { const value = -10; - const result = await schema.verifyValue('number', value); + const result = await schema.verifyValue('number', value, base); expect(result.errorCode()).toBe(EMPTY_STRING); expect(result.data).toBe(value); @@ -855,7 +955,7 @@ describe('schemaVerify', () => { it(`should succeed and return value when type is 'number' with value -0`, async () => { const value = -0; - const result = await schema.verifyValue('number', value); + const result = await schema.verifyValue('number', value, base); expect(result.errorCode()).toBe(EMPTY_STRING); expect(result.data).toBe(value); @@ -864,7 +964,7 @@ describe('schemaVerify', () => { it(`should succeed and return value when type is 'number' with value 0`, async () => { const value = 0; - const result = await schema.verifyValue('number', value); + const result = await schema.verifyValue('number', value, base); expect(result.errorCode()).toBe(EMPTY_STRING); expect(result.data).toBe(value); @@ -873,7 +973,7 @@ describe('schemaVerify', () => { it(`should succeed and return value when type is 'number' with value 1`, async () => { const value = 1; - const result = await schema.verifyValue('number', value); + const result = await schema.verifyValue('number', value, base); expect(result.errorCode()).toBe(EMPTY_STRING); expect(result.data).toBe(value); @@ -882,7 +982,7 @@ describe('schemaVerify', () => { it(`should succeed and return value when type is 'number' with max safe int value`, async () => { const value = Number.MAX_SAFE_INTEGER; - const result = await schema.verifyValue('number', value); + const result = await schema.verifyValue('number', value, base); expect(result.errorCode()).toBe(EMPTY_STRING); expect(result.data).toBe(value); @@ -891,7 +991,7 @@ describe('schemaVerify', () => { it(`should succeed and return value when type is 'number' with min safe int value`, async () => { const value = Number.MIN_SAFE_INTEGER; - const result = await schema.verifyValue('number', value); + const result = await schema.verifyValue('number', value, base); expect(result.errorCode()).toBe(EMPTY_STRING); expect(result.data).toBe(value); @@ -900,7 +1000,7 @@ describe('schemaVerify', () => { it(`should succeed and return value when type is 'number' with min value`, async () => { const value = Number.MIN_VALUE; - const result = await schema.verifyValue('number', value); + const result = await schema.verifyValue('number', value, base); expect(result.errorCode()).toBe(EMPTY_STRING); expect(result.data).toBe(value); @@ -909,7 +1009,7 @@ describe('schemaVerify', () => { it(`should succeed and return value when type is 'number' with max value`, async () => { const value = Number.MAX_VALUE; - const result = await schema.verifyValue('number', value); + const result = await schema.verifyValue('number', value, base); expect(result.errorCode()).toBe(EMPTY_STRING); expect(result.data).toBe(value); @@ -918,7 +1018,7 @@ describe('schemaVerify', () => { it(`should succeed and return value when type is 'number' with epsilon value`, async () => { const value = Number.EPSILON; - const result = await schema.verifyValue('number', value); + const result = await schema.verifyValue('number', value, base); expect(result.errorCode()).toBe(EMPTY_STRING); expect(result.data).toBe(value); @@ -927,60 +1027,78 @@ describe('schemaVerify', () => { it(`should fail when type is 'number' with NaN value`, async () => { const value = Number.NaN; - const result = await schema.verifyValue('number', value); + const result = await schema.verifyValue('number', value, base); expect(result.errorCode()).toBe( - schemaError(`unsupported_type:${typeof value}`, `${schema.schemaName}.verifyValue`) + schemaError( + `field_does_not_support_value_type:${valueTypeLabel(value)}`, + `${schema.schemaName}.verifyValue` + ) ); expect(result.ok()).toBe(false); }); it(`should fail when type is 'number' with string value '0'`, async () => { const value = '0'; - const result = await schema.verifyValue('number', value); + const result = await schema.verifyValue('number', value, base); expect(result.errorCode()).toBe( - schemaError(`unsupported_type:${typeof value}`, `${schema.schemaName}.verifyValue`) + schemaError( + `field_does_not_support_value_type:${valueTypeLabel(value)}`, + `${schema.schemaName}.verifyValue` + ) ); expect(result.ok()).toBe(false); }); it(`should fail when type is 'number' with string value '0.00000000'`, async () => { const value = '0.00000000'; - const result = await schema.verifyValue('number', value); + const result = await schema.verifyValue('number', value, base); expect(result.errorCode()).toBe( - schemaError(`unsupported_type:${typeof value}`, `${schema.schemaName}.verifyValue`) + schemaError( + `field_does_not_support_value_type:${valueTypeLabel(value)}`, + `${schema.schemaName}.verifyValue` + ) ); expect(result.ok()).toBe(false); }); it(`should fail when type is 'number' with string value '1'`, async () => { const value = '1'; - const result = await schema.verifyValue('number', value); + const result = await schema.verifyValue('number', value, base); expect(result.errorCode()).toBe( - schemaError(`unsupported_type:${typeof value}`, `${schema.schemaName}.verifyValue`) + schemaError( + `field_does_not_support_value_type:${valueTypeLabel(value)}`, + `${schema.schemaName}.verifyValue` + ) ); expect(result.ok()).toBe(false); }); it(`should fail when type is 'number' with positive infinite value`, async () => { const value = Number.POSITIVE_INFINITY; - const result = await schema.verifyValue('number', value); + const result = await schema.verifyValue('number', value, base); expect(result.errorCode()).toBe( - schemaError(`unsupported_type:${typeof value}`, `${schema.schemaName}.verifyValue`) + schemaError( + `field_does_not_support_value_type:${valueTypeLabel(value)}`, + `${schema.schemaName}.verifyValue` + ) ); expect(result.ok()).toBe(false); }); it(`should fail when type is 'number' with negative infinite value`, async () => { const value = Number.NEGATIVE_INFINITY; - const result = await schema.verifyValue('number', value); + const result = await schema.verifyValue('number', value, base); expect(result.errorCode()).toBe( - schemaError(`unsupported_type:${typeof value}`, `${schema.schemaName}.verifyValue`) + schemaError( + `field_does_not_support_value_type:${valueTypeLabel(value)}`, + `${schema.schemaName}.verifyValue` + ) ); expect(result.ok()).toBe(false); }); @@ -1066,5 +1184,54 @@ describe('schemaVerify', () => { expect(result.ok()).toBe(false); }); }); + + describe('Recursive Verification', () => { + let subA: SampleSchemaSubA; + let subB: SampleSchemaSubB; + let bData: SampleBData; + let aData: SampleAData; + + beforeAll(() => { + subB = new SampleSchemaSubB(base); + subA = new SampleSchemaSubA(subB, base); + }); + + beforeEach(() => { + bData = { + str2b: 'bbb', + int2b: 111 + }; + + aData = { + int1a: 31, + str1a: 'aaa', + subValue: { + str2b: 'zzzzz', + int2b: 9999 + } + }; + }); + + it(`should recursively verify all properties`, async () => { + const result = await schemaSubA.verify(aData, base); + + expect(result.errorCode()).toBe(EMPTY_STRING); + expect(result.ok()).toBe(true); + }); + + it(`should fail when value in sub-schema field is not supported`, async () => { + aData.subValue.int2b = 'aaaa' as any; + const int2b = schemaSubB.fields.get('int2b'); + int2b!.types.length = 0; + int2b?.types.push('number'); + const result = await schemaSubA.verify(aData, base); + + base.debug(`aData: ${JSON.stringify(aData)}`); + expect(result.errorCode()).toBe( + schemaError('field_does_not_support_type:aa', `SampleSchemaSubA.subValue`) + ); + expect(result.ok()).toBe(false); + }); + }); }); });