@openactive/data-model-validator
Version:
A library to allow a developer to validate a JSON document against the OpenActive Modelling Opportunity Specification
173 lines (159 loc) • 4.7 kB
JavaScript
const PropertyHelper = require('../helpers/property');
const OptionsHelper = require('../helpers/options');
const ValidationError = require('../errors/validation-error');
class Rule {
constructor(options) {
this.options = options || new OptionsHelper();
/** @type {string[] | '*'} */
this.targetModels = [];
/** @type {'*' | {[model: string]: '*' | string[]}} */
this.targetFields = {};
/** @type {string[] | '*'} */
this.targetValidationModes = '*';
/**
* @type {{
* name: string;
* description: string;
* tests: {[key: string]: {
* description?: string;
* message: string;
* sampleValues?: { [messageTemplateArg: string]: string };
* category: string;
* severity: string;
* type: string;
* }}
* }}
*/
this.meta = {
name: 'Rule',
description: 'This is a base rule description that should be overridden.',
tests: {},
};
}
async validate(nodeToTest) {
let errors = [];
if (!this.isValidationModeTargeted(nodeToTest.options.validationMode)) {
return errors;
}
if (this.isModelTargeted(nodeToTest.model)) {
const modelErrors = this.validateModel(nodeToTest);
errors = errors.concat(modelErrors);
}
for (const field in nodeToTest.value) {
if (
Object.prototype.hasOwnProperty.call(nodeToTest.value, field)
&& this.isFieldTargeted(nodeToTest.model, field)
) {
const fieldErrors = await this.validateField(nodeToTest, field);
errors = errors.concat(fieldErrors);
}
}
return errors;
}
/**
* @param {import('../classes/model-node').ModelNodeType} node
* @returns {Promise<import('../errors/validation-error')[]>}
*/
// eslint-disable-next-line no-unused-vars
validateModel(node) {
throw Error('Model validation rule not implemented');
}
/**
* @param {import('../classes/model-node').ModelNodeType} node
* @param {string} field
* @returns {Promise<import('../errors/validation-error')[]>}
*/
// eslint-disable-next-line no-unused-vars
async validateField(node, field) {
throw Error('Field validation rule not implemented');
}
createError(testKey, extra = {}, messageValues = undefined) {
const rule = this.meta.tests[testKey];
let { message } = rule;
if (typeof messageValues !== 'undefined') {
for (const key in messageValues) {
if (Object.prototype.hasOwnProperty.call(messageValues, key)) {
message = message.replace(new RegExp(`{{${key}}}`, 'g'), messageValues[key]);
}
}
}
const error = Object.assign(
extra,
{
rule: this.meta.name,
category: rule.category,
type: rule.type,
severity: rule.severity,
message,
},
);
return new ValidationError(error);
}
isModelTargeted(model) {
return (
this.targetModels === '*'
|| PropertyHelper.stringMatchesField(
this.targetModels,
model.type,
model.version,
)
|| (
this.targetModels instanceof Array
&& PropertyHelper.arrayHasField(
this.targetModels,
model.type,
model.version,
)
)
);
}
/**
* @param {import('../classes/model')} model
* @param {string} field
*/
isFieldTargeted(model, field) {
if (this.targetFields === '*') {
return true;
}
if (typeof this.targetFields === 'object') {
for (const modelType in this.targetFields) {
if (Object.prototype.hasOwnProperty.call(this.targetFields, modelType)) {
if (
PropertyHelper.stringMatchesField(
modelType,
model.type,
model.version,
)
&& (
this.targetFields[modelType] === '*'
|| PropertyHelper.stringMatchesField(
this.targetFields[modelType],
field,
model.version,
)
|| (
this.targetFields[modelType] instanceof Array
&& PropertyHelper.arrayHasField(
this.targetFields[modelType],
field,
model.version,
)
)
)
) {
return true;
}
}
}
}
return false;
}
isValidationModeTargeted(validationMode) {
if (this.targetValidationModes === '*') return true;
if (this.targetValidationModes instanceof Array) {
return this.targetValidationModes.includes(validationMode);
}
return false;
}
}
module.exports = Rule;