From 972f6360471d6b462532de20ac5270ac8808ca17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Jim=C3=A9nez=20Saiz?= Date: Tue, 6 Jun 2023 15:23:55 +0200 Subject: [PATCH] feat(doorkeeper): :sparkles: add new check method to doorkeeper (#5) --- .vscode/settings.json | 3 +- .../api/doorkeeper/src/Doorkeeper.test.ts | 68 ++++ packages/api/doorkeeper/src/Doorkeeper.ts | 356 ++++++++++-------- 3 files changed, 264 insertions(+), 163 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 354862fe..27628caa 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -59,7 +59,8 @@ "service-config", "openc2", "app-wrapper", - "ci" + "ci", + "doorkeeper" ], } diff --git a/packages/api/doorkeeper/src/Doorkeeper.test.ts b/packages/api/doorkeeper/src/Doorkeeper.test.ts index 7adbd985..3760dabf 100644 --- a/packages/api/doorkeeper/src/Doorkeeper.test.ts +++ b/packages/api/doorkeeper/src/Doorkeeper.test.ts @@ -121,9 +121,77 @@ describe('#DoorKeeper #package', () => { }); it(`Should resolve a correct JSON object when try to validate a schema that is in the scope and is CORRECT`, async () => { await expect(dk.validate('Config.Artifact', artifact, v4())).resolves.toBe(artifact); + await expect(dk.validate('Config.Artifact', artifact)).resolves.toBe(artifact); + }); + it(`Should invoke the callback a correct JSON object when try to validate a schema that is in the scope and is CORRECT`, done => { + dk.validate('Config.Artifact', artifact, (error, result) => { + expect(error).toBeUndefined(); + expect(result).toEqual(artifact); + done(); + }); + }); + it(`Should invoke the callback a correct JSON object when try to validate a schema that is in the scope and is CORRECT using external uuid`, done => { + dk.validate('Config.Artifact', artifact, v4(), (error, result) => { + expect(error).toBeUndefined(); + expect(result).toEqual(artifact); + done(); + }); + }); + it(`Should invoke the callback with an error when try to validate a schema that is in the scope and is INCORRECT`, done => { + dk.validate('Config.Artifact', {}, (error, result) => { + expect(error).toBeInstanceOf(Multi); + expect((error as Multi).message).toEqual('Errors during the schema validation process'); + expect((error as Multi).name).toEqual('ValidationError'); + expect((error as Multi).causes).toBeDefined(); + const causes = (error as Multi).causes as Crash[]; + for (const cause of causes) { + expect(cause).toBeInstanceOf(Crash); + expect(cause.name).toEqual('ValidationError'); + } + const trace = (error as Multi).trace(); + expect(trace).toEqual([ + "ValidationError: must have required property 'id'", + "ValidationError: must have required property 'processId'", + "ValidationError: must have required property 'release'", + "ValidationError: must have required property 'version'", + ]); + expect(result).toEqual({}); + done(); + }); + }); + it(`Should invoke the callback with an error when try to validate a schema that is in the scope and is INCORRECT using external uuid`, done => { + dk.validate('Config.Artifact', {}, v4(), (error, result) => { + expect(error).toBeInstanceOf(Multi); + expect((error as Multi).message).toEqual('Errors during the schema validation process'); + expect((error as Multi).name).toEqual('ValidationError'); + expect((error as Multi).causes).toBeDefined(); + const causes = (error as Multi).causes as Crash[]; + for (const cause of causes) { + expect(cause).toBeInstanceOf(Crash); + expect(cause.name).toEqual('ValidationError'); + } + const trace = (error as Multi).trace(); + expect(trace).toEqual([ + "ValidationError: must have required property 'id'", + "ValidationError: must have required property 'processId'", + "ValidationError: must have required property 'release'", + "ValidationError: must have required property 'version'", + ]); + expect(result).toEqual({}); + done(); + }); }); it(`Should resolve a correct JSON object when attempt to validate a schema that is in the scope and is CORRECT`, () => { expect(dk.attempt('Config.Artifact', artifact, v4())).toBe(artifact); + expect(dk.attempt('Config.Artifact', artifact)).toBe(artifact); + }); + it(`Should return a TRUE value when try to check a schema that is in the scope and is CORRECT`, () => { + expect(dk.check('Config.Artifact', artifact)).toBeTruthy(); + expect(dk.check('Config.Artifact', artifact, v4())).toBeTruthy(); + }); + it(`Should return a FALSE value when try to check a schema that is in the scope and is INCORRECT`, () => { + expect(dk.check('Config.Artifact', {})).toBeFalsy(); + expect(dk.check('Config.Artifact', {}, v4())).toBeFalsy(); }); }); describe('#Sad path', () => { diff --git a/packages/api/doorkeeper/src/Doorkeeper.ts b/packages/api/doorkeeper/src/Doorkeeper.ts index 0646dd20..ffee09d9 100644 --- a/packages/api/doorkeeper/src/Doorkeeper.ts +++ b/packages/api/doorkeeper/src/Doorkeeper.ts @@ -18,6 +18,9 @@ type ValidatedOutput = K extends keyof T ? T[K] : any; /** AJV options but all errors must be true */ export type DoorkeeperOptions = Options; +/** Callback function for the validation process */ +export type ResultCallback = (error?: Crash | Multi, result?: ValidatedOutput) => void; + /** Wrapping class for AJV */ export class DoorKeeper { readonly uuid = v4(); @@ -33,47 +36,6 @@ export class DoorKeeper { this.ajv.addKeyword({ keyword: 'markdownDescription' }); this.ajv.addKeyword({ keyword: 'defaultSnippets' }); } - /** - * Registers a group of schemas from an object using the keys of the - * object as key and the value as the validation schema - * @param schemas - Object containing the [key, validation schema] - * @returns - the instance - */ - register(schemas: Record, AnySchema>): DoorKeeper; - /** - * Registers a group of schemas from an array and compiles them - * @param schemas - Array containing the - * @returns - the instance - */ - register(schemas: AnySchema[]): DoorKeeper; - /** - * Registers one schema with its key - * @param key - the key with which identify the schema - * @param validatorSchema - the schema to be registered - * @returns - the instance - */ - register(key: SchemaSelector, validatorSchema: AnySchema): DoorKeeper; - register( - keyOrArraySchemasOrObjectSchemas: SchemaSelector | AnySchema[] | Record, - validatorSchema?: AnySchema - ): DoorKeeper { - if ( - typeof keyOrArraySchemasOrObjectSchemas === 'string' && - typeof validatorSchema === 'object' && - !Array.isArray(validatorSchema) - ) { - this.addSchema(validatorSchema, keyOrArraySchemasOrObjectSchemas); - } else if (Array.isArray(keyOrArraySchemasOrObjectSchemas)) { - this.compileSchemas(keyOrArraySchemasOrObjectSchemas); - } else if (typeof keyOrArraySchemasOrObjectSchemas === 'object') { - for (const [key, _schema] of Object.entries(keyOrArraySchemasOrObjectSchemas)) { - this.addSchema(_schema, key); - } - } else { - throw new Crash('Invalid parameters, no schema will be registered', this.uuid); - } - return this; - } /** * Add a new schema to the ajv collection * @param schema - schema to be added @@ -107,127 +69,6 @@ export class DoorKeeper { } } } - /** - * Validate an Object against the input schema - * @param schema - The schema we want to validate - * @param data - Object to be validated - * @param uuid - unique identifier for this operation - * @param callback - callback function with the result of the validation - */ - validate>( - schema: K, - data: any, - uuid: string, - callback: (error?: Crash | Multi, result?: ValidatedOutput) => void - ): void; - /** - * Validate an Object against the input schema - * @param schema - The schema we want to validate - * @param data - Object to be validated - * @param uuid - unique identifier for this operation - */ - validate>( - schema: K, - data: any, - uuid: string - ): Promise>; - validate>( - schema: K, - data: any, - uuid: string, - callback?: (error?: Crash | Multi, result?: ValidatedOutput) => void - ): Promise> | void { - const validator = this.ajv.getSchema(schema); - let error: Crash | Multi | undefined = undefined; - // ********************************************************************************************* - // #region No valid schema - if (validator === undefined) { - error = new Crash(`${schema} is not registered in the collection.`, uuid, { - name: 'ValidationError', - info: { schema, data }, - }); - } - // #endregion - // ********************************************************************************************* - // #region Check the against schema - else { - if (!validator(data)) { - if (validator.errors) { - error = this.multify(validator.errors, schema, uuid, data); - } else { - error = new Crash(`Unexpected error in JSON schema validation process`, uuid, { - name: 'ValidationError', - info: { schema, data }, - }); - } - } - } - // #endregion - // ********************************************************************************************* - // #region Return the result - if (callback) { - callback(error, data); - } else { - if (error) { - return Promise.reject(error); - } else { - return Promise.resolve(data); - } - } - //#endregion - } - - /** - * Checks if the input schema is registered - * @param schema - schema asked for - * @returns - if the schema is registered in the ajv collection - */ - isSchemaRegistered>(schema: K): boolean { - return !!this.ajv.getSchema(schema); - } - - /** - * Try to validate an Object against the input schema or throw a validation - * error - * @param schema - The schema we want to validate - * @param data - Object to be validated - * @param uuid - unique identifier for this operation - */ - public attempt>( - schema: K, - data: any, - uuid: string - ): ValidatedOutput { - const validator = this.ajv.getSchema(schema); - // ********************************************************************************************* - // #region No valid schema - if (validator === undefined) { - throw new Crash(`${schema} is not registered in the collection.`, uuid, { - name: 'ValidationError', - info: { schema, data }, - }); - } - // #endregion - // ********************************************************************************************* - // #region Check the against schema - else { - if (!validator(data)) { - if (validator.errors) { - throw this.multify(validator.errors, schema, uuid, data); - } else { - throw new Crash(`Unexpected error in JSON schema validation process`, uuid, { - name: 'ValidationError', - info: { schema, data }, - }); - } - } - } - // #endregion - // ********************************************************************************************* - // #region Return the result - return data as ValidatedOutput; - //#endregion - } /** * Create a Multi error from AJV ErrorObject array * @param errors - AJV errors @@ -293,4 +134,195 @@ export class DoorKeeper { } return message; } + /** + * + * @param schema - The schema we want to validate + * @param data - Object to be validated + * @param uuid - unique identifier for this operation + */ + private checkSchema>(schema: K, data: any, uuid = v4()): void { + const validator = this.ajv.getSchema(schema); + if (validator === undefined) { + throw new Crash(`${schema} is not registered in the collection.`, uuid, { + name: 'ValidationError', + info: { schema, data }, + }); + } else { + if (!validator(data)) { + if (validator.errors) { + throw this.multify(validator.errors, schema, uuid, data); + } else { + throw new Crash(`Unexpected error in JSON schema validation process`, uuid, { + name: 'ValidationError', + info: { schema, data }, + }); + } + } + } + } + /** + * Registers a group of schemas from an object using the keys of the + * object as key and the value as the validation schema + * @param schemas - Object containing the [key, validation schema] + * @returns - the instance + */ + public register(schemas: Record, AnySchema>): DoorKeeper; + /** + * Registers a group of schemas from an array and compiles them + * @param schemas - Array containing the + * @returns - the instance + */ + public register(schemas: AnySchema[]): DoorKeeper; + /** + * Registers one schema with its key + * @param key - the key with which identify the schema + * @param validatorSchema - the schema to be registered + * @returns - the instance + */ + public register(key: SchemaSelector, validatorSchema: AnySchema): DoorKeeper; + public register( + keyOrArraySchemasOrObjectSchemas: SchemaSelector | AnySchema[] | Record, + validatorSchema?: AnySchema + ): DoorKeeper { + if ( + typeof keyOrArraySchemasOrObjectSchemas === 'string' && + typeof validatorSchema === 'object' && + !Array.isArray(validatorSchema) + ) { + this.addSchema(validatorSchema, keyOrArraySchemasOrObjectSchemas); + } else if (Array.isArray(keyOrArraySchemasOrObjectSchemas)) { + this.compileSchemas(keyOrArraySchemasOrObjectSchemas); + } else if (typeof keyOrArraySchemasOrObjectSchemas === 'object') { + for (const [key, _schema] of Object.entries(keyOrArraySchemasOrObjectSchemas)) { + this.addSchema(_schema, key); + } + } else { + throw new Crash('Invalid parameters, no schema will be registered', this.uuid); + } + return this; + } + /** + * Checks if the input schema is registered + * @param schema - schema asked for + * @returns - if the schema is registered in the ajv collection + */ + public isSchemaRegistered>(schema: K): boolean { + return !!this.ajv.getSchema(schema); + } + /** + * Validate an Object against the input schema + * @param schema - The schema we want to validate + * @param data - Object to be validated + * @param uuid - unique identifier for this operation + * @param callback - callback function with the result of the validation + */ + public validate>( + schema: K, + data: any, + uuid: string, + callback: ResultCallback + ): void; + /** + * Validate an Object against the input schema + * @param schema - The schema we want to validate + * @param data - Object to be validated + * @param callback - callback function with the result of the validation + */ + public validate>( + schema: K, + data: any, + callback: ResultCallback + ): void; + /** + * Validate an Object against the input schema + * @param schema - The schema we want to validate + * @param data - Object to be validated + * @param uuid - unique identifier for this operation + */ + public validate>( + schema: K, + data: any, + uuid: string + ): Promise>; + /** + * Validate an Object against the input schema + * @param schema - The schema we want to validate + * @param data - Object to be validated + * @param uuid - unique identifier for this operation + */ + public validate>( + schema: K, + data: any + ): Promise>; + public validate>( + schema: K, + data: any, + uuidOrCallBack?: string | ResultCallback, + callbackOrUndefined?: ResultCallback + ): Promise> | void { + let error: Crash | Multi | undefined; + const uuid = typeof uuidOrCallBack === 'string' ? uuidOrCallBack : v4(); + const callback = typeof uuidOrCallBack === 'function' ? uuidOrCallBack : callbackOrUndefined; + try { + this.checkSchema(schema, data, uuid); + } catch (rawError) { + error = Crash.from(rawError); + } finally { + if (callback) { + callback(error, data); + } else { + if (error) { + return Promise.reject(error); + } else { + return Promise.resolve(data); + } + } + } + } + /** + * Try to validate an Object against the input schema or throw a ValidationError + * @param schema - The schema we want to validate + * @param data - Object to be validated + */ + public attempt>(schema: K, data: any): ValidatedOutput; + /** + * Try to validate an Object against the input schema or throw a ValidationError + * @param schema - The schema we want to validate + * @param data - Object to be validated + * @param uuid - unique identifier for this operation + */ + public attempt>( + schema: K, + data: any, + uuid: string + ): ValidatedOutput; + public attempt>( + schema: K, + data: any, + uuid?: string + ): ValidatedOutput { + this.checkSchema(schema, data, uuid); + return data as ValidatedOutput; + } + /** + * Validate an Object against the input schema and return a boolean + * @param schema - The schema we want to validate + * @param data - Object to be validated + */ + public check>(schema: K, data: any): boolean; + /** + * Validate an Object against the input schema and return a boolean + * @param schema - The schema we want to validate + * @param data - Object to be validated + * @param uuid - unique identifier for this operation + */ + public check>(schema: K, data: any, uuid: string): boolean; + public check>(schema: K, data: any, uuid?: string): boolean { + try { + this.checkSchema(schema, data, uuid); + return true; + } catch (error) { + return false; + } + } }