NAME

Dancer2::Plugin::FormValidator - neat and easy to start form validation plugin for Dancer2.

VERSION

version 0.71

SYNOPSIS

### If you need a simple and easy validation in your project,
### then this module is what you need.

use Dancer2::Plugin::FormValidator;

### First create form validation profile class.

package RegisterForm {
     use Moo;
     with 'Dancer2::Plugin::FormValidator::Role::Profile';

    ### Here you need to declare validators.

    sub profile {
        return {
            username     => [ qw(required alpha_num_ascii length_min:4 length_max:32) ],
            email        => [ qw(required email length_max:127) ],
            password     => [ qw(required length_max:40) ],
            password_cnf => [ qw(required same:password) ],
            confirm      => [ qw(required accepted) ],
        };
    }
}

### Now you can use it in your Dancer2 project.

post '/form' => sub {
    if (validate profile => RegisterForm->new) {
        my $valid_hash_ref = validated;

        save_user_input($valid_hash_ref);
        redirect '/success_page';
    }

    redirect '/form';
};

The html result could be like:

Screenshot register form

DISCLAIMER

This is alpha version, not stable.

Interfaces may change in future:

  • Roles: Dancer2::Plugin::FormValidator::Role::Extension, Dancer2::Plugin::FormValidator::Role::Validator.

  • Validators.

Won't change:

  • Dsl keywords.

  • Template tokens.

  • Roles: Dancer2::Plugin::FormValidator::Role::Profile, Dancer2::Plugin::FormValidator::Role::HasMessages, Dancer2::Plugin::FormValidator::Role::ProfileHasMessages.

If you like it - add it to your bookmarks. I intend to complete the development by the summer 2022.

Have any ideas? Find this project on github (repo ref is at the bottom). Help is always welcome!

DESCRIPTION

This is micro-framework that provides validation in your Dancer2 application. It consists of dsl's keywords: validate, validator_language, errors. It has a set of built-in validators that can be extended by compatible modules (extensions). Also proved runtime switching between languages, so you can show proper error messages to users.

Uses simple and declarative approach to validate forms:

Validator

First, you need to create class which will implements at least one main role: Dancer2::Plugin::FormValidator::Role::Profile.

This role requires profile method which should return a HashRef Data::FormValidator accepts:

package RegisterForm

use Moo;
with 'Dancer2::Plugin::FormValidator::Role::Profile';

sub profile {
    return {
        username     => [ qw(required alpha_num_ascii length_min:4 length_max:32) ],
        email        => [ qw(required email length_max:127) ],
        password     => [ qw(required length_max:40) ],
        password_cnf => [ qw(required same:password) ],
        confirm      => [ qw(required accepted) ],
    };
};

Profile method

Profile method should always return a HashRef[ArrayRef] where keys are input fields names and values are ArrayRef with list of validators.

Application

Then you need to set basic configuration:

set plugins => {
       FormValidator => {
           session => {
               namespace => '_form_validator' # This is required field
           },
       },
   };

Now you can validate POST parameters in your controller:

use Dancer2::Plugin::FormValidator;
use RegisterForm;

post '/register' => sub {
    if (my $valid_hash_ref = validate profile => RegisterForm->new) {
        if (login($valid_hash_ref)) {
            redirect '/success_page';
        }
    }

    redirect '/register';
};

get '/register' => sub {
    template 'app/register' => {
        title  => 'Register page',
    };
};

Template

In you template you have access to: $errors - this is HashRef with parameters names as keys and error messages as ArrayRef values and $old - contains old input values.

Template app/register:

<div class="w-3/4 max-w-md bg-white shadow-lg py-4 px-6">
    <form method="post" action="/register">
        <div class="py-2">
            <label class="block font-normal text-gray-400" for="name">
                Name
            </label>
            <input
                    type="text"
                    id="name"
                    name="name"
                    value="<: $old[name] :>"
                    class="border border-2 w-full h-5 px-4 py-5 mt-1 rounded-md
                    hover:outline-none focus:outline-none focus:ring-1 focus:ring-indigo-100"
            >
            <: for $errors[name] -> $error { :>
                <small class="pl-1 text-red-400"><: $error :></small>
            <: } :>
        </div>
        <div class="py-2">
            <label class="block font-normal text-gray-400" for="email">
                Name
            </label>
            <input
                    type="text"
                    id="email"
                    name="email"
                    value="<: $old[email] :>"
                    class="border border-2 w-full h-5 px-4 py-5 mt-1 rounded-md
                    hover:outline-none focus:outline-none focus:ring-1 focus:ring-indigo-100"
            >
            <: for $errors[email] -> $error { :>
                <small class="pl-1 text-red-400"><: $error :></small>
            <: } :>

        <!-- Other fields -->
        ...
        ...
        ...
        <!-- Other fields end -->

        </div>
        <button
                type="submit"
                class="mt-4 bg-sky-600 text-white py-2 px-6 rounded-md hover:bg-sky-700"
        >
            Register
        </button>
    </form>
</div>

CONFIGURATION

...
plugins:
    FormValidator:
        session:
            namespace: '_form_validator'         # this is required
        messages:
            language: en                         # this is default
            ucfirst: 1                           # this is default
            validators:
                required:
                    en: %s is needed from config # custom en message
                    de: %s ist erforderlich      # custom de message
                ...
        extensions:
            dbic:
                provider: Dancer2::Plugin::FormValidator::Extension::DBIC
                ...
...

DSL KEYWORDS

validate

validate(Hash $params): HashRef|undef

Accept params as hash:

(
    profile => Object implementing Dancer2::Plugin::FormValidator::Role::Profile # required
    input   => HashRef of values to validate, default is body_parameters->as_hashref_mixed
    lang    => Accepts two-lettered language id, default is 'en'
)

Profile is required, input and lang is optional.

Returns valid input HashRef if validation succeed, otherwise returns undef.

if (validate profile => RegisterForm->new) {
    # Success, data is valid.
    my $valid_hash_ref = validated;

    # Do some operations...
}
else {
    # Error, data is invalid.
    my $errors = errors;

    # Redirect or show errors...
}

validated

validated(): HashRef|undef

No arguments. Returns valid input HashRef if validate succeed. Undef value will be returned after first call within one validation process.

my $valid_hash_ref = validated;

errors

errors(): HashRef

No arguments. Returns HashRef[ArrayRef] if validation failed.

my $errors_hash_multi = errors;

Validators

accepted

Validates that field exists and one of the listed: (yes on 1).

alpha

Validate that string only contain of alphabetic utf8 symbols, i.e. /^[[:alpha:]]+$/.

alpha_ascii

Validate that string only contain of latin alphabetic ascii symbols, i.e. /^[[:alpha:]]+$/a.

alpha_num

Validate that string only contain of alphabetic utf8 symbols, underscore and numbers 0-9, i.e. /^\w+$/.

alpha_num_ascii

Validate that string only contain of latin alphabetic ascii symbols, underscore and numbers 0-9, i.e. /^\w+$/a.

email

Validate that field is valid email(rfc822).

email_dns

Validate that field is valid email(rfc822) and dns exists.

enum:value1,value2

Validate that field is one of listed values.

field => [ qw(enum:value1,value2) ]

integer

Validate that field is integer.

length_max:num

Validate that string length <= num.

field => [ qw(length_max:32) ]

length_min:num

Validate that string length >= num.

field => [ qw(length_max:4) ]

max:num

Validate that field is number <= num.

field => [ qw(max:32) ]

min:num

Validate that field is number >= num.

field => [ qw(min:4) ]

numeric

Validate that field is number.

required

Validate that field exists and not empty string.

same

CUSTOM MESSAGES

To define custom error messages for fields/validators your Validator should implement Role: Dancer2::Plugin::FormValidator::Role::ProfileHasMessages.

package Validator {
    use Moo;

    with 'Dancer2::Plugin::FormValidator::Role::ProfileHasMessages';

    sub profile {
        return {
            name  => [qw(required)],
            email => [qw(required email)],
        };
    }

    sub messages {
        return {
            name => {
                required => {
                    en => 'Specify your %s',
                },
            },
            email => {
                required => {
                    en => '%s is needed',
                },
                email => {
                    en => '%s please use valid email',
                }
            }
        };
    }
}

EXTENSIONS

Writing custom extensions

You can extend the set of validators by writing extensions:

package Extension {
    use Moo;

    with 'Dancer2::Plugin::FormValidator::Role::Extension';

    sub validators {
        return {
            is_true  => 'IsTrue',   # Full class name
            email    => 'Email',    # Full class name
            restrict => 'Restrict', # Full class name
        }
    }
}

Extension should implement Role: Dancer2::Plugin::FormValidator::Role::Extension.

Custom validators:

package IsTrue {
    use Moo;

    with 'Dancer2::Plugin::FormValidator::Role::Validator';

    sub message {
        return {
            en => '%s is not a true value',
        };
    }

    sub validate {
        my ($self, $field, $input) = @_;

        if (exists $input->{$field}) {
            if ($input->{$field} == 1) {
                return 1;
            }
            else {
                return 0;
            }
        }

        return 1;
    }
}

Validator should implement Role: Dancer2::Plugin::FormValidator::Role::Validator.

Config:

set plugins => {
    FormValidator => {
        session    => {
            namespace => '_form_validator'
        },
        extensions => {
            extension => {
                provider => 'Extension',
            }
        }
    },
};

Extensions modules

There is a set of ready-made extensions available on cpan:

TODO

  • Document all config field with explanation.

  • Document all Roles and HashRef structures.

  • Extensions docs.

  • Contribution and help details.

BUGS AND LIMITATIONS

If you find one, please let me know.

SOURCE CODE REPOSITORY

https://github.com/AlexP007/dancer2-plugin-formvalidator.

AUTHOR

Alexander Panteleev <alexpan at cpan dot org>.

LICENSE AND COPYRIGHT

This software is copyright (c) 2022 by Alexander Panteleev. This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.