UNPKG

openapi-validator-utils

Version:

OpenAPI request, parameters, headers and response validator methods

217 lines (195 loc) 7.79 kB
const Ajv = require('ajv').default; const addFormats = require('ajv-formats'); const defaultOptions = require('./config/default'); const { formatComponents, configError, sanitizeValueSchema, } = require('./utils'); const { argsValidation, ajvErrors, inputValidation, endpointValidation, optionsValidation, responseArgsValidation, } = require('./validators'); /** * @name ValidateRequest * @function * @param {*} value Value we want to validate * @param {string} endpoint OpenApi endpoint we want to validate * @param {string} method OpenApi method we want to validate * @param {string} contentType Content api of the request we want to validate */ /** * @name ValidateParams * @function * @param {*} value Value we want to validate * @param {string} endpoint OpenApi endpoint we want to validate * @param {string} method OpenApi method we want to validate * @param {string} contentType Content api of the request we want to validate */ /** * @name ValidateResponse * @function * @param {*} value Value we want to validate * @param {string} endpoint OpenApi endpoint we want to validate * @param {string} method OpenApi method we want to validate * @param {string} status OpenApi status we want to validate * @param {string} contentType Content api of the request we want to validate */ /** * @name IsRequestRequired * @function * @param {string} endpoint OpenApi endpoint we want to validate * @param {string} method OpenApi method we want to validate * @param {string} contentType Content api of the request we want to validate */ /** * @name ValidateRequiredValues * @function * @param {*} value Values we want to see if are send as required parameters * @param {string} endpoint OpenApi endpoint we want to validate * @param {string} method OpenApi method we want to validate */ /** * Validator methods * @typedef {object} ValidatorMethods * @property {ValidateRequest} validateRequest * @property {ValidateParams} validateQueryParam * @property {ValidateParams} validatePathParam * @property {ValidateParams} validateHeaderParam * @property {ValidateResponse} validateResponse * @property {IsRequestRequired} isRequestRequired * @property {ValidateRequiredValues} validateRequiredValues */ /** * Validate method * @param {object} openApiDef OpenAPI definition * @param {object} options Options to extend the errorHandler or Ajv configuration * @returns {ValidatorMethods} validator methods */ const validate = (openApiDef, userOptions = {}) => { const options = { ...defaultOptions, ...(userOptions || {}), }; const { errorHandler } = options; const inputValidationError = inputValidation(openApiDef); configError(inputValidationError, errorHandler); const optionsValidationError = optionsValidation(userOptions); configError(optionsValidationError, errorHandler); const schemaOptions = { strictValidation: options.strictValidation }; const defsSchema = { $id: 'defs.json', definitions: { components: formatComponents(openApiDef.components, schemaOptions), }, }; const ajv = new Ajv({ schemas: [defsSchema], ...(options.ajvConfig || {}), }); addFormats(ajv); // Obtain method to validate against a given schema const getSchemaValidator = (schemaName, schema) => { // Slashes (/) are not supported in schema names, so we must replace them const validSchemaName = schemaName.replace(/\//g, '-'); let validateSchema = ajv.getSchema(validSchemaName); // Compile schema just once, only if it hasn't been defined previously if (!validateSchema) { ajv.addSchema(schema, validSchemaName); validateSchema = ajv.getSchema(validSchemaName); } return validateSchema; }; // Validate is current value matches the structure in the selected schema const schemaValidation = (value, schema, type, schemaName) => { const validateSchema = getSchemaValidator(schemaName, schema); const valid = validateSchema(value); if (!valid) { return ajvErrors(validateSchema.errors, value, type, errorHandler); } return true; }; const validateRequiredValues = (values, endpoint, method) => { const argsValidationError = argsValidation(values, endpoint, method); configError(argsValidationError, errorHandler); const requiredParamsError = endpointValidation.requiredParams( values, openApiDef, endpoint, method, ); configError(requiredParamsError, errorHandler); return true; }; const validateResponse = (value, endpoint, method, status, contentType = 'application/json') => { const argsValidationError = responseArgsValidation(value, endpoint, method, status); configError(argsValidationError, errorHandler); const responseEndpoint = endpointValidation.response( openApiDef, endpoint, method, status, contentType, ); configError(responseEndpoint, errorHandler); let responseSchema = { ...openApiDef.paths[endpoint][method].responses[status].content[contentType].schema, }; responseSchema = formatComponents(responseSchema); const schemaName = `${method}-${endpoint}-${status}-${contentType}-response`; return schemaValidation(value, responseSchema, 'response', schemaName); }; const isRequestRequired = (endpoint, method, contentType = 'application/json') => { try { if (method === 'get') return false; const argsValidationError = argsValidation('request', endpoint, method); configError(argsValidationError, errorHandler); const requestEndpoint = endpointValidation.request(openApiDef, endpoint, method, contentType); configError(requestEndpoint, errorHandler); return !!openApiDef.paths[endpoint][method].requestBody.required; } catch (error) { // When we receive this is because it was not documented // Document request body it is not required there might be endpoints // where you don't want request body if (error.message.includes('does not have requestBody definition')) return false; throw error; } }; const validateRequest = (value, endpoint, method, contentType = 'application/json') => { const argsValidationError = argsValidation(value, endpoint, method); configError(argsValidationError, errorHandler); const requestEndpoint = endpointValidation.request(openApiDef, endpoint, method, contentType); configError(requestEndpoint, errorHandler); let requestBodySchema = { ...openApiDef.paths[endpoint][method].requestBody.content[contentType].schema, }; requestBodySchema = formatComponents(requestBodySchema); const schemaName = `${method}-${endpoint}-${contentType}-request`; return schemaValidation(value, requestBodySchema, 'request', schemaName); }; const validateParam = type => (value, key, endpoint, method) => { const argsValidationError = argsValidation(value, endpoint, method, key); configError(argsValidationError, errorHandler); const paramEndpoint = endpointValidation.params(openApiDef, endpoint, method, key, type); configError(paramEndpoint, errorHandler); let parametersSchema = paramEndpoint.parameter.schema; parametersSchema = formatComponents(parametersSchema); const sanitizeValue = sanitizeValueSchema(value, parametersSchema); const schemaName = `${method}-${endpoint}-${key}-${type}-param`; return schemaValidation(sanitizeValue, parametersSchema, type, schemaName); }; return { validateRequest, validateQueryParam: validateParam('query'), validatePathParam: validateParam('path'), validateHeaderParam: validateParam('header'), validateResponse, validateRequiredValues, isRequestRequired, }; }; module.exports = validate;