/* * Copyright (c) 2022. * Author Peter Placzek (tada5hi) * For the full copyright and license information, * view the LICENSE file that was distributed with this source code. */ import {IValidator, ValidationResult} from "./index"; import {keys} from "memory-cache"; type StringRule = | { type: "equal", value: string } | { type: "notEqual", value: string } | { type: "minLength", min: number } | { type: "maxLength", max: number } export class StringValidator implements IValidator { constructor(private rules?: StringRule[]) { if (!Array.isArray(this.rules)) { this.rules = []; } } /** * Adds a rule to the array of rules, or replaces a rule if it already exists. * Use this function to prevent having multiple rules of the same type. */ private addRule: (rule: StringRule) => StringRule[] = rule => { // Filter the current rule set, removing any rule that has the same type of the one being added const filtered = this.rules.filter(r => r.type !== rule.type); // Add the new rule to the filtered rule array return [...filtered, rule] } /** * Fails if the value being validated is not equal to @param value. */ equals: (value: string) => StringValidator = value => { this.rules = this.addRule({ type: "equal", value: value }); return this; } /** * Fails if the value being validated is equal to @param value. */ notEquals: (value: string) => StringValidator = value => { this.rules = this.addRule({ type: "notEqual", value: value }); return this; } /** * Fails if the string's length is less than @param min. */ minLength: (min: number) => StringValidator = min => { this.rules = this.addRule({ type: "minLength", min: min }); return this; } /** * Fails if the string's length is greater than @param max. */ maxLength: (max: number) => StringValidator = max => { this.rules = this.addRule({ type: "maxLength", max: max }); return this; } /** * Fails if the string is empty. */ notEmpty: () => StringValidator = () => { // We don't need to use a specific rule for notEmpty here, we can just set a min length of 1! this.rules = this.addRule({ type: "minLength", min: 1 }); return this; } /** * Fails if the string is not empty. NOTE that an empty string is _not_ the same as a null or undefined value. */ empty: () => StringValidator = () => { // Again, we don't need a specific rule for empty, we just set a max length of 0 this.rules = this.addRule({ type: "maxLength", max: 0 }); return this; } /** * Checks an individual rule against the value being validated. */ checkRule: (rule: StringRule, value: string, key: string) => ValidationResult = (rule: StringRule, value : string, key: string) => { const err = (msg: string) : ValidationResult => ({ success: false, message: msg }); const ok = () : ValidationResult => ({ success: true, value: value }); switch (rule.type) { case "equal": return rule.value !== value ? err(`${key} was expected to be ${rule.value} but was ${value}.`) : ok(); case "notEqual": return rule.value === value ? err(`${key} must not be ${rule.value}.`) : ok(); case "minLength": return value.length < rule.min ? err(`${key} length must be greater than or equal to ${rule.min} but was ${value.length}.`) : ok(); case "maxLength": return value.length > rule.max ? err(`${key} length must be less than or equal to ${rule.max} but was ${value.length}.`) : ok(); } } go: (value: unknown, key?: string) => ValidationResult = (value, key) => { // Since the value is unknown, we must check that the type is string before validating each rule key = key ?? 'value'; if (value === null) { return { success: false, message: "expected "+key+" to be string but received null." } } else if (value === undefined) { return { success: false, message: "expected "+key+" to be string but received undefined." } } else if (typeof value !== "string") { return { success: false, message: `expected ${key} to be string but received ${typeof value}.` } } // TypeScript compiler now knows that value is a string // Iterate over all rules and short-circuit to return an error if any rule fails for (let rule of this.rules) { const result = this.checkRule(rule, value, key); if (result.success === false) { return result; } } // If none of the rules in the loop had an error, the value passed validation! return { success: true, value: value } } }