lib_ParameterValidator.js
import debug from 'debug';
import inspector from 'schema-inspector';
import ParameterError from './errors/ParameterError.js';
import { convertOpenAPIToSchemaInspector, parseContentTypesPayloads } from './helpers.js';
/**
* Parameter Validation class (should be initiated once per endpoint upon its setup)
* @module ParameterValidator
*/
export default class ParameterValidator {
/**
* ParameterValidator
* @property {Object[]} parametersDefinition - openapi parameters definition
* @see {@link https://swagger.io/docs/specification/describing-parameters/} - for parametersDefinition
* @property {Object} requestBodyDefinition - openapi body definition
* @see {@link https://swagger.io/docs/specification/describing-request-body/} - for requestBodyDefinition
*/
constructor(parametersDefinition, requestBodyDefinition) {
const pathParamsDefinition = parametersDefinition?.filter(({ in: source }) => source === 'path');
const qsParamsDefinition = parametersDefinition?.filter(({ in: source }) => source === 'query');
this.debug = debug('openapi:parameter');
this.definition = { pathParamsDefinition, qsParamsDefinition, requestBodyDefinition };
this.debug('set parameter definition', JSON.stringify(this.definition));
this.setupSchema();
}
/**
* Setup the dynamic schema of parameters (path / query / body)
* @private
*/
setupSchema() {
this.schema = {
type: 'object',
required: [],
properties: {},
};
const mapParams = (array) => array.reduce((all, property) => ({
...all,
[property.name]: { ...property, ...property.schema },
}), {});
if (this.definition.pathParamsDefinition) {
this.schema.properties.path = {
type: 'object',
required: this.definition.pathParamsDefinition.filter((param) => param.required).map((i) => i.name),
properties: {
...mapParams(this.definition.pathParamsDefinition),
},
};
this.schema.required.push('path');
convertOpenAPIToSchemaInspector(this.schema.properties.path);
}
if (this.definition.qsParamsDefinition) {
this.schema.properties.qs = {
type: 'object',
required: this.definition.qsParamsDefinition.filter((param) => param.required).map((i) => i.name),
properties: {
...mapParams(this.definition.qsParamsDefinition),
},
};
this.schema.required.push('qs');
convertOpenAPIToSchemaInspector(this.schema.properties.qs);
}
if (this.definition.requestBodyDefinition) {
this.schema.properties.requestBody = parseContentTypesPayloads(this.definition.requestBodyDefinition);
this.schema.required.push('requestBody');
convertOpenAPIToSchemaInspector(this.schema.properties.requestBody);
}
this.debug('created validation schema', JSON.stringify(this.schema));
}
/**
* test an incoming request against the current instance of ParameterValidator
* @param {Map<string, any>} path
* @param {Map<string, any>} qs
* @param {Map<string, any>} requestBody
* @throws module:ParameterError
*/
test(path = {}, qs = {}, requestBody = {}) {
this.debug('testing parameter', path, qs, requestBody);
// TODO: check string byte / binary via custom validator
const result = inspector.validate(this.schema, { path, qs, requestBody });
if (!result.valid) {
this.debug('error found', result.format());
throw new ParameterError('INPUT_VALIDATION_FAILED', result.error);
}
}
}