src/controllers/validation.js
File `validation.js` has 417 lines of code (exceeds 250 allowed). Consider refactoring./* ___ ___ ___ _ _ _ ___ ___ | __/ __| ___| _ \___ _ _ _ __ (_) |_ /_\ | _ \_ _| | _|\__ \ / -_) _/ -_) '_| ' \| | _| / _ \| _/| | |_| |___/ \___|_| \___|_| |_|_|_|_|\__| /_/ \_\_| |___| */ //******************************************************************* 'use strict'; //*******************************************************************// required modulesconst Validator = require('jsonschema').Validator;const include = require('include')(__dirname); //*******************************************************************// other files const errors = require('./errors/patternErrorMessages.json');const fileValidation = require('./fileValidation.js'); const v = new Validator(); /** * Removes 'instance' from prop field of validation errors. Used to make fields human readable * * @param {string} prop - Prop field from validation error * @return {string} */function removeInstance(prop){ let fixedProp = ''; if (prop.indexOf('.') !== -1){ fixedProp = prop.substring((prop.indexOf('.') + 1), (prop.length)); } return fixedProp; } /** * Combines property and argument fields, if property exists, for missing field errors * * @param {string} property - Upper field to combine * @param {string} argument - Field where error is. * @return {string} - Concatination of property, '.', and argument */function combinePropArgument(property, argument){ let field; if (property.length > 0){ field = `${property}.${argument}`; } else { field = argument; } return field; }/** * Creates error object which can be read by error message building function * * @param {string} field - Field where error occured at * @param {string} errorType - Type of error returned * @param {string} expectedFieldType - Type that the field is expected to be * @param {string} enumMessage - Enum message returned by validation * @param {string} dependency - Fields that are a dependeny of field * @param {array} anyOfFields - Array of strings of all field included in anyOf * * @return Error object */Function `makeErrorObj` has 6 arguments (exceeds 4 allowed). Consider refactoring.function makeErrorObj(field, errorType, expectedFieldType, enumMessage, dependency, anyOfFields){ const output = { field, errorType, expectedFieldType, enumMessage, dependency, anyOfFields }; let key; for (key in output){ if (output[key] === null){ delete output[key]; } } return output;} let requiredFields = [];/** * Checks for additional required fields if a missing field has sub fields, stores these fields in requiredFields * @param {Object} schema - schema to traverse in search for all required fields */function checkForExtraRequired(schema){ const keys = schema.properties; for (const key in keys){ if (schema.properties[key].type === 'object' && schema.required.includes(key)){ const indexOfSuper = requiredFields.indexOf(key) + 1; requiredFields.splice(indexOfSuper, 0, ...schema.properties[key].required.map(function(s){ return `${key}.${s}`; })); checkForExtraRequired(schema.properties[key]); } }}/** Traverses schema object in search of all fields listed as required. Stores all fields in requiredFiles array. * @param {Object} schema - schema to traverse in search for all required fields */function getAllRequired(schema){ const keys = Object.keys(schema); keys.forEach((key)=>{ switch (key){ case 'allOf': schema.allOf.forEach((sch)=>{ getAllRequired(sch); }); break; case 'properties': getAllRequired(schema.properties); break; case 'required': requiredFields = requiredFields.concat(schema.required); checkForExtraRequired(schema); } });}/** Traverses through schema to find field specified. Once found it executes a function on that field in the schema. * @param {Object} schema - schema to look for field in * @param {Array} field - Array(String) containing the path to the field to find * @param {Function} func - Function to be run on the schema of field */Function `findField` has 26 lines of code (exceeds 25 allowed). Consider refactoring.function findField(schema, field, func){ const fieldCopy = JSON.parse(JSON.stringify(field)); const schemaKeys = Object.keys(schema); schemaKeys.forEach((key)=>{ if (key === fieldCopy[0]){ if (fieldCopy.length === 1){ func(schema[key]); } else { fieldCopy.shift(); findField(schema[key], fieldCopy, func); } } else { switch (key){ case 'allOf': case 'oneOf': schema[key].forEach((sch)=>{ findField(sch, fieldCopy, func); }); break; case 'properties': findField(schema.properties, fieldCopy, func); break; } } });} /** * Handles errors where a required field is missing. * @param {Object} output - Object used to keep track of any errors, will be outputted if any found * @param {Array} output.errorArray - Array containing error objects which detail errors in schema * @param {Array} result - Array of all errors from schema validator * @param {Number} counter - Index of the current error * @param {Object} schema - schema which input is being validated against */function handleMissingError(output, result, counter, schema){ requiredFields = []; const property = removeInstance(result[counter].property); const field = combinePropArgument(property, result[counter].argument); output.errorArray.push(makeErrorObj(field, 'missing')); findField(schema, field.split('.'), getAllRequired); for (const i in requiredFields){ if (requiredFields.hasOwnProperty(i)) { requiredFields[i] = `${field}.${requiredFields[i]}`; } } requiredFields.forEach((requiredField)=>{ output.errorArray.push(makeErrorObj(requiredField, 'missing')); });} /** * Handles errors where a field is the wrong type. * @param {Object} output - Object used to keep track of any errors, will be outputted if any found * @param {Array} output.errorArray - Array containing error objects which detail errors in schema * @param {Array} result - Array of all errors from schema validator * @param {Number} counter - Index of the current error */function handleTypeError(output, result, counter){ const expectedType = result[counter].argument[0]; const property = removeInstance(result[counter].property); output.errorArray.push(makeErrorObj(property, 'type', expectedType)); } /** * Handles errors where a field is formatted wrong. * @param {Object} output - Object used to keep track of any errors, will be outputted if any found * @param {Array} output.errorArray - Array containing error objects which detail errors in schema * @param {Array} result - Array of all errors from schema validator * @param {Number} counter - Index of the current error */function handleFormatError(output, result, counter){ const field = `${removeInstance(result[counter].property)}`; output.errorArray.push(makeErrorObj(field, 'format')); } /** * Handles errors where a field is not one of the enum values. * @param {Object} output - Object used to keep track of any errors, will be outputted if any found * @param {Array} output.errorArray - Array containing error objects which detail errors in schema * @param {Array} result - Array of all errors from schema validator * @param {Number} counter - Index of the current error */function handleEnumError(output, result, counter){ const property = removeInstance(result[counter].property); output.errorArray.push(makeErrorObj(property, 'enum', null, result[counter].message)); } /** * Pulls the dependency of a certain field from the error message generated by the schema validator * @param {Array} result - Array of all errors from schema validator * @param {Number} counter - Index of the current error */function getDependency(result, counter){ const stackMessage = result[counter].stack; const dependency = stackMessage.split(' property ')[1].split(' not ')[0]; return dependency; } /** * Handles errors where a field has a dependency which is not provided. * @param {Object} output - Object used to keep track of any errors, will be outputted if any found * @param {Array} output.errorArray - Array containing error objects which detail errors in schema * @param {Array} result - Array of all errors from schema validator * @param {Number} counter - Index of the current error */function handleDependencyError(output, result, counter){ const error = result[counter]; const dependentField = removeInstance(error.argument); const schemaPath = removeInstance(error.property); const dependency = `${schemaPath}.${getDependency(result, counter)}`; output.errorArray.push(makeErrorObj(dependentField, 'dependencies', null, null, dependency)); } /** * Creates error object for errors resulting from an anyOf section of the validation schema * * @param {Object} errorTracking - Error object containing all error to report and the error message to deliver. * @param {Array} errorTracking.errorArray - Array contain all errors to report to user. * @param {Array} result - Array of errors found during validation. * @param {Number} counter - Position in result that the current error being handled is. */function handleAnyOfError(errorTracking, result, counter){ const error = result[counter]; const property = removeInstance(error.property); const requiredOptions = []; error.schema.anyOf.forEach((fieldObj)=>{ requiredOptions.push(combinePropArgument(property, fieldObj.required[0])); }); errorTracking.errorArray.push(makeErrorObj(null, 'anyOf', null, null, null, requiredOptions)); } /** Get the schema to be used for validating user input * @param {Object} pathData - All data from swagger for the path that has been run * @return {Object} schemas - fullSchema is the full validation schemas for all permit types. schemaToUse is the validation schema for this route */function getValidationSchema(pathData){ const fileToGet = `src/${pathData['x-validation'].split('#')[0]}`; const schemaToGet = pathData['x-validation'].split('#')[1]; const applicationSchema = include(fileToGet); return { 'fullSchema':applicationSchema, 'schemaToUse':applicationSchema[schemaToGet] };} /** Processes ValidationError into ErrorObj, extracting the info needed to create an error message * @param {Array} - Array of ValidationErrors from validation * @param {Array} - Array to store processed ErrorObjs in */function processErrors(errors, processedErrors, schema){ const length = errors.length; let counter; for (counter = 0; counter < length; counter++){ switch (errors[counter].name){ case 'required': handleMissingError(processedErrors, errors, counter, schema); break; case 'type': handleTypeError(processedErrors, errors, counter); break; case 'format': case 'pattern': handleFormatError(processedErrors, errors, counter); break; case 'enum': handleEnumError(processedErrors, errors, counter); break; case 'dependencies': handleDependencyError(processedErrors, errors, counter); break; case 'anyOf': handleAnyOfError(processedErrors, errors, counter); break; } }} /** Validates the fields in user input * @param {Object} body - Input from user to be validated * @param {Object} pathData - All data from swagger for the path that has been run * @param {Object} validationSchema - schema to be used for validating input, same as validation.json without refs * @return {Array} - Array of ValidationErrors from validation */function validateBody(body, pathData, validationSchema){ const processedFieldErrors = { errorArray:[] }; const schema = getValidationSchema(pathData); const applicationSchema = schema.fullSchema; const schemaToUse = schema.schemaToUse; let key; for (key in applicationSchema){ if (applicationSchema.hasOwnProperty(key)) { v.addSchema(applicationSchema[key], key); } } const val = v.validate(body, schemaToUse); const error = val.errors; if (error.length > 0){ processErrors(error, processedFieldErrors, validationSchema); } return processedFieldErrors;} /** * Takes input like fieldOne and converts it to Field One so that it is easier to read * @param {String} input - String to be made more readable * @return {String} - More readble string */function makeFieldReadable(input){ return input .replace(/([A-Z])/g, ' $1') .replace(/^./, function(str){ return str.toUpperCase(); }) .replace('Z I P', 'Zip') .replace('U R L', 'URL'); } /** * Takes input like fieldOne.fieldTwo and converts it to Field One/Field Two to make it easier to read * @param {String} input - path to field which has error * @return {String} - human readable path to errored field */function makePathReadable(input){ if (typeof input === 'string'){ const parts = input.split('.'); const readableParts = []; let readablePath = ''; parts.forEach((field)=>{ readableParts.push(makeFieldReadable(field)); }); readablePath = readableParts.shift(); readableParts.forEach((part)=>{ readablePath = `${readablePath}/${part}`; }); return readablePath; } else { return false; } } /** * Creates error message for format errors * * @param {String} fullPath - path to field where error is at * @return {String} - error message to be given to user */function buildFormatErrorMessage(fullPath){ const field = fullPath.substring(fullPath.lastIndexOf('.') + 1); const readablePath = makePathReadable(fullPath); const errorMessage = `${readablePath}${errors[field]}`; return errorMessage; } /** * Creates error message for anyOf errors * * @param {array} anyOfFields - list of fields, at least one being required. * @return {string} */function makeAnyOfMessage(anyOfFields){ if (anyOfFields){ let output, count = 1; const length = anyOfFields.length; output = `${makePathReadable(anyOfFields[0])}`; while (count < length) { const field = anyOfFields[count]; output = `${output} or ${makePathReadable(field)}`; count ++; } return output; } else { return false; }} /** * Combines all errors into one string which can be used to determine where all errors are at * @param {Array} errorMessages - Array of error objects * @return {String} - Error message containing all errors */function concatErrors(errorMessages){ let output = ''; errorMessages.forEach((message)=>{ output = `${output}${message} `; }); output = output.trim(); return output;} /** * Creates error messages for all field errors * @param {Object} output - Error object containing all error to report and the error message to deliver. * @param {Array} output.errorArray - Array contain all errors to report to user. * @param {Object} error - error object to be processed * @param {Array} messages - Array of all error messages to be returned * @return {String} - All field error messages concated together */Function `generateErrorMesage` has 46 lines of code (exceeds 25 allowed). Consider refactoring.function generateErrorMesage(output){ let errorMessage = ''; const messages = []; output.errorArray.forEach((error)=>{ const missing = `${makePathReadable(error.field)} is a required field.`; const type = `${makePathReadable(error.field)} is expected to be type '${error.expectedFieldType}'.`; const enumMessage = `${makePathReadable(error.field)} ${error.enumMessage}.`; const dependencies = `Having ${makePathReadable(error.field)} requires that ${makePathReadable(error.dependency)} be provided.`; const anyOf = `Either ${makeAnyOfMessage(error.anyOfFields)} is a required field.`; const length = `${makePathReadable(error.field)} is too long, must be ${error.expectedFieldType} chracters or shorter`; switch (error.errorType){ case 'missing': messages.push(missing); error.message = missing; break; case 'type': messages.push(type); error.message = type; break; case 'format': case 'pattern': messages.push(buildFormatErrorMessage(error.field)); error.message = buildFormatErrorMessage(error.field); break; case 'enum': messages.push(enumMessage); error.message = enumMessage; break; case 'dependencies': messages.push(dependencies); error.message = dependencies; break; case 'anyOf': messages.push(anyOf); error.message = anyOf; break; case 'length': messages.push(length); error.message = length; break; default: fileValidation.generateFileErrors(output, error, messages); break; } }); errorMessage = concatErrors(messages); return errorMessage; } /** * Checks the length of all fields with a maxLength field in schema * @param {Object} schema - Section of the validation schema being used * @param {Object} input - User input being validated * @param {Object} processedFieldErrors - Current object containing errors * @param {Array} processedFieldErrors.errorArray - Array of all errors found so far * @param {String} path - Path to field being checked * @return {Array} - Array of error objects representing all errors found so far */Function `checkFieldLengths` has 39 lines of code (exceeds 25 allowed). Consider refactoring.function checkFieldLengths(schema, input, processedFieldErrors, path){ const keys = Object.keys(schema); keys.forEach((key)=>{ switch (key){ case 'allOf': case 'anyOf': schema[key].forEach((sch)=>{ checkFieldLengths(sch, input, processedFieldErrors, path); }); break; case 'properties': checkFieldLengths(schema.properties, input, processedFieldErrors, path); break; default:{ let field; if (path === ''){ field = `${key}`; } else { field = `${path}.${key}`; } if (schema[key].type === 'object'){ if (input[key]){ checkFieldLengths(schema[key], input[key], processedFieldErrors, field); } } else if (schema[key].fromIntake){ if (input){ const maxLength = schema[key].maxLength; const fieldLength = `${input[key]}`.length; Avoid deeply nested control flow statements. if (maxLength < fieldLength){ processedFieldErrors.errorArray.push(makeErrorObj(field, 'length', maxLength)); } } } break; } } }); return processedFieldErrors;} /** * Checks that individualIsCitizen field is present if application is a temp-outfitters application and it is for an individual * @param {Object} input - User input * @param {Object} processedFieldErrors - Object containing all errors from the validation process * @return {Object} - processedFieldErrors with any errors from this validation step added to it */Function `checkForIndividualIsCitizen` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.function checkForIndividualIsCitizen(input, processedFieldErrors){ if (input.tempOutfitterFields && input.applicantInfo){ if (!input.applicantInfo.orgType || input.applicantInfo.orgType.toUpperCase() === 'PERSON'){ if ((typeof input.tempOutfitterFields.individualIsCitizen) !== 'boolean'){ processedFieldErrors.errorArray.push(makeErrorObj('tempOutfitterFields.individualIsCitizen', 'missing')); } } } return processedFieldErrors;} /** * Checks that smallBusiness field is present if application is a temp-outfitters application and it is not for an individual * @param {Object} input - User input * @param {Object} processedFieldErrors - Object containing all errors from the validation process * @return {Object} - processedFieldErrors with any errors from this validation step added to it */Function `checkForSmallBusiness` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.function checkForSmallBusiness(input, processedFieldErrors){ if (input.tempOutfitterFields && input.applicantInfo){ if (input.applicantInfo.orgType && input.applicantInfo.orgType.toUpperCase() !== 'PERSON'){ if ((typeof input.tempOutfitterFields.smallBusiness) !== 'boolean'){ processedFieldErrors.errorArray.push(makeErrorObj('tempOutfitterFields.smallBusiness', 'missing')); } } } return processedFieldErrors;} /** * Checks that organizationName field is present if application is not for an individual * @param {Object} input - User input * @param {Object} processedFieldErrors - Object containing all errors from the validation process * @return {Object} - processedFieldErrors with any errors from this validation step added to it */Function `checkForOrgName` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.function checkForOrgName(input, processedFieldErrors){ if (input.applicantInfo){ if (input.applicantInfo.orgType && input.applicantInfo.orgType.toUpperCase() !== 'PERSON'){ if (!input.applicantInfo.organizationName || input.applicantInfo.organizationName.length <= 0){ processedFieldErrors.errorArray.push(makeErrorObj('applicantInfo.organizationName', 'missing')); } } } return processedFieldErrors;} /** * Additional validation checks that can't be defined in the validation schema * @param {Object} validationSchema - schema to be used for validating input, same as validation.json without refs * @param {Object} input - User input * @param {Object} processedFieldErrors - Object containing an array of error objects for every error with fields * @return {Object} - processedFieldErrors with any errors from these validation steps added to it */function additionalValidation(validationSchema, input, processedFieldErrors){ processedFieldErrors = checkFieldLengths(validationSchema, input, processedFieldErrors, ''); processedFieldErrors = checkForOrgName(input, processedFieldErrors); processedFieldErrors = checkForIndividualIsCitizen(input, processedFieldErrors); processedFieldErrors = checkForSmallBusiness(input, processedFieldErrors); return processedFieldErrors;} /** * Drives validation of fields * @param {Object} body - User input * @param {Object} pathData - information about path * @param {Object} validationSchema - schema to be used for validating input, same as validation.json without refs * @return {Object} - Object containing an array of error objects for every error with fields */function getFieldValidationErrors(body, pathData, validationSchema){ let processedFieldErrors = validateBody(body, pathData, validationSchema); processedFieldErrors = additionalValidation(validationSchema, body, processedFieldErrors, ''); return processedFieldErrors;} module.exports.removeInstance = removeInstance;module.exports.combinePropArgument = combinePropArgument;module.exports.makeErrorObj = makeErrorObj;module.exports.getAllRequired = getAllRequired;module.exports.findField = findField;module.exports.handleMissingError = handleMissingError;module.exports.handleTypeError = handleTypeError;module.exports.handleFormatError = handleFormatError;module.exports.handleEnumError = handleEnumError;module.exports.getDependency = getDependency;module.exports.handleDependencyError = handleDependencyError;module.exports.handleAnyOfError = handleAnyOfError;module.exports.getValidationSchema = getValidationSchema;module.exports.validateBody = validateBody;module.exports.processErrors = processErrors;module.exports.makeFieldReadable = makeFieldReadable;module.exports.makePathReadable = makePathReadable;module.exports.buildFormatErrorMessage = buildFormatErrorMessage;module.exports.makeAnyOfMessage = makeAnyOfMessage;module.exports.concatErrors = concatErrors;module.exports.generateErrorMesage = generateErrorMesage;module.exports.getFieldValidationErrors = getFieldValidationErrors;module.exports.checkForSmallBusiness = checkForSmallBusiness;module.exports.checkForIndividualIsCitizen = checkForIndividualIsCitizen;module.exports.checkForOrgName = checkForOrgName;