Hello monks, this is requests for comments, so if you are interested in this topic, please leave any.

Preface

Dancer2 is a lightweight framework that provides us with rich routing and serialization features out of the box. The rest is left to the developer, including working with the database, models and data in general.

When working with data, especially received from a user, you want to be sure of their consistency. In other words, I don't want to get a number where the username should be and the word in the age field (like age => twenty).

Perl is a language with very powerful regular expressions and many hackers use them. However, this approach has the following disadvantages:

Instead of writing if ($age !~ /^\d+$/) {…} else {…} and so for each field received from the user, I want to use a construction like (age => 'integer').

And here you should pay attention to special modules that simplify the validation process.

Modules

I've found some useful modules on cpan:

They all have their pros and cons, but none of them met all my criteria.

Criteria

1. Control flow the field to the validator, and not vice versa

This means that the code used fields as first class entities:

{ email => qw(required, email), age => qw(integer, min:18), }

And not validators:

{ required => qw(email), email => qw(email), integer => qw(age), min => { filed => ‘age’, num => 18 }, }

2. Required is validator too

Very often you can see:

{ required => … , validators => { … } }

Requred is the same validator and it is not clear to me why to extract it into a separate entity.

3. Has a set of built-in validators

Not all modules have built-in validators and you need to implement them yourself.

4. Easily extendable with new modules or custom code

This feature is provided by most modules.

5. It has a minimal set of dependencies and does not require the mandatory use of DBIc or Moose

Because we have a Dancer2 project and maybe we don't use DBIc models at all.

6. Multilingual support

Didn't find it anywhere.

7. Tightly integrated with Dancer2 and provides a set of handy dsl words and html templates tokens

I do not want to constantly get and transfer data to the template like errors or old input.

Dancer2::Plugin::FormValidator

Based on the above criteria, I decided to write my plugin specifically for Dancer2. Since this is my first big OS project, I ask respected monks to share their opinion on what I got as a result.

use Dancer2; use Dancer2::Plugin::FormValidator; 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) ], }; } } post '/form' => sub { if (validate profile => RegisterForm->new) { my $valid_hash_ref = validated; save_user_input($valid_hash_ref); redirect '/success_page'; } redirect '/form'; };

Plugin can be found on Metacpan => Dancer2::Plugin::FormValidator.
Github repo => dancer2-plugin-formvalidator.

I am think about the following questions:

Replies are listed 'Best First'.
Re: RFC: Creating Dancer2 validation plugin.
by stevieb (Canon) on May 04, 2022 at 15:32 UTC

    I've got just a couple of comments after a cursory look...

    In tests, pieces of code are often repeated, is there a generally accepted method of reusing them?

    Depending on what those pieces are, put them into a library (say, TestLib.pm in the t/ directory). Then:

    use warnings; use strict; use lib 't/'; use TestLib; use Test::More; ...

    It becomes a pain in the rear to have to modify several tests in several files if you change the code tests are testing.

    The documentation appears to be very thorough. Good job. I've passed up on what look like half decent Dancer2 plugins due to lack of documentation.

    Also kudos on using Email::Valid instead of regex for email validation. Realistically, to use regex to thoroughly validate an email address, you'd be dealing with a regex straight from the depths of hell.

    About the code (I took a glance, I didn't go through it thoroughly)... I personally like to see use strict; and use warnings; at the top of every Perl file, script or library. Also, it's best practice to assign all parameters from the @_ array at the top of a function/method. For example, instead of:

    sub _register_hooks { my $self = shift; $self->app->add_hook( Dancer2::Core::Hook->new( name => 'before_template_render', code => sub { my $tokens = shift; ...

    I'd have:

    sub _register_hooks { my ($self, $token) = @_; ...

    This way, anybody reading your code understands exactly how many and which parameters the sub wants and/or needs. To further, if you ever change the parameters coming in, you don't have to go searching for their order by trying to seek out shift statements half way through the sub. Doing it by shifting so far into the sub can lead to head scratching as to why you're getting 'baz' as a parameter for $token instead of token data, because you shifted off a new parameter that wasn't aligned properly.

    Using my ($x, $x, %z) = @_; is essentially self-documenting code, on the first line of the sub definition.

    For small things like this:

    sub BUILD { shift->_register_hooks; return; }

    I prefer to write it like:

    sub BUILD { $_[0]->_register_hooks; }

    Again, just a preference, but it leaves @_ intact, and in a method, $_[0] unambiguously refers to the object (or class, as it were).

    Same as below. If my sub grows to three or more lines, or has more than two parameters, I replace the $_[0] etc with the way I showed above.

    sub _validator_language { shift->config_validator->language(shift); return; }

    ...

    sub _validator_language { $_[0]->config_validator->language($_[1]); }

    ...is there a reason you're intentionally returning undef?

    As far as private methods, because I don't often document them (because they're usually very small and specific), I do usually add a small comment before assigning parameters just so it's easy for others to see what it's to be doing.

    sub _access_token_data { # Fetches and stores the access token data to the AUTH_CACHE_FILE my ($self, $data) = @_; ... } sub _access_token_set_expiry { # Sets the access token expiry date/time after generation and # renewal my ($self, $token_data) = @_; }
      >
      sub _register_hooks { my $self = shift; $self->app->add_hook( Dancer2::Core::Hook->new( name => 'before_template_render', code => sub { my $tokens = shift; ...
      I'd have:
      sub _register_hooks { my ($self, $token) = @_; ...

      But that's not equivalent! Note the anonymous sub, the second shift happens in a nested subroutine, it uses its arguments, not the _register_hooks' ones.

      map{substr$_->[0],$_->[1]||0,1}[\*||{},3],[[]],[ref qr-1,-,-1],[{}],[sub{}^*ARGV,3]

        You're right, I completely glossed over that. Thanks for the correction; it's a very important one!

        I still stand by the assignment rather than shift though.

      You highlighted important things there, big thanks!

      I agree, that using @_ in args is more straightforward, and it should be used instead of shift.
      Also, it's a good idea to leave comments for private methods.

      ...is there a reason you're intentionally returning undef?

      As I know, perl returns the last value from a block, so returning undef is a way to protect from random bugs. Is it unnecessary or there is a better way?

Re: RFC: Creating Dancer2 validation plugin.
by AlexP (Pilgrim) on May 17, 2022 at 11:19 UTC
      Congratulations, Alex! An important module.
Re: RFC: Creating Dancer2 validation plugin.
by perlfan (Parson) on May 04, 2022 at 18:10 UTC
    I hear ya. I've always found plugins for this kind of stuff tend to be overkill. Why? Because it's more about the pattern than anything.

    e.g., I tend to follow this patter in dancer2 apps for authentication:

    • unauthenticated request handler to process login form (or HMAC auth form API)
    • check auth credentials somehow
    • on success, store session info on $USER
    • on fail, throw a 403 or something
    • all other endpoint handlers are authenticated by an assertion that the session $USER is valid; failure redirects or throws 403 as appropriate
    So there are keywords that do this already: post, session, and send_error.

    So what would actually be useful, and I've thought about this, is a repository of single page "apps" that do things like implement authentication (like the old PHPlib used to provide) or implement an example of doing HMAC authentication for API handling, etc. I good one also would be how to handle an eBay IPN (payment notification) which turns out to be trivial in dancer2/mojo - everyone likes getting paid, here's how! xD