עידו פרלמוטר (Ido Perlmuter) > Brannigan-0.9 > Brannigan

Download:
Brannigan-0.9.tar.gz

Dependencies

Annotate this POD

Website

CPAN RT

Open  0
View/Report Bugs
Module Version: 0.9   Source   Latest Release: Brannigan-1.1

NAME ^

Brannigan - Comprehensive, flexible system for validating and parsing input, mainly targeted at web applications.

VERSION ^

version 0.9

SYNOPSIS ^

This example uses Catalyst, but should be pretty self explanatory. It's fairly complex, since it details pretty much all of the available Brannigan functionality, so don't be alarmed by the size of this thing.

        package MyApp::Controller::Post;

        use strict;
        use warnings;
        use Brannigan;

        # create a new Brannigan object with two validation/parsing schemes:
        my $b = Brannigan->new(
        {
                name => 'post',
                ignore_missing => 1,
                params => {
                        subject => {
                                required => 1,
                                length_between => [3, 40],
                        },
                        text => {
                                required => 1,
                                min_length => 10,
                                validate => sub {
                                        my $value = shift;

                                        return undef unless $value;
                                        
                                        return $value =~ m/^lorem ipsum/ ? 1 : undef;
                                }
                        },
                        day => {
                                required => 0,
                                integer => 1,
                                value_between => [1, 31],
                        },
                        mon => {
                                required => 0,
                                integer => 1,
                                value_between => [1, 12],
                        },
                        year => {
                                required => 0,
                                integer => 1,
                                value_between => [1900, 2900],
                        },
                        section => {
                                required => 1,
                                integer => 1,
                                value_between => [1, 3],
                                parse => sub {
                                        my $val = shift;
                                        
                                        my $ret = $val == 1 ? 'reviews' :
                                                  $val == 2 ? 'receips' :
                                                  'general';
                                                  
                                        return { section => $ret };
                                },
                        },
                        id => {
                                required => 1,
                                exact_length => 10,
                                value_between => [1000000000, 2000000000],
                        },
                        '/^picture_(\d+)$/' => {
                                length_between => [3, 100],
                                validate => sub {
                                        my ($value, $num) = @_;

                                        ...
                                },
                        },
                        picture_1 => {
                                default => 'http://www.example.com/avatar.png',
                        },
                        somearray => {
                                array => 1,
                                min_length => 3,
                                values => {
                                        integer => 1,
                                },
                        },
                        somehash => {
                                hash => 1,
                                keys => {
                                        _all => {
                                                required => 1,
                                        },
                                        en => {
                                                exact_length => 10,
                                        },
                                        he => {
                                                exact_length => 10,
                                        },
                                },
                        },
                },
                groups => {
                        date => {
                                params => [qw/year mon day/],
                                parse => sub {
                                        my ($year, $mon, $day) = @_;
                                        return undef unless $year && $mon && $day;
                                        return { date => $year.'-'.$mon.'-'.$day };
                                },
                        },
                        tags => {
                                regex => '/^tags_(en|he|fr)$/',
                                forbid_words => ['bad_word', 'very_bad_word'],
                                parse => sub {
                                        return { tags => \@_ };
                                },
                        },
                },
        }, {
                name => 'edit_post',
                inherits_from => 'post',
                params => {
                        subject => {
                                required => 0, # subject is no longer required
                        },
                        id => {
                                forbidden => 1,
                        },
                },
        });

        # create the custom 'forbid_words' validation method
        $b->custom_validation('forbid_words', sub {
                my $value = shift;

                foreach (@_) {
                        return 0 if $value =~ m/$_/;
                }

                return 1;
        });

        # post a new blog post
        sub new_post : Local {
                my ($self, $c) = @_;

                # get input parameters hash-ref
                my $params = $c->request->params;

                # process the parameters
                my $parsed_params = $b->process('post', $params);

                if ($parsed_params->{_rejects}) {
                        die $c->list_errors($parsed_params);
                } else {
                        $c->model('DB::BlogPost')->create($parsed_params);
                }
        }

        # edit a blog post
        sub edit_post : Local {
                my ($self, $c, $id) = @_;

                my $params = $b->process('edit_posts', $c->req->params);

                if ($params->{_rejects}) {
                        die $c->list_errors($params);
                } else {
                        $c->model('DB::BlogPosts')->find($id)->update($params);
                }
        }

DESCRIPTION ^

Brannigan is an attempt to ease the pain of collecting, validating and parsing input parameters in web applications. It's designed to answer both of the main problems that web applications face:

Brannigan's approach to data validation is as follows: define a structure of parameters and their needed validations, and let the module automatically examine input parameters against this structure. Brannigan provides you with common validation methods that are used everywhere, and also allows you to create custom validations easily. This structure also defines how, if at all, the input should be parsed. This is akin to schema-based validations such as XSD, but much more functional, and most of all flexible.

Check the synopsis section for an example of such a structure. I call this structure a validation/parsing scheme. Schemes can inherit all the properties of other schemes, which allows you to be much more flexible in certain situations. Imagine you have a blogging application. A base scheme might define all validations and parsing needed to create a new blog post from a user's input. When editing a post, however, some parameters that were required when creating the post might not be required now (so you can just use older values), and maybe new parameters are introduced. Inheritance helps you avoid repeating yourself. You can another scheme which gets all the properties of the base scheme, only changing whatever it is needs changing (and possibly adding specific properties that don't exist in the base scheme).

HOW BRANNIGAN WORKS

In essence, Brannigan works in three stages (which all boil down to one single command):

HOW SCHEMES LOOK

The validation/parsing scheme defines the structure of the data you're expecting to receive, along with information about the way it should be validated and parsed. Schemes are created by passing them to the Brannigan constructor. You can pass as many schemes as you like, and these schemes can inherit from one another. You can create the Brannigan object that gets these schemes wherever you want. Maybe in a controller of your web app that will directly use this object to validate and parse input it gets, or maybe in a special validation class that will hold all schemes. It doesn't matter where, as long as you make the object available for your application.

A scheme is a hash-ref based data structure that has the following keys:

BUILT-IN VALIDATION METHODS

As mentioned earlier, Brannigan comes with a set of built-in validation methods which are most common and useful everywhere. For a list of all validation methods provided by Brannigan, check Brannigan::Validations.

CROSS-SCHEME CUSTOM VALIDATION METHODS

Custom validate methods are nice, but when you want to use the same custom validation method in different places inside your scheme, or more likely in different schemes altogether, repeating the definition of each custom method in every place you want to use it is not very comfortable. Brannigan provides a simple mechanism to create custom, named validation methods that can be used across schemes as if they were internal methods.

The process is simple: when creating your schemes, give the names of the custom validation methods and their relevant supplement values as with every built-in validation method. For example, suppose we want to create a custom validation method named 'forbid_words', that makes sure a certain text does not contain any words we don't like it to contain. Suppose this will be true for a parameter named 'text'. Then we define 'text' like so:

        text => {
                required => 1,
                forbid_words => ['curse_word', 'bad_word', 'ugly_word'],
        }

As you can see, we have provided the name of our custom method, and the words we want to forbid. Now we need to actually create this forbid_words() method. We do this after we've created our Brannigan object, by using the custom_validation() method, as in this example:

        $b->custom_validation('forbid_words', sub {
                my ($value, @forbidden) = @_;

                foreach (@forbidden) {
                        return 0 if $value =~ m/$_/;
                }

                return 1;
        });

We give the custom_validation() method the name of our new method, and an anonymous subroutine, just like in "local" custom validation methods.

And that's it. Now we can use the forbid_words() validation method across our schemes. If a paremeter failed our custom method, it will be added to the rejects like built-in methods. So, if 'text' failed our new method, our rejects hash-ref will contain:

        text => [ 'forbid_words(curse_word, bad_word, ugly_word)' ]

As an added bonus, you can use this mechanism to override Brannigan's built-in validations. Just give the name of the validation method you wish to override, along with the new code for this method. Brannigan gives precedence to cross-scheme custom validations, so your method will be used instead of the internal one.

NOTES ABOUT PARSE METHODS

As stated earlier, your parse() methods are expected to return a hash-ref of key-value pairs. Brannigan collects all of these key-value pairs and merges them into one big hash-ref (along with all the non-parsed parameters).

Brannigan actually allows you to have your parse() methods be two-leveled. This means that a value in a key-value pair in itself can be a hash-ref or an array-ref. This allows you to use the same key in different places, and Brannigan will automatically aggregate all of these occurrences, just like in the first level. So, for example, suppose your scheme has a regex rule that matches parameters like 'tag_en' and 'tag_he'. Your parse method might return something like { tags => { en => 'an english tag' } } when it matches the 'tag_en' parameter, and something like { tags => { he => 'a hebrew tag' } } when it matches the 'tag_he' parameter. The resulting hash-ref from the process method will thus include { tags => { en => 'an english tag', he => 'a hebrew tag' } }.

Similarly, let's say your scheme has a regex rule that matches parameters like 'url_1', 'url_2', etc. Your parse method might return something like { urls => [$url_1] } for 'url_1' and { urls => [$url_2] } for 'url_2'. The resulting hash-ref in this case will be { urls => [$url_1, $url_2] }.

Take note however that only two-levels are supported, so don't go crazy with this.

SO HOW DO I PROCESS INPUT?

OK, so we have created our scheme(s), we know how schemes look and work, but what now?

Well, that's the easy part. All you need to do is call the process() method on the Brannigan object, passing it the name of the scheme to enforce and a hash-ref of the input parameters/data structure. This method will return a hash-ref back, with all the parameters after parsing. If any validations failed, this hash-ref will have a '_rejects' key, with the rejects hash-ref described earlier. Remember: Brannigan doesn't raise any errors. It's your job to decide what to do, and that's a good thing.

Example schemes, input and output can be seen in Brannigan::Examples.

METHODS ^

new( \%scheme | @schemes )

Creates a new instance of Brannigan, with the provided scheme(s) (see "HOW SCHEMES LOOK" for more info on schemes).

process( $scheme, \%params )

Receives the name of a scheme and a hash-ref of input parameters (or a data structure), and validates and parses these paremeters according to the scheme (see "HOW SCHEMES LOOK" for detailed information about this process).

Returns a hash-ref of parsed parameters according to the parsing scheme, possibly containing a list of failed validations for each parameter.

Actual processing is done by Brannigan::Tree.

custom_validation( $name, $code )

Receives the name of a custom validation method ($name), and a reference to an anonymous subroutine ($code), and creates a new validation method with that name and code, to be used across schemes in the Brannigan object as if they were internal methods. You can even use this to override internal validation methods, just give the name of the method you want to override and the new code.

INTERNAL METHODS ^

_build_tree( $scheme, [ \%custom_validations ] )

Builds the final "tree" of validations and parsing methods to be performed on the parameters hash during processing. Optionally receives a hash-ref of cross-scheme custom validation methods defined in the Brannigan object (see "CROSS-SCHEME CUSTOM VALIDATION METHODS" for more info).

CAVEATS ^

Brannigan is still in an early stage. Currently, no checks are made to validate the schemes built, so if you incorrectly define your schemes, Brannigan will not croak and processing will probably fail. Also, there is no support yet for recursive inheritance or any crazy inheritance situation. While deep inheritance is supported, it hasn't been tested extensively. Also bugs are popping up as I go along, so keep in mind that you might encounter bugs (and please report any if that happens).

IDEAS FOR THE FUTURE ^

The following list of ideas may or may not be implemented in future versions of Brannigan:

SEE ALSO ^

Brannigan::Validations, Brannigan::Tree, Brannigan::Examples.

AUTHOR ^

Ido Perlmuter, <ido at ido50 dot net>

BUGS ^

Please report any bugs or feature requests to bug-brannigan at rt.cpan.org, or through the web interface at http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Brannigan. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.

SUPPORT ^

You can find documentation for this module with the perldoc command.

        perldoc Brannigan

You can also look for information at:

ACKNOWLEDGEMENTS ^

Brannigan is inspired by Oogly (Al Newkirk) and the "Ketchup" jQuery validation plugin (http://demos.usejquery.com/ketchup-plugin/).

LICENSE AND COPYRIGHT ^

Copyright 2010 Ido Perlmuter.

This program is free software; you can redistribute it and/or modify it under the terms of either: the GNU General Public License as published by the Free Software Foundation; or the Artistic License.

See http://dev.perl.org/licenses/ for more information.

syntax highlighting: