Skip to content

Commit

Permalink
Tests passing and now reporting the correct location as the verifier …
Browse files Browse the repository at this point in the history
…visits each node in the schema data.
  • Loading branch information
mbrich committed May 16, 2024
1 parent 683eae6 commit 6b2de52
Show file tree
Hide file tree
Showing 10 changed files with 242 additions and 165 deletions.
33 changes: 33 additions & 0 deletions src/custom/schema/verify.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/**
* 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 {type SchemaVerifyInit} from '../../schema/verify/init';

/**
* @category Schemas - Custom Types
*/
export interface CustomSchemaVerify extends SchemaVerifyInit {
type: string;
}
22 changes: 7 additions & 15 deletions src/custom/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ import {type CustomTypeVerifier} from './type/verifier';
import {Fate} from '@toreda/fate';
import {schemaError} from '../schema/error';
import {SchemaPath} from '../schema/path';

Check warning on line 34 in src/custom/types.ts

View workflow job for this annotation

GitHub Actions / build_project (20.x)

'SchemaPath' is defined but never used. Allowed unused vars must match /^_/u
import {SchemaVerifyInit} from '../schema/verify/init';

Check warning on line 35 in src/custom/types.ts

View workflow job for this annotation

GitHub Actions / build_project (20.x)

'SchemaVerifyInit' is defined but never used. Allowed unused vars must match /^_/u
import {CustomSchemaVerify} from './schema/verify';

/**
* @category Schemas - Custom Types
Expand Down Expand Up @@ -130,7 +132,7 @@ export class CustomTypes<DataT, InputT extends SchemaData<DataT>, VerifiedT = In
return typeof o === 'function' ? o : null;
}

public getSchema(id: string): Schema<unknown, SchemaData<unknown>> | null {
public getSchema(id?: string): Schema<unknown, SchemaData<unknown>> | null {
if (typeof id !== 'string') {
return null;
}
Expand All @@ -150,24 +152,14 @@ export class CustomTypes<DataT, InputT extends SchemaData<DataT>, VerifiedT = In
return fate;
}

public async verifySchema(
id: string,
type: string,
value: SchemaData<DataT>,
path: SchemaPath,
base: Log
): Promise<Fate<SchemaData<unknown>>> {
public async verify(init: CustomSchemaVerify): Promise<Fate<SchemaData<unknown>>> {
const fate = new Fate<SchemaData<unknown>>();
const schema = this.getSchema(type);
const schema = this.getSchema(init.type);

if (!schema) {
return fate.setErrorCode(schemaError('missing_custom_type_schema', type));
return fate.setErrorCode(schemaError('missing_custom_type_schema', init.type));
}

return schema.verify({
data: value,
path: path,
base: base
});
return schema.verify(init);
}
}
53 changes: 34 additions & 19 deletions src/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,12 +99,12 @@ export class Schema<DataT, InputT extends SchemaData<DataT>, VerifiedT = InputT>
): Promise<Fate<DataT | SchemaData<unknown> | null>> {
const fate = new Fate<DataT | SchemaData<unknown> | null>();

const currPath = path.mkChild(field.name);
//const parsePath = this.getParsePath(propPath);
if (!field) {
return fate.setErrorCode(schemaError(`missing_field`, currPath.getValue()));
return fate.setErrorCode(schemaError(`missing_field`, path.getValue()));
}

const currPath = path.mkChild(field.name);

if (value === undefined) {
return fate.setErrorCode(schemaError('missing_field_value', currPath.getValue()));
}
Expand All @@ -126,8 +126,12 @@ export class Schema<DataT, InputT extends SchemaData<DataT>, VerifiedT = InputT>
base: Log
): Promise<Fate<DataT | SchemaData<unknown>>> {
const fate = new Fate<DataT | SchemaData<unknown>>();
if (value === null && !field.types.includes('null')) {
return fate.setErrorCode(schemaError('field_does_not_support_type:null', path.getValue()));
if (value === null) {
if (field.types.includes('null')) {
return fate.setSuccess(true);
} else {
return fate.setErrorCode(schemaError('field_does_not_support_type:null', path.getValue()));
}
}

for (const type of field.types) {
Expand Down Expand Up @@ -172,7 +176,7 @@ export class Schema<DataT, InputT extends SchemaData<DataT>, VerifiedT = InputT>
return this.customTypes.has(type);
}
/**
* Check if `type` is supported by the schema. Doesn't check if value actuallyz
* Check if `type` is supported by the schema. Doesn't check if value actually
* conforms to the specified type.
* @param type
* @param value
Expand Down Expand Up @@ -213,14 +217,13 @@ export class Schema<DataT, InputT extends SchemaData<DataT>, VerifiedT = InputT>
}

/**
* 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.
* Check if value type matches type for primitives, and whether the content matches
* the expected range or format (if any).
* @param type
* @param value
*/
public async verifyValue(init: SchemaVerifyValue): Promise<Fate<DataT | SchemaData<unknown>>> {
const fate = new Fate<DataT | SchemaData<unknown>>();
const currPath = init.path.mkChild(init.fieldId);

if (this.isBuiltIn(init.fieldType)) {
if (this.valueIsBuiltInType(init.fieldType, init.value)) {
Expand All @@ -231,20 +234,21 @@ export class Schema<DataT, InputT extends SchemaData<DataT>, VerifiedT = InputT>
return fate.setErrorCode(
schemaError(
`field_does_not_support_value_type:${valueTypeLabel(init.value)}`,
currPath.getValue()
init.path.getValue()
)
);
}
}

if (this.customTypes.hasSchema(init.fieldType) && typeof init.value === 'object') {
return this.customTypes.verifySchema(
init.fieldId,
init.fieldType,
init.value as SchemaData<DataT>,
currPath,
init.base
);
return this.customTypes.verify({
id: init.fieldId,
type: init.fieldType,
data: init.value as SchemaData<DataT>,
path: init.path,
base: init.base,
childSchema: true
});
}

//const custom = await this.customTypes.verify(type, value, this.base);
Expand All @@ -253,13 +257,24 @@ export class Schema<DataT, InputT extends SchemaData<DataT>, VerifiedT = InputT>
}

return fate.setErrorCode(
schemaError(`field_does_not_support_type:${init.fieldType}`, currPath.getValue())
schemaError(`field_does_not_support_type:${init.fieldType}`, init.path.getValue())
);
}

/**
* Verify provided data object's structure, content, and types against this schema.
* @param init
*/
public async verify(init: SchemaVerifyInit): Promise<Fate<VerifiedT>> {
const fate = new Fate<VerifiedT>();
const currPath = init.path.mkChild(stringValue(init.id, this.schemaName));

const schemaName = stringValue(this.schemaName, '__missing_schemaName__');
// Root schemas (no parent) use their schema name as the first path item. Child schemas DO NOT
// set their own path because they have no way to know their property name in parent schema.

const parentPath = init?.path ? init.path : new SchemaPath();
const currPath =
init?.childSchema === true ? parentPath : parentPath.mkChild(stringValue(init.id, schemaName));

if (!init.base) {
console.error(`Missing argument: base`);
Expand Down
11 changes: 10 additions & 1 deletion src/schema/error/code.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,16 @@
*/

/**
* Possible error codes returned by schema functions.
*
* @category Schemas
*/
export type SchemaErrorCode = 'missing_argument' | string;
export type SchemaErrorCode =
| 'empty_schema_object'
| 'exception'
| 'missing_argument'
| 'missing_schema_data'
| 'nonfunction_argument'
| 'null_transform_output'
| 'schema_field_mismatch'
| string;
14 changes: 13 additions & 1 deletion src/schema/path.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,26 @@ export class SchemaPath {
public readonly idSeparator: string;

constructor(init?: SchemaPathInit) {
this.path = Array.isArray(init?.path) ? init.path : [];
this.path = this.mkPath(init?.path);
this.idSeparator = stringValue(init?.idSeparator, '.');
}

public getValue(): string {
return this.path.join(this.idSeparator);
}

private mkPath(parts?: string | string[]): string[] {
if (Array.isArray(parts)) {
return parts;
}

if (typeof parts === 'string') {
return [parts];
}

return [];
}

public mkChild(id: string): SchemaPath {
if (typeof id !== 'string') {
return this;
Expand Down
2 changes: 1 addition & 1 deletion src/schema/path/init.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export interface SchemaPathInit {
path?: string[];
path?: string | string[];
idSeparator?: string;
}
10 changes: 9 additions & 1 deletion src/schema/verify/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,19 @@ import {SchemaPath} from '../path';
import {type SchemaData} from '../data';

/**
* Arguments needed for schema verification.
*
* @category Schemas
*/
export interface SchemaVerifyInit<DataT = unknown> {
/** Optional ID used in printed schema paths. Schema name is used when ID not provided. */
id?: string;
/** Data object to verify against this schema. */
data: SchemaData<DataT>;
path: SchemaPath;
/** Current path of properties accessed to reach this point. */
path?: SchemaPath;
base: Log;
/** Whether schema being processed is a child property of another schema. */
childSchema?: boolean;
type?: string;
}
Loading

0 comments on commit 6b2de52

Please sign in to comment.