Skip to content
Snippets Groups Projects
Select Git revision
  • issue/81
  • develop default protected
  • master protected
3 results

validationHandler.ts

Blame
  • validationHandler.ts 10.16 KiB
    /*
     * form-creator-api. RESTful API to manage and answer forms.
     * Copyright (C) 2019 Centro de Computacao Cientifica e Software Livre
     * Departamento de Informatica - Universidade Federal do Parana - C3SL/UFPR
     *
     * This file is part of form-creator-api.
     *
     * This program is free software: you can redistribute it and/or modify
     * it under the terms of the GNU Affero General Public License as published by
     * the Free Software Foundation, either version 3 of the License, or
     * (at your option) any later version.
     *
     * This program is distributed in the hope that it will be useful,
     * but WITHOUT ANY WARRANTY; without even the implied warranty of
     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     * GNU Affero General Public License for more details.
     *
     * You should have received a copy of the GNU Affero General Public License
     * along with this program.  If not, see <https://www.gnu.org/licenses/>.
     */
    
    import { FormAnswer } from "../core/formAnswer";
    import { Input } from "../core/input";
    import { InputAnswer, InputAnswerDict } from "../core/inputAnswer";
    import { ValidationType } from "./enumHandler";
    import { ValidationDict, ValidationError } from "./validationError";
    
    /**
     * Validation's handler. Manage parse validation through the project.
     */
    export class ValidationHandler {
    
        /**
         * Validate a string according given a regex.
         * @param answer - Answer to be validated.
         * @param regex - Regex to validate answer.
         * @returns - True if answer match regex, else false.
         */
        private static validateByRegex(answer: string, regex: string): boolean {
            const regexp = new RegExp(regex);
            return regexp.test(answer);
        }
    
        /**
         * Validate if is null, undefined nor "".
         * @param answer - answer to be validated.
         * @returns - True if not null, "" nor undefined, else false.
         */
        private static validateMandatory(answer: string): boolean {
            return ((!answer) === false);
        }
    
        /**
         * Validate if answer has minimum number of chars.
         * @param answer - Answer to be validated.
         * @param size - Minimum size that answer should have.
         * @returns - True if has at least Size chars, else false.
         */
        private static validateMinChar(answer: string, size: string): boolean {
            return (answer !== null && answer !== undefined && parseInt(size, 10) <= answer.length);
        }
    
        /**
         * Validate if answer has minimum number of chars.
         * @param answer - Answer to be validated.
         * @param size - Maximum size that answer should have.
         * @returns - True if has at max Size chars, else false.
         */
        private static validateMaxChar(answer: string, size: string): boolean {
            return (answer !== null && answer !== undefined && parseInt(size, 10) >= answer.length);
        }
    
        /**
         * Validate if answer is of a determined type.
         * @param answer - Answer to be validated.
         * @param type - Type that answer should be.
         * @returns - True if it is of the determined type, else false.
         */
        private static validateTypeOf(answer: string, type: string): boolean {
            // Using string here to avoid validate validations
            if (type === "int") {
                return (!isNaN(parseInt(answer, 10)));
            } else if (type === "float") {
                return (!isNaN(parseFloat(answer)));
            } else if (type === "date") {
                return ((new Date(answer)).toString() !== "Invalid Date");
            } else {
                return (false);
            }
        }
    
        /**
         * Validate if answer has minimum one checkbox checked.
         * @param input - Input that checkbox belongs to.
         * @param inputAnswer - Answers to checkbox.
         * @returns - true if has at minimum one checkbox marked, else false.
         */
        private static validateSomeCheckbox(input: Input, inputAnswers: InputAnswerDict): boolean {
            let result: boolean = false;
            for (const answer of inputAnswers[input.id]) {
                if ((answer.value === "true") && this.inputSugestionExists(input, answer.placement)) {
                    result = true;
                }
            }
            return result;
        }
    
        /**
         * Validate if a sugestion exists.
         * @param input - Input that have sugestions to be verified.
         * @param placement - Value of answer to be verified.
         * @returns - True if sugestion exists, else false.
         */
        private static inputSugestionExists(input: Input, placement: number): boolean {
            let result: boolean = false;
            for (const sugestion of input.sugestions) {
                if (sugestion.placement === placement) {
                    result = true;
                }
            }
            return result;
        }
    
        /**
         * Validate if a input has a minimum number of answers.
         * @param inputAnswers - Dictionary of InputAnswers to be verified.
         * @param id - Input to be searched.
         * @param argument - Max number of answers.
         * @returns - True if has minimum answers, else false.
         */
        private static validateMaxAnswers(inputAnswers: InputAnswerDict, id: number, argument: string): boolean {
            const max: number = parseInt(argument, 10);
            // Verify if argument is an integer
            if (!(isNaN(max))) {
                return (inputAnswers[id].length <= max);
            } else {
                return false;
            }
        }
    
        /**
         * Validate if exists a answer for a dependent input.
         * @param inputAnswers - Dictionary of InputAnswers to be verified.
         * @param argument - Placement of the dependent input.
         * @returns - True if the input was answered, else false.
         */
        private static validateDependency(inputAnswers: InputAnswer[], argument: string): boolean {
            let result: boolean = false;
            const placement: number = parseInt(argument, 10);
            if (!(isNaN(placement))) {
                for (const inputAnswer of inputAnswers) {
                    if (inputAnswer.placement === placement) {
                        result = (inputAnswer.value === "true");
                    }
                }
            }
            return result;
        }
    
        /**
         * Validate if answer has minimum number of chars.
         * @param input - Input to validate answer.
         * @param answer - Answer of input.
         * @returns - A string with all errors.
         */
        private static validateInput(input: Input, inputAnswers: InputAnswerDict): string {
            const errors: string[] = [];
    
            let inputMandatory: boolean = false;
    
            for (const val of input.validation) {
                if (val.type === ValidationType.MANDATORY) {
                    inputMandatory = true;
                    break;
                }
            }
    
            if ((inputAnswers[input.id] === undefined || inputAnswers[input.id][0].value === "") && !(inputMandatory)) {
                return;
            }
    
            for (const validation of input.validation) {
    
                switch (validation.type) {
    
                    case ValidationType.REGEX:
                        for (const answer of inputAnswers[input.id]) {
                            if (!this.validateByRegex(answer.value, validation.arguments[0])) {
                                errors.push("RegEx do not match");
                            }
                        }
                        break;
    
                    case ValidationType.MANDATORY:
                        for (const answer of inputAnswers[input.id]) {
                            if (!(this.validateMandatory(answer.value))) {
                                errors.push("Input answer is mandatory");
                            }
                        }
                        break;
    
                    case ValidationType.MAXCHAR:
                        for (const answer of inputAnswers[input.id]) {
                            if (!(this.validateMaxChar(answer.value, validation.arguments[0]))) {
                                errors.push("Input answer must be lower than " + validation.arguments[0]);
                            }
                        }
                        break;
    
                    case ValidationType.MINCHAR:
                        for (const answer of inputAnswers[input.id]) {
                            if (!(this.validateMinChar(answer.value, validation.arguments[0]))) {
                                errors.push("Input answer must be greater than " + validation.arguments[0]);
                            }
                        }
                        break;
    
                    case ValidationType.TYPEOF:
                        for (const answer of inputAnswers[input.id]) {
                            if (!(this.validateTypeOf(answer.value, validation.arguments[0]))) {
                                errors.push("Input answer must be a " + validation.arguments[0]) + " type";
                            }
                        }
                        break;
    
                    case ValidationType.SOMECHECKBOX:
                        if (!(this.validateSomeCheckbox(input, inputAnswers))) {
                            errors.push("Input answer must have a answer");
                        }
                        break;
    
                    case ValidationType.MAXANSWERS:
                        if (!(this.validateMaxAnswers(inputAnswers, input.id, validation.arguments[0]))) {
                            errors.push("Number of input answers must be lower than " + validation.arguments[0]);
                        }
                        break;
    
                    case ValidationType.DEPENDENCY:
                        const id: number = parseInt(validation.arguments[0], 10);
                        if (!(isNaN(id)) && !(this.validateDependency(inputAnswers[id], validation.arguments[1]))) {
                            errors.push("Must answer question with id " + validation.arguments[0] + " and placement " + validation.arguments[1]);
                        }
                        break;
                }
            }
    
            return errors.join(";");
        }
    
        /**
         * Validate if form answer is valid.
         * @param formAnswer - FormAnswer to be validated.
         */
        public static validateFormAnswer(formAnswer: FormAnswer): void {
            const errorsDict: ValidationDict = {};
            for (const input of formAnswer.form.inputs) {
                const error: string = this.validateInput(input, formAnswer.inputAnswers);
                if (error !== "" && error !== undefined) {
                    errorsDict[input.id] = error;
                }
            }
    
            if (Object.keys(errorsDict).length > 0) {
                throw new ValidationError(errorsDict, "Validation Error");
            }
        }
    }