Skip to content

Instantly share code, notes, and snippets.

@Radiergummi
Created August 11, 2021 05:54

Revisions

  1. Radiergummi created this gist Aug 11, 2021.
    274 changes: 274 additions & 0 deletions validators.ts
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,274 @@
    /**
    * Validates a value that isn't falsy.
    *
    * @param field
    * @param message
    */
    export function required(
    field?: string,
    message?: string | Message<string>,
    ): Validator {
    return validator(
    ( v?: string ) => !!v,
    msg( field, message || `${ field } is required` ),
    );
    }

    /**
    * Validates a string is an email address. Please note that this validator is
    * exceptionally forgiving by merely checking whether the value consists of
    * "something", followed by an @ sign, followed by "something" dot "something".
    * As validating email addresses is by and large pointless, this should be
    * enough to ensure we receive something that mail can be sent to.
    *
    * @param field Name of the field under validation.
    * @param message Message to show in case of an invalid value.
    * @see https://stackoverflow.com/questions/46155 for more discussion.
    */
    export function email(
    field?: string,
    message?: string | Message<string>,
    ): Validator<string> {
    return matches( /\S+@\S+\.\S+/, field, message || (
    () => `${ field } must be a valid email address`
    ) );
    }

    export function website(
    field?: string,
    message?: string | Message<string>,
    ): Validator<string> {
    return matches(
    /^((http|https):\/\/)?(www.)?(?!.*(ftp|http|https|www.))[a-zA-Z0-9_-]+(\.[a-zA-Z]+)+((\/)[\w#]+)*(\/\w+\?[a-zA-Z0-9_]+=\w+(&[a-zA-Z0-9_]+=\w+)*)?$/,
    field,
    message || (
    () => `${ field } must be a valid website`
    ),
    );
    }

    /**
    * Validates a string is shorter than a given amount of characters.
    *
    * @param expression Regular expression to match.
    * @param field Name of the field under validation.
    * @param message Message to show in case of an invalid value.
    */
    export function matches(
    expression: string | RegExp | ValueFn<string | RegExp>,
    field?: string,
    message?: string | Message<string>,
    ): Validator<string> {
    function unwrap( wrapped: string | RegExp | ValueFn<string | RegExp> ): RegExp {
    const value = val( wrapped );

    return typeof value === 'string'
    ? new RegExp( value )
    : value;
    }

    return validator<string>(
    ( v?: string ) => (
    !!v &&
    unwrap( expression ).exec( v ) !== null
    ),
    msg( field, message ),
    );
    }

    /**
    * Validates a string is shorter than a given amount of characters.
    *
    * @param characterAmount Maximum allowed number of characters.
    * @param field Name of the field under validation.
    * @param message Message to show in case of an invalid value.
    */
    export function shorterThan(
    characterAmount: number | ValueFn<number>,
    field?: string,
    message?: string | Message<string>,
    ): Validator {
    return validator(
    ( v?: string ) => (
    !v || (
    !!v && val<number>( characterAmount ) > v.length
    )
    ),
    msg(
    field,
    message || ( () => `${ field } must be shorter than ${ val<number>( characterAmount ) } characters.` ),
    ),
    );
    }

    /**
    * Validates a string is longer than a given amount of characters.
    *
    * @param characterAmount Minimum required number of characters.
    * @param field Name of the field under validation.
    * @param message Message to show in case of an invalid value.
    */
    export function longerThan(
    characterAmount: number | ValueFn<number>,
    field?: string,
    message?: string | Message<string>,
    ): Validator {
    return validator(
    ( v?: string ) => (
    !!v &&
    v.length > val<number>( characterAmount )
    ),
    msg(
    field,
    message || ( () => `${ field } must be longer than ${ val<number>( characterAmount ) } characters.` ),
    ),
    );
    }

    /**
    * Validates a string equals another string.
    *
    * @param value Value to compare against.
    * @param field Name of the field under validation.
    * @param message Message to show in case of an invalid value.
    */
    export function stringEquals(
    value: string | ( () => string ),
    field?: string,
    message?: string | Message<string>,
    ): Validator {
    return equals<string>(
    value,
    field,
    message,
    );
    }

    /**
    * Validates a string equals another string if both are converted to lowercase
    * characters only.
    *
    * @param value String to compare against.
    * @param field Name of the field under validation.
    * @param message Message to show in case of an invalid value.
    */
    export function lowerStringEquals(
    value: string | ( () => string ),
    field?: string,
    message?: string | Message<string>,
    ): Validator {
    return validator(
    ( v?: string ) => (
    !!v &&
    val<string>( value ).toLowerCase() === v.toLowerCase()
    ),
    msg( field, message ),
    );
    }

    export function truthy(
    field?: string,
    message?: string | Message<string>,
    ): Validator {
    return validator(
    ( v?: any ) => typeof v === 'string'
    ? [ 'on', 'true', 'yes' ].includes( v.toLowerCase() )
    : !!v,
    msg( field, message || `${ field } must be accepted` ),
    );
    }

    /**
    * Validates a value equals another value of a given type.
    *
    * @param value Value to compare against.
    * @param field Name of the field under validation.
    * @param message Message to show in case of an invalid value.
    */
    export function equals<T>(
    value: T | ValueFn<T>,
    field?: string,
    message?: string | Message<T>,
    ): Validator<T> {
    return validator<T>(
    ( v?: T ): boolean => (
    v === val<T>( value )
    ),
    msg<T>( field, message ),
    );
    }

    /**
    * Curries a validator function.
    *
    * @param predicate Predicate function to evaluate the value against.
    * @param message Message function to generate an error function if
    * validation fails.
    * @return Validator<T> Validator function for the given predicate and message.
    */
    export function validator<T>(
    predicate: Predicate<T>,
    message: Message<T>,
    ): Validator<T> {
    return ( v?: T ) => predicate( v ) || message( v ).trim();
    }

    /**
    * Evaluates value functions to comparison values or uses plain values otherwise
    * if given. Both value function results and values must be of type T.
    *
    * @param value
    * @return T
    */
    function val<T = any>( value: T | ValueFn<T> ): T {
    return typeof value === 'function'
    ? ( value as ValueFn<T> )()
    : value;
    }

    /**
    * Converts plain string messages or missing values to a valid message function
    * for a given value type T.
    *
    * @param field Name of the field under validation.
    * @param message Given message handler as passed to the validator.
    * @return Message<T> Valid message function.
    */
    function msg<T>( field?: string, message?: string | Message<T> ): Message<T> {
    if ( !message ) {
    return (): string => `${ field || 'Field' } is invalid`;
    }

    return typeof message === 'string'
    ? () => message
    : message;
    }

    /**
    * A value function for a given value type T that returns a value of type T.
    * This may be used to pass dynamic comparison expressions to validators that
    * should be evaluated at validation time instead of validator creation time.
    */
    type ValueFn<T> = () => T;

    /**
    * A predicate function for a given value type T that assesses whether a value
    * is valid. If the value under validation has not been provided, the parameter
    * will be undefined. The function returns a boolean true to indicate a valid
    * value, or a boolean false to indicate an invalid value.
    */
    export type Predicate<T> = ( value: T | undefined ) => boolean;

    /**
    * A message function to generate an error message for a given invalid value of
    * type T. It receives the field name and invalid value, if available, as its
    * parameters, and returns an error message.
    */
    export type Message<T> = ( value: T | undefined ) => string;

    /**
    * A validator function for a given value type T. It either returns true, if the
    * validator assessed a given value as valid, or a string message in case of an
    * invalid value.
    */
    export type Validator<T = any> = ( v?: T ) => true | string;