Skip to content

Commit

Permalink
Integrating custom verifiers and custom schemas both into same custom…
Browse files Browse the repository at this point in the history
…Types registry per schema.
  • Loading branch information
mbrich committed May 9, 2024
1 parent 99d2f52 commit 5d232fd
Show file tree
Hide file tree
Showing 11 changed files with 187 additions and 180 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@
"@typescript-eslint/typescript-estree": "^7.5.0",
"@typhonjs-typedoc/typedoc-pkg": "^0.0.5",
"big.js": "^6.2.1",
"eslint": "^9.2.0",
"eslint-config-prettier": "^9.1.0",
"eslint": "^8.57.0",
"eslint-config-prettier": "^8.10.0",
"eslint-plugin-prettier": "^5.1.3",
"gulp": "^4.0.2",
"jest": "^29.7.0",
Expand Down
31 changes: 31 additions & 0 deletions src/custom/type/verifier.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
* MIT License
*
* Copyright (c) 2019 - 2024 Toreda, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/

import {Fate} from '@toreda/fate';

/**
* @category Schemas - Custom Types
*/
export type CustomTypeVerifier<VerifiedT> = (value?: unknown | null) => Promise<Fate<VerifiedT>>;
72 changes: 66 additions & 6 deletions src/custom/schemas.ts → src/custom/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,24 +26,26 @@
import {Log} from '@toreda/log';
import {Schema} from '../schema';
import {type SchemaData} from '../schema/data';
import {type CustomSchemasInit} from './schemas/init';
import {type CustomSchemasData} from './schemas/data';
import {type CustomTypesInit} from './types/init';
import {type CustomTypesData} from './types/data';
import {CustomTypeVerifier} from './type/verifier';
import {Fate} from '@toreda/fate';

/**
* @category Schemas
* @category Schemas - Custom Types
*/
export class CustomSchemas {
export class CustomTypes<DataT, InputT extends SchemaData<DataT>, VerifiedT = InputT> {
public readonly log: Log;
public readonly registered: Map<string, Schema<unknown, SchemaData<unknown>>>;

constructor(init: CustomSchemasInit) {
constructor(init: CustomTypesInit) {
this.registered = new Map<string, Schema<unknown, SchemaData<unknown>>>();
this.log = init.base.makeLog('schemaTypes');

this.registerTypes(init.data);
}

public registerTypes(data?: CustomSchemasData): void {
public registerTypes(data?: CustomTypesData): void {
if (!data) {
return;
}
Expand All @@ -66,6 +68,26 @@ export class CustomSchemas {
return this.registered.has(id);
}

public hasSchema(id: string): boolean {
if (!this.has(id)) {
return false;
}

const o = this.registered.get(id);

return typeof o?.verify === 'function';
}

public hasVerifier(id: string): boolean {
if (!this.has(id)) {
return false;
}

const o = this.registered.get(id);

return typeof o === 'function';
}

public register(id: string, schema: Schema<unknown, SchemaData<unknown>>): boolean {
if (typeof id !== 'string') {
return false;
Expand All @@ -92,4 +114,42 @@ export class CustomSchemas {

return schema ? schema : null;
}

public getVerifier(id: string): CustomTypeVerifier<DataT> | null {
if (typeof id !== 'string') {
return null;
}

const o = this.registered.get(id);

return typeof o === 'function' ? o : null;
}

public getSchema(id: string): Schema<unknown, SchemaData<unknown>> | null {
if (typeof id !== 'string') {
return null;
}

const schema = this.registered.get(id);
if (typeof schema?.verify !== 'function') {
return null;
}

return schema;
}

public async verifyValue(type: string, value: unknown): Promise<Fate<DataT>> {
const fate = new Fate<DataT>();

return fate;
}

public async verifySchema(
type: string,
value: unknown | SchemaData<unknown>
): Promise<Fate<SchemaData<unknown>>> {
const fate = new Fate<SchemaData<unknown>>();

return fate;
}
}
8 changes: 4 additions & 4 deletions src/custom/schemas/data.ts → src/custom/types/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, mezzwrge, publish, distribute, sublicense, and/or sell
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
Expand All @@ -27,8 +27,8 @@ import {Schema} from '../../schema';
import {type SchemaData} from '../../schema/data';

/**
* @category Schemas
* @category Schemas - Custom Types
*/
export interface CustomSchemasData {
[k: string]: Schema<unknown, SchemaData<unknown>>;
export interface CustomTypesData {
[k: string]: Schema<any, any, any>;
}
8 changes: 4 additions & 4 deletions src/custom/schemas/init.ts → src/custom/types/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,12 @@
*/

import {Log} from '@toreda/log';
import {type CustomSchemasData} from './data';
import {type CustomTypesData} from './data';

/**
* @category Custom Types
* @category Schemas - Custom Types
*/
export interface CustomSchemasInit {
data?: CustomSchemasData;
export interface CustomTypesInit {
data?: CustomTypesData;
base: Log;
}
6 changes: 3 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ export {booleanVerify} from './boolean/verify';
export {Code} from './code';
export {CodeDefaults} from './code/defaults';
export {Codes} from './codes';
export {CustomSchemas} from './custom/schemas';
export {CustomSchemasData} from './custom/schemas/data';
export {CustomSchemasInit} from './custom/schemas/init';
export {CustomTypes} from './custom/types';
export {CustomTypesData} from './custom/types/data';
export {CustomTypesInit} from './custom/types/init';
export {divisible} from './divisible';
export {empty} from './empty';
export {emptyArrayVerify} from './empty/array/verify';
Expand Down
56 changes: 14 additions & 42 deletions src/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ import {isDbl, isFloat, isUrl} from '@toreda/strong-types';
import {isUInt} from './is/uint';
import {isInt} from './is/int';
import {type SchemaFieldData} from './schema/field/data';
import {CustomSchemas} from './custom/schemas';
import {CustomTypes} from './custom/types';
import {schemaBuiltIns} from './schema/built/ins';

/**
Expand All @@ -48,7 +48,7 @@ export class Schema<DataT, InputT extends SchemaData<DataT>, VerifiedT = InputT>
public readonly fields: Map<keyof InputT, SchemaField<InputT>>;
public readonly cfg: SchemaConfig;
public readonly outputTransform: SchemaOutputTransformer<DataT, VerifiedT | null>;
public readonly customSchemas: CustomSchemas;
public readonly customTypes: CustomTypes<DataT, InputT, VerifiedT>;
public readonly base: Log;

constructor(init: SchemaInit<DataT | null, InputT, VerifiedT>) {
Expand All @@ -57,8 +57,8 @@ export class Schema<DataT, InputT extends SchemaData<DataT>, VerifiedT = InputT>
this.cfg = new SchemaConfig(init.options);

this.base = init.base.makeLog(`schema___${init.name}`);
this.customSchemas = new CustomSchemas({
data: init.customSchemas,
this.customTypes = new CustomTypes<DataT, InputT, VerifiedT>({
data: init.customTypes,
base: this.base
});
this.outputTransform = init.outputTransform ? init.outputTransform : simpleOutputTransform;
Expand Down Expand Up @@ -139,12 +139,6 @@ export class Schema<DataT, InputT extends SchemaData<DataT>, VerifiedT = InputT>
);
}

public customTypeSupported(type: SchemaFieldType, _value: unknown): _value is SchemaData<unknown> {
const custom = this.customSchemas.get(type);

return custom !== null && custom !== undefined;
}

public isBuiltIn(type: SchemaFieldType): boolean {
if (typeof type !== 'string') {
return false;
Expand All @@ -158,7 +152,7 @@ export class Schema<DataT, InputT extends SchemaData<DataT>, VerifiedT = InputT>
return true;
}

return this.customSchemas.has(type);
return this.customTypes.has(type);
}
/**
* Check if `type` is supported by the schema. Doesn't check if value actuallyz
Expand Down Expand Up @@ -201,28 +195,6 @@ export class Schema<DataT, InputT extends SchemaData<DataT>, VerifiedT = InputT>
}
}

public async verifySchemaData(
type: SchemaFieldType,
value: SchemaData<unknown>
): Promise<Fate<SchemaData<unknown> | null>> {
const fate = new Fate<SchemaData<unknown> | null>({
data: null
});

const custom = this.customSchemas.get(type);
if (!custom) {
return fate.setErrorCode(`unsupported_type:${type?.toString()}`);
}

const result = await custom.verify(value as SchemaData<unknown>, this.base);
if (!result.ok()) {
return fate.setErrorCode(`bad_schema_property:${result.errorCode()}`);
}

fate.data = result.data;
return fate.setSuccess(true);
}

/**
* Check if value's content matches `type`. Some types are primitives verified by type checks.
* Others require more in depth validation, like URLs or Schema Data.
Expand All @@ -231,32 +203,32 @@ export class Schema<DataT, InputT extends SchemaData<DataT>, VerifiedT = InputT>
*/
public async verifyValue(
type: SchemaFieldType,
value: unknown
value: unknown | SchemaData<unknown>
): Promise<Fate<DataT | SchemaData<unknown>>> {
const fate = new Fate<DataT | SchemaData<unknown>>();

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.customTypeSupported(type, value)) {
if (!this.customTypes.has(type)) {
return fate.setErrorCode(
schemaError(`unsupported_type:${typeof value}`, `${this.schemaName}.verifyValue`)
);
}

const custom = this.customSchemas.get(type);
if (!custom) {
return fate.setErrorCode(`field_does_not_support_custom_type:${type?.toString()}`);
if (this.customTypes.hasSchema(type)) {
return this.customTypes.verifySchema(type, value);
}

const result = await custom.verify(value, this.base);
if (!result.data) {
return fate.setErrorCode(`schema_verify_returned_no_data:${type?.toString()}`);
//const custom = await this.customTypes.verify(type, value, this.base);
if (this.customTypes.hasVerifier(type)) {
return this.customTypes.verifyValue(type, value);
}

return result;
return fate.setErrorCode(`field_cant_verify_custom_type:${type?.toString()}`);
}

public async verify(data: SchemaData<DataT>, base: Log): Promise<Fate<VerifiedT>> {
Expand Down
2 changes: 0 additions & 2 deletions src/schema/field/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ export const schemaFieldTypes: SchemaFieldType[] = [
'array',
'bigint',
'boolean',
'custom',
'datetime',
'dbl',
'double',
Expand All @@ -46,7 +45,6 @@ export const schemaFieldTypes: SchemaFieldType[] = [
'null',
'number',
'real',
'schema',
'string',
'time',
'uint',
Expand Down
4 changes: 2 additions & 2 deletions src/schema/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import {type SchemaFieldData} from './field/data';
import type {SchemaOptions} from './options';
import {type SchemaOutputTransformer} from './output/transformer';
import {Log} from '@toreda/log';
import {type CustomSchemasData} from '../custom/schemas/data';
import {type CustomTypesData} from '../custom/types/data';

/**
* @name Schema Init
Expand All @@ -51,6 +51,6 @@ export interface SchemaInit<DataT, InputT, OutputT = InputT> {
*/
options?: SchemaOptions;
outputTransform?: SchemaOutputTransformer<DataT, OutputT>;
customSchemas?: CustomSchemasData;
customTypes?: CustomTypesData;
base: Log;
}
Loading

0 comments on commit 5d232fd

Please sign in to comment.