lib/paths/path.js
'use strict'; const logger = require('lllog')();const util = require('util'); const ResponseGenerator = require('../response-generator');const SchemaValidator = require('../schema-validator'); class Path { constructor({ uri, httpMethod, parameters, requestBody, responses }) { this.uri = uri; this.httpMethod = httpMethod; this.parameters = parameters || []; this.requestBody = requestBody; this.responses = responses; } validateRequestParameters({ headers, query, path, cookies, requestBody }) { const request = { headers, query, path, cookies }; return [ ...this.validateRequestBody(requestBody), ...this.parameters .map(parameter => this.validateParameter(parameter, request)) .filter(validation => !!validation) ]; } Function `validateRequestBody` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring. validateRequestBody(requestBody) { if(!this.requestBody) return []; if(!requestBody) { if(this.requestBody.required) return ['Missing required request body']; // If body wasn't required, then there's no problem return []; } const { content } = this.requestBody; if(!content || !content['application/json'] || !content['application/json'].schema) { // Cannot validate the body if it's application/json content is not defined logger.warn('Missing application/json content for request body'); return []; } // Validate the body const { schema } = content['application/json']; try { const validationErrors = SchemaValidator.validate(requestBody, schema); return validationErrors.map(error => { const cleanDataPath = error.dataPath.replace(/^\./, ''); return `Invalid request body:${cleanDataPath && ` '${cleanDataPath}'`} ${error.message}`; }); } catch(e) { logger.debug(e.stack); return [e.message]; } } validateParameter(parameter, { headers, query, path, cookies }) { switch(parameter.in) { case 'header': return this._validateParameter({ ...parameter, name: parameter.name.toLowerCase() }, headers); case 'query': return this._validateParameter(parameter, query); case 'path': return this._validateParameter(parameter, path); case 'cookie': return this._validateParameter(parameter, cookies); default: return `Invalid declaration for ${parameter.in} param ${parameter.name}`; } } _validateParameter(parameter, requestParameters) { const { in: paramIn, name, required, deprecated } = parameter; if(required && typeof requestParameters[name] === 'undefined') return `Missing required ${paramIn} param ${name}`; // Optional parameters not sent are always valid if(typeof requestParameters[name] === 'undefined') return; // If a deprecated parameter is received, leave a warning if(deprecated) logger.warn(`Using deprecated ${paramIn} param ${name}`); return this.validateParameterSchema(parameter, requestParameters[name]); } validateParameterSchema(parameter, value) { const { in: paramIn, name, schema } = parameter; if(!schema) { // Cannot validate a parameter if it's schema is not defined logger.warn(`Missing schema for ${paramIn} param ${name}`); return; } if(!schema.type) { logger.warn(`Missing schema type for ${paramIn} param ${name}`); return; } return this.validateParameterType(parameter, value) || this.validateParameterEnum(parameter, value); } validateParameterType({ in: paramIn, name, schema }, value) { try { const error = this.isValidType(schema.type, value); if(error) return `Invalid ${paramIn} param ${name}. Expected value of type ${schema.type} but received ${util.inspect(value)}`; } catch(e) { return `${e.message} for ${paramIn} param ${name}`; } } isValidType(type, value) { switch(type) { case 'array': return !Array.isArray(value); case 'object': return typeof value !== 'object' || Array.isArray(value); case 'string': return typeof value !== 'string'; case 'number': return Number.isNaN(Number(value)); case 'integer': return Number.isNaN(Number(value)) || (parseInt(Number(value), 10)) !== Number(value); case 'boolean': return value !== (!!value) && value !== 'true' && value !== 'false'; default: throw new Error(`Invalid type declaration ${type}`); } } validateParameterEnum({ in: paramIn, name, schema }, value) { if(!this.isValidEnumValue(schema.enum, value)) { const enumAsString = schema.enum .map(util.inspect) .join(', '); return `Invalid ${paramIn} param ${name}. Expected enum of [${enumAsString}] but received ${util.inspect(value)}`; } } isValidEnumValue(possibleValues, value) { return !possibleValues || !possibleValues.length || possibleValues.includes(value); } getResponse(preferredStatusCode, preferredExampleName) { const { statusCode, headers, schema, responseMimeType } = preferredStatusCode ? this.getResponseByStatusCode(preferredStatusCode) : this.getFirstResponse(); return { statusCode: Number(statusCode), headers: headers && this.generateResponseHeaders(headers), body: schema ? ResponseGenerator.generate(schema, preferredExampleName) : null, responseMimeType }; } getResponseByStatusCode(statusCode) { if(!this.responses[statusCode]) { logger.warn(`Could not find a response for status code ${statusCode}. Responding with first response`); return this.getFirstResponse(); } const preferredResponse = this.responses[statusCode]; const [[responseMimeType, responseContent] = []] = Object.entries(preferredResponse.content || {}); return { statusCode, schema: responseContent, responseMimeType, headers: preferredResponse.headers }; } getFirstResponse() { const [[statusCode, firstResponse]] = Object.entries(this.responses); const [[responseMimeType, firstResponseContent] = []] = Object.entries(firstResponse.content || {}); return { statusCode, schema: firstResponseContent, responseMimeType, headers: firstResponse.headers }; } generateResponseHeaders(headersSchema) { const responseHeaders = {}; for(const [headerName, headerData] of Object.entries(headersSchema)) responseHeaders[headerName] = ResponseGenerator.generate(headerData); return responseHeaders; } } module.exports = Path;