Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 0 additions & 20 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,7 @@
// import built-in validators
import builtInValidatorClasses from './lib/validators';
import IValidator from './lib/validators/validator';

// TODO: Import user-defined validators dynamically in specific directory

// defined all validators and change to key-value object format for export
const loadedValidators: { [name: string]: IValidator } = Object.assign(
{},
...builtInValidatorClasses.map((validatorClass) => {
const validator = new validatorClass();
return {
[validator.name as string]: validator,
};
})
);

// export all other non-default objects of validators module
export * from './lib/validators';
// Export all other modules
export * from './models';
export * from './lib/utils';
export * from './lib/validators/validator';
export * from './lib/template-engine';
export * from './lib/artifact-builder';

export { loadedValidators, IValidator };
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export interface DateInputArgs {
format?: string;
}

export class DateTypeValidator implements IValidator {
export default class DateTypeValidator implements IValidator {
public readonly name = 'date';
// Validator for arguments schema in schema.yaml, should match DateInputArgs
private argsValidator = Joi.object({
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export interface IntInputArgs {
less?: number;
}

export class IntegerTypeValidator implements IValidator {
export default class IntegerTypeValidator implements IValidator {
public readonly name = 'integer';
// Validator for arguments schema in schema.yaml, should match IntInputArgs
private argsValidator = Joi.object({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export interface StringInputArgs {
max?: number;
}

export class StringTypeValidator implements IValidator {
export default class StringTypeValidator implements IValidator {
public readonly name = 'string';
// Validator for arguments schema in schema.yaml, should match StringInputArgs
private argsValidator = Joi.object({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export interface UUIDInputArgs {
version?: UUIDVersion;
}

export class UUIDTypeValidator implements IValidator {
export default class UUIDTypeValidator implements IValidator {
public readonly name = 'uuid';
// Validator for arguments schema in schema.yaml, should match UUIDInputArgs
private argsValidator = Joi.object({
Expand Down
27 changes: 15 additions & 12 deletions packages/core/src/lib/validators/index.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
// Data Type Validators
import { IntInputArgs, IntegerTypeValidator } from './data-type-validators';
import { DateInputArgs, DateTypeValidator } from './data-type-validators';
import { StringInputArgs, StringTypeValidator } from './data-type-validators';
import { UUIDInputArgs, UUIDTypeValidator } from './data-type-validators';
// export all other non-default objects of validators module
export * from './data-type-validators/dateTypeValidator';
export * from './data-type-validators/integerTypeValidator';
export * from './data-type-validators/stringTypeValidator';
export * from './data-type-validators/uuidTypeValidator';
export * from './validatorLoader';

// TODO: Other Built-in Validators
// import default objects and export
import IValidator from './validator';
import DateTypeValidator from './data-type-validators/dateTypeValidator';
import IntegerTypeValidator from './data-type-validators/integerTypeValidator';
import StringTypeValidator from './data-type-validators/stringTypeValidator';
import UUIDTypeValidator from './data-type-validators/uuidTypeValidator';

// export all validators needed args interface
export { IntInputArgs, DateInputArgs, StringInputArgs, UUIDInputArgs };

// export default all validators of IValidator for use
export default [
export {
IValidator,
DateTypeValidator,
IntegerTypeValidator,
StringTypeValidator,
UUIDTypeValidator,
];
};
53 changes: 53 additions & 0 deletions packages/core/src/lib/validators/validatorLoader.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import IValidator from './validator';
import * as glob from 'glob';
import * as path from 'path';

export interface IValidatorLoader {
load(validatorName: string): Promise<IValidator>;
}

export class ValidatorLoader implements IValidatorLoader {
// only found built-in validators in sub folders
private builtInFolderPath: string = path.resolve(__dirname, '*', '*.ts');
private userDefinedFolderPath?: string;

constructor(folderPath?: string) {
this.userDefinedFolderPath = folderPath;
}
public async load(validatorName: string) {
let validatorFiles = [
...(await this.getValidatorFilePaths(this.builtInFolderPath)),
];
if (this.userDefinedFolderPath) {
// include sub-folder or non sub-folders
const userDefinedValidatorFiles = await this.getValidatorFilePaths(
path.resolve(this.userDefinedFolderPath, '**', '*.ts')
);
validatorFiles = validatorFiles.concat(userDefinedValidatorFiles);
}

for (const file of validatorFiles) {
// import validator files to module
const validatorModule = await import(file);
// get validator class by getting default.
if (validatorModule && validatorModule.default) {
const validatorClass = validatorModule.default;
const validator = new validatorClass() as IValidator;
if (validator.name === validatorName) return validator;
}
}
// throw error if not found
throw new Error(
`The name "${validatorName}" of validator not defined in built-in validators and passed folder path, or the defined validator not export as default.`
);
}

private async getValidatorFilePaths(sourcePath: string): Promise<string[]> {
return new Promise((resolve, reject) => {
glob(sourcePath, { nodir: true }, (err, files) => {
if (err) return reject(err);
else return resolve(files);
});
});
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { DateTypeValidator } from '@validators/data-type-validators';
import { DateTypeValidator } from '@validators/.';

describe('Test "date" type validator', () => {
it.each([
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { IntegerTypeValidator } from '@validators/data-type-validators';
import { IntegerTypeValidator } from '@validators/.';

describe('Test "integer" type validator', () => {
it.each([
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import faker from '@faker-js/faker';
import { StringTypeValidator } from '@validators/data-type-validators';
import { StringTypeValidator } from '@validators/.';

describe('Test "string" type validator', () => {
it.each([
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as uuid from 'uuid';
import { UUIDTypeValidator } from '@validators/data-type-validators';
import { UUIDTypeValidator } from '@validators/.';

describe('Test "uuid" type validator ', () => {
it.each([
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { IValidator } from '@validators/.';
import * as Joi from 'joi';
import { isUndefined } from 'lodash';

type IPVersion = 'ipv4' | 'ipv6';

export interface IPInputArgs {
version?: IPVersion[];
}

export default class IPTypeValidator implements IValidator {
public readonly name = 'ip';
// Validator for arguments schema in schema.yaml, should match DateInputArgs
private argsValidator = Joi.object({
version: Joi.string().optional(),
});
public validateSchema(args: IPInputArgs): boolean {
try {
// validate arguments schema
Joi.assert(args, this.argsValidator);
return true;
} catch {
throw new Error('The arguments schema for date type is incorrect');
}
}
public validateData(value: string, args: IPInputArgs): boolean {
let schema = Joi.string().ip();
// if there are args passed
if (!isUndefined(args)) {
schema = args.version
? Joi.string().ip({
version: args.version,
})
: schema;
}
try {
// validate data value
Joi.assert(value, schema);
return true;
} catch {
throw new Error('The input parameter is invalid, it should be ip type');
}
}
}
40 changes: 40 additions & 0 deletions packages/core/test/validators/validatorLoader.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { ValidatorLoader } from '@validators/.';
import * as path from 'path';

describe('Test validator loader ', () => {
it.each([
// built-in validator
{ name: 'date', expected: 'date' },
{ name: 'uuid', expected: 'uuid' },
{ name: 'integer', expected: 'integer' },
{ name: 'string', expected: 'string' },
// custom validator
{ name: 'ip', expected: 'ip' },
])(
'Should load successfully when loading validator name "$name".',
async ({ name, expected }) => {
// Arrange
const folderPath = path.resolve(__dirname, 'test-custom-validators');
const validatorLoader = new ValidatorLoader(folderPath);
// Act
const result = await validatorLoader.load(name);

// Assert
expect(result.name).toEqual(expected);
}
);

it.each([{ name: 'not-existed-validator' }])(
'Should load failed when loading validator name "$name".',
async ({ name }) => {
// Arrange
const folderPath = path.resolve(__dirname, 'test-custom-validators');
const validatorLoader = new ValidatorLoader(folderPath);
// Act
const loadAction = validatorLoader.load(name);

// Asset
await expect(loadAction).rejects.toThrow(Error);
}
);
});
20 changes: 9 additions & 11 deletions packages/serve/src/lib/route/route-component/requestValidator.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
import {
APISchema,
loadedValidators,
IValidatorLoader,
RequestSchema,
ValidatorDefinition,
} from '@vulcan/core';

import { RequestParameters } from './requestTransformer';

export interface IRequestValidator {
validate(reqParams: RequestParameters, apiSchema: APISchema): Promise<void>;
}

export class RequestValidator implements IRequestValidator {
private validatorLoader: IValidatorLoader;
constructor(loader: IValidatorLoader) {
this.validatorLoader = loader;
}
// validate each parameters of request and transform the request content of koa ctx to "RequestParameters" format
public async validate(reqParams: RequestParameters, apiSchema: APISchema) {
await Promise.all(
Expand All @@ -27,16 +32,9 @@ export class RequestValidator implements IRequestValidator {
schemaValidators: Array<ValidatorDefinition>
) {
await Promise.all(
schemaValidators.map((schemaValidator) => {
if (!(schemaValidator.name in loadedValidators)) {
throw new Error(
`The name "${schemaValidator.name}" of validator not defined, please defined it through IValidator.`
);
}
loadedValidators[schemaValidator.name].validateData(
fieldValue,
schemaValidator.args
);
schemaValidators.map(async (schemaValidator) => {
const validator = await this.validatorLoader.load(schemaValidator.name);
validator.validateData(fieldValue, schemaValidator.args);
})
);
}
Expand Down
3 changes: 2 additions & 1 deletion packages/serve/test/app.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
RequestSchema,
TemplateEngine,
ValidatorDefinition,
ValidatorLoader,
} from '@vulcan/core';

import {
Expand Down Expand Up @@ -175,7 +176,7 @@ describe('Test vulcan server to call restful APIs', () => {

beforeAll(async () => {
const reqTransformer = new RequestTransformer();
const reqValidator = new RequestValidator();
const reqValidator = new RequestValidator(new ValidatorLoader());
const paginationTransformer = new PaginationTransformer();
stubTemplateEngine = sinon.stubInterface<TemplateEngine>();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
FieldInType,
RequestSchema,
ValidatorDefinition,
ValidatorLoader,
} from '@vulcan/core';

describe('Test request validator - validate successfully', () => {
Expand Down Expand Up @@ -141,7 +142,7 @@ describe('Test request validator - validate successfully', () => {
'Should success when give matched request parameters and api schema',
async (schema: APISchema, reqParams: RequestParameters) => {
// Act
const validator = new RequestValidator();
const validator = new RequestValidator(new ValidatorLoader());
const validateAction = validator.validate(reqParams, schema);
const result = expect(validateAction).resolves;
await result.not.toThrow();
Expand Down