jormaechea/open-api-mocker

View on GitHub
lib/paths/path.js

Summary

Maintainability
A
35 mins
Test Coverage
A
100%
'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;