mirror of
https://gitlab.com/openstapps/openstapps.git
synced 2026-01-20 00:23:03 +00:00
refactor: update dependencies and fix resulting errors
Upgraded JSON Schema from version 6 to version 7 Upgraded TypeDoc version to latest Replaced 'jsonschema' with 'json-schema' package to better comply with 'ts-json-schema-generator' Replace JSON Schema validation with AJV in areas where it wasn't used previously Removed commander help output as it causes strange issues
This commit is contained in:
committed by
Rainer Killinger
parent
b7cdb6a9ad
commit
5330255b7e
122
src/validate.ts
122
src/validate.ts
@@ -14,15 +14,20 @@
|
||||
*/
|
||||
import {asyncPool} from '@krlwlfrt/async-pool';
|
||||
import {Logger} from '@openstapps/logger';
|
||||
import Ajv from 'ajv';
|
||||
import betterAjvErrors from 'better-ajv-errors';
|
||||
import {PathLike} from 'fs';
|
||||
import {Schema, Validator as JSONSchemaValidator, ValidatorResult} from 'jsonschema';
|
||||
import {JSONSchema7} from 'json-schema';
|
||||
import * as mustache from 'mustache';
|
||||
import {basename, join, resolve} from 'path';
|
||||
import {Schema} from 'ts-json-schema-generator';
|
||||
import {
|
||||
ExpectableValidationErrors,
|
||||
ExpectedValidationErrors,
|
||||
globPromisified,
|
||||
isThingWithType,
|
||||
readFilePromisified,
|
||||
ValidationError,
|
||||
ValidationResult,
|
||||
writeFilePromisified,
|
||||
} from './common';
|
||||
|
||||
@@ -30,21 +35,24 @@ import {
|
||||
* StAppsCore validator
|
||||
*/
|
||||
export class Validator {
|
||||
|
||||
/**
|
||||
* JSON Schema Validator
|
||||
*/
|
||||
private readonly ajv = Ajv({verbose: true, jsonPointers: true, extendRefs: true});
|
||||
/**
|
||||
* Map of schema names to schemas
|
||||
*/
|
||||
private readonly schemas: { [type: string]: Schema; } = {};
|
||||
|
||||
/**
|
||||
* JSONSchema validator instance
|
||||
* A wrapper function for Ajv that transforms the error into the compatible old error
|
||||
*
|
||||
* @param schema the schema that will be validated against
|
||||
* @param instance the instance that will be validated
|
||||
*/
|
||||
private readonly validator: JSONSchemaValidator;
|
||||
|
||||
/**
|
||||
* Create a new validator
|
||||
*/
|
||||
constructor() {
|
||||
this.validator = new JSONSchemaValidator();
|
||||
private ajvValidateWrapper(schema: JSONSchema7, instance: unknown): ValidationResult {
|
||||
return fromAjvResult(this.ajv.validate(schema, instance), schema, instance, this.ajv);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -65,13 +73,9 @@ export class Validator {
|
||||
await asyncPool(2, schemaFiles, async (file: string) => {
|
||||
// read schema file
|
||||
const buffer = await readFilePromisified(file);
|
||||
const schema = JSON.parse(buffer.toString());
|
||||
|
||||
// add schema to validator
|
||||
this.validator.addSchema(schema);
|
||||
|
||||
// add schema to map
|
||||
this.schemas[basename(file, '.json')] = schema;
|
||||
this.schemas[basename(file, '.json')] = JSON.parse(buffer.toString());
|
||||
|
||||
Logger.info(`Added ${file} to validator.`);
|
||||
});
|
||||
@@ -85,17 +89,17 @@ export class Validator {
|
||||
* @param instance Instance to validate
|
||||
* @param schema Name of schema to validate instance against or the schema itself
|
||||
*/
|
||||
public validate(instance: unknown, schema?: string | Schema): ValidatorResult {
|
||||
public validate(instance: unknown, schema?: string | Schema): ValidationResult {
|
||||
if (typeof schema === 'undefined') {
|
||||
if (isThingWithType(instance)) {
|
||||
// schema name can be infered from type string
|
||||
// schema name can be inferred from type string
|
||||
// tslint:disable-next-line: completed-docs
|
||||
const schemaSuffix = (instance as { type: string; }).type.split(' ')
|
||||
.map((part: string) => {
|
||||
.map((part: string) => {
|
||||
return part.substr(0, 1)
|
||||
.toUpperCase() + part.substr(1);
|
||||
.toUpperCase() + part.substr(1);
|
||||
})
|
||||
.join('');
|
||||
.join('');
|
||||
const schemaName = `SC${schemaSuffix}`;
|
||||
|
||||
return this.validate(instance, schemaName);
|
||||
@@ -108,20 +112,61 @@ export class Validator {
|
||||
throw new Error(`No schema available for ${schema}.`);
|
||||
}
|
||||
|
||||
return this.validator.validate(instance, this.schemas[schema]);
|
||||
// schema will be cached
|
||||
return this.ajvValidateWrapper(this.schemas[schema], instance);
|
||||
}
|
||||
|
||||
return this.validator.validate(instance, schema);
|
||||
return this.ajvValidateWrapper(schema, instance);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a ValidationResult from ajv
|
||||
*
|
||||
* Implemented for compatibility purposes
|
||||
*
|
||||
* @param result the result, now a ValidationResult
|
||||
* @param schema the schema that has been validated against
|
||||
* @param instance the data that has been validated
|
||||
* @param ajvInstance the ajv instance with which the validation was done
|
||||
*/
|
||||
function fromAjvResult(
|
||||
result: boolean | PromiseLike<unknown>,
|
||||
schema: JSONSchema7,
|
||||
instance: unknown,
|
||||
ajvInstance: Ajv.Ajv,
|
||||
): ValidationResult {
|
||||
// tslint:disable-next-line
|
||||
// @ts-ignore function can return void, which at runtime will be undefined. TS doesn't allow to assign void to undefined
|
||||
const betterErrorObject: betterAjvErrors.IOutputError[] | undefined =
|
||||
betterAjvErrors(schema, instance, ajvInstance.errors, {format: 'js', indent: null});
|
||||
|
||||
return {
|
||||
errors: ajvInstance.errors?.map((ajvError, index) => {
|
||||
|
||||
const error: ValidationError = {
|
||||
dataPath: ajvError.dataPath,
|
||||
instance: instance,
|
||||
message: betterErrorObject?.[index].error!,
|
||||
name: ajvError.keyword,
|
||||
schemaPath: ajvError.schemaPath,
|
||||
suggestion: betterErrorObject?.[index].suggestion,
|
||||
};
|
||||
// (validationError as ValidationError).humanReadableError = betterErrorCLI?.[index] as unknown as string;
|
||||
|
||||
return error;
|
||||
}) ?? [],
|
||||
valid: typeof result === 'boolean' ? result : false,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate all test files in the given resources directory against schema files in the given (schema) directory
|
||||
*
|
||||
* @param schemaDir The directory where the JSON schema files are
|
||||
* @param resourcesDir The directory where the test files are
|
||||
*/
|
||||
export async function validateFiles(schemaDir: string, resourcesDir: string): Promise<ExpectableValidationErrors> {
|
||||
export async function validateFiles(schemaDir: string, resourcesDir: string): Promise<ExpectedValidationErrors> {
|
||||
// instantiate new validator
|
||||
const v = new Validator();
|
||||
await v.addSchemas(schemaDir);
|
||||
@@ -136,7 +181,7 @@ export async function validateFiles(schemaDir: string, resourcesDir: string): Pr
|
||||
Logger.log(`Found ${testFiles.length} file(s) to test.`);
|
||||
|
||||
// map of errors per file
|
||||
const errors: ExpectableValidationErrors = {};
|
||||
const errors: ExpectedValidationErrors = {};
|
||||
|
||||
// tslint:disable-next-line:no-magic-numbers - iterate over files to test
|
||||
await asyncPool(2, testFiles, async (testFile: string) => {
|
||||
@@ -162,13 +207,11 @@ export async function validateFiles(schemaDir: string, resourcesDir: string): Pr
|
||||
|
||||
// iterate over errors
|
||||
for (const error of result.errors) {
|
||||
// get idx of expected error
|
||||
const errorIdx = expectedErrors.indexOf(error.name);
|
||||
const errorIndex = expectedErrors.indexOf(error.name);
|
||||
let expected = false;
|
||||
|
||||
if (errorIdx >= 0) {
|
||||
// remove from list of expected errors
|
||||
expectedErrors.splice(errorIdx, 1);
|
||||
if (errorIndex >= 0) {
|
||||
expectedErrors.splice(errorIndex, 1);
|
||||
expected = true;
|
||||
} else {
|
||||
unexpectedErrors++;
|
||||
@@ -188,14 +231,14 @@ export async function validateFiles(schemaDir: string, resourcesDir: string): Pr
|
||||
await Logger.error(`Extraneous expected error '${error}' in ${testFile}.`);
|
||||
|
||||
errors[testFileName].push({
|
||||
argument: false,
|
||||
dataPath: 'undefined',
|
||||
expected: false,
|
||||
instance: testDescription.instance,
|
||||
message: `expected error ${error} did not occur`,
|
||||
instance: undefined,
|
||||
// instance: testDescription.instance,
|
||||
message: 'undefined',
|
||||
name: `expected ${error}`,
|
||||
property: 'unknown',
|
||||
schema: 'undefined',
|
||||
stack: 'undefined',
|
||||
schemaPath: 'undefined',
|
||||
suggestion: 'undefined',
|
||||
});
|
||||
}
|
||||
} else if (unexpectedErrors === 0) {
|
||||
@@ -212,7 +255,7 @@ export async function validateFiles(schemaDir: string, resourcesDir: string): Pr
|
||||
* @param reportPath Path to write report to
|
||||
* @param errors Errors that occurred in validation
|
||||
*/
|
||||
export async function writeReport(reportPath: PathLike, errors: ExpectableValidationErrors): Promise<void> {
|
||||
export async function writeReport(reportPath: PathLike, errors: ExpectedValidationErrors): Promise<void> {
|
||||
let buffer = await readFilePromisified(resolve(__dirname, '..', 'resources', 'file.html.mustache'));
|
||||
const fileTemplate = buffer.toString();
|
||||
|
||||
@@ -229,15 +272,16 @@ export async function writeReport(reportPath: PathLike, errors: ExpectableValida
|
||||
let fileOutput = '';
|
||||
|
||||
errors[fileName].forEach((error, idx) => {
|
||||
|
||||
fileOutput += mustache.render(errorTemplate, {
|
||||
idx: idx + 1,
|
||||
// tslint:disable-next-line:no-magic-numbers
|
||||
instance: JSON.stringify(error.instance, null, 2),
|
||||
message: error.message,
|
||||
property: error.property,
|
||||
// tslint:disable-next-line:no-magic-numbers
|
||||
schema: JSON.stringify(error.schema, null, 2),
|
||||
name: error.name,
|
||||
schemaPath: error.schemaPath,
|
||||
status: (error.expected) ? 'alert-success' : 'alert-danger',
|
||||
suggestion: error.suggestion,
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user