The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

Brannigan::Examples - Example schemes, input and output for Brannigan.

DESCRIPTION

The following document provides examples for Brannigan usage. Brannigan is a system for validating and parsing input, targeted at web applications. It can validate and parse both simple user input and complex data structures.

In this document you will find example schemes and input/output to/from these schemes.

The examples in this document are directly extracted from the tests provided with the Brannigan distrubution.

SIMPLE SCHEMES

The following example shows a Brannigan object with two simple schemes. The first scheme (post) is the base scheme, while the second (edit_post) inherits the base scheme while making a few simple changes.

        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],
                                },
                        },
                        groups => {
                                date => {
                                        params => [qw/year mon day/],
                                        parse => sub {
                                                my ($year, $mon, $day) = @_;
                                                return undef unless $year && $mon && $day;
                                                return { date => $year.'-'.$mon.'-'.$day };
                                        },
                                },
                        },
                }, {
                        name => 'edit_post',
                        inherits_from => 'post',
                        params => {
                                subject => {
                                        required => 0,
                                },
                                id => {
                                        forbidden => 1,
                                },
                                edit_reason => {
                                        required => 0,
                                },
                        },
                });

Basically, these schemes are used to create and edit blog posts. A post has a subject between 3 to 40 characters long; a text field longer than 10 characters that must also begin with 'lorem ipsum'; day, month and year values; a section integer the blog section for this post belongs; and a 10-digit ID field. We have a 'date' group that automatically parses the day, month and year values to a YYYY-MM-DD string. The 'edit_post' scheme is used when a blog post is edited. This time, the subject is not required (maybe we just want to use the previous value), an ID shouldn't be provided, and a new paramter called 'edit_reason' can be provided (but isn't required).

INPUT/OUTPUT EXAMPLE WITH REJECTS AND NO INHERITANCE

Suppose our app receives the following input for creating a new post:

        my $input_bad = {
                subject         => 'su',
                text            => undef,
                day             => 13,
                mon             => 12,
                year            => 2010,
                section         => 2,
                thing           => 3,
                id              => 300000000,
        };

The output from running $b->process('post', $input_bad) will be:

        {
                '_rejects' => {
                        'text' => [ 'required(1)' ],
                        'subject' => [ 'length_between(3, 40)' ],
                        'id' => [ 'exact_length(10)', 'value_between(1000000000, 2000000000)' ]
                },
                'date' => '2010-12-13',
                'subject' => 'su',
                'section' => 'receips',
                'day' => 13,
                'mon' => 12,
                'id' => 300000000,
                'year' => 2010
        }

We can see that three parameters failed validation, and we can see the validations they failed in the _rejects key. Even though they did fail, they are still returned in the output. Notice, also, that while the input had a 'thing' key with a value of 3, the output lacks that key, since it is not referenced anywhere in the scheme and 'ignore_missing' is on.

INPUT/OUTPUT EXAMPLE WITH NO REJECTS AND NO INHERITANCE

        my $input_good = {
                subject         => 'subject',
                text            => 'lorem ipsum dolor sit amet',
                section         => 2,
                thing           => 3,
                id              => 1515151515,
        };

        my $output = $b->process('post', $input_good)

In this case, $output will be:

        {
                'subject' => 'subject',
                'text' => 'lorem ipsum dolor sit amet',
                'section' => 'receips',
                'id' => 1515151515
        }

This time all parameters passed their validations, so a _rejects key does not exist in the output.

INPUT/OUTPUT EXAMPLE WITH REJECTS AND INHERITANCE

Well, let's suppose now we are editing the post we've created above.

        my $input_edited_bad = {
                subject         => 'subject edited',
                section         => 3,
                id              => 1515151515,
        };

        my $output2 = $b->process('edit_post', $input_edited_bad);

$output2 will be:

        {
                '_rejects' => {
                        'id' => [ 'forbidden(1)' ],
                        'text' => [ 'required(1)' ],
                },
                'subject' => 'subject edited',
                'section' => 'general',
                'id' => 1515151515
        }

INPUT/OUTPUT EXAMPLE WITH INHERITANCE AND NO REJECTS

        my $input_edited_good = {
                id              => undef,
                section         => 1,
                text            => 'lorem ipsum oh shit my parents are here',
                edit_reason     => 'forgot to mention my parents are here',
        };

        my $output3 = $b->process('edit_post', $input_edited_bad);

$output3 will be:

        {
                'section' => 'reviews',
                'text' => 'lorem ipsum oh shit my parents are here',
                'edit_reason' => 'forgot to mention my parents are here',
        }

COMPLEX SCHEME

The following schemes are examples of Brannigan's ability to validate and parse complex data structures.

        my $b = Brannigan->new(
                {
                        name => 'complex_scheme',
                        ignore_missing => 1,
                        params => {
                                name => {
                                        hash => 1,
                                        keys => {
                                                '/^(first|last)_name$/' => {
                                                        required => 1,
                                                },
                                                middle_name => {
                                                        required => 0,
                                                },
                                        },
                                },
                                '/^(birth|death)_date$/' => {
                                        hash => 1,
                                        required => 1,
                                        keys => {
                                                _all => {
                                                        required => 1,
                                                        integer => 1,
                                                },
                                                day => {
                                                        value_between => [1, 31],
                                                },
                                                mon => {
                                                        value_between => [1, 12],
                                                },
                                                year => {
                                                        value_between => [1900, 2100],
                                                },
                                        },
                                        parse => sub {
                                                my ($date, $type) = @_;

                                                # $type has either 'birth' or 'date',
                                                # $date has the hash-ref that was provided
                                                $date->{day} = '0'.$date->{day} if $date->{day} < 0;
                                                $date->{mon} = '0'.$date->{mon} if $date->{mon} < 0;
                                                return { "${type}_date" => join('-', $date->{year}, $date->{mon}, $date->{day}) };
                                        },
                                },
                                death_date => {
                                        required => 0,
                                },
                                id_num => {
                                        integer => 1,
                                        exact_length => 9,
                                        validate => sub {
                                                my $value = shift;

                                                return $value =~ m/^0/ ? 1 : undef;
                                        },
                                        default => sub {
                                                # generate a random 9-digit number that begins with zero
                                                my @digits = (0);
                                                foreach (2 .. 9) {
                                                        push(@digits, int(rand(10)));
                                                }
                                                #return join('', @digits);
                                                
                                                # commented out so we can actually write a test for this
                                                return '012345678';
                                        },
                                },
                                phones => {
                                        hash => 1,
                                        keys => {
                                                _all => {
                                                        validate => sub {
                                                                my $value = shift;

                                                                return $value =~ m/^\d{2,3}-\d{7}$/ ? 1 : undef;
                                                        },
                                                },
                                                '/^(home|mobile|fax)$/' => {
                                                        parse => sub {
                                                                my ($value, $type) = @_;

                                                                return { $type => $value };
                                                        },
                                                },
                                        },
                                },
                                education => {
                                        required => 1,
                                        array => 1,
                                        length_between => [1, 3],
                                        values => {
                                                hash => 1,
                                                keys => {
                                                        '/^(start_year|end_year)$/' => {
                                                                required => 1,
                                                                value_between => [1900, 2100],
                                                        },
                                                        school => {
                                                                required => 1,
                                                                min_length => 4,
                                                        },
                                                        type => {
                                                                required => 1,
                                                                one_of => ['Elementary', 'High School', 'College/University'],
                                                                parse => sub {
                                                                        my $value = shift;

                                                                        # returns the first character of the value in lowercase
                                                                        my @chars = split(//, $value);
                                                                        return { type => lc shift @chars };
                                                                },
                                                        },
                                                },
                                        },
                                },
                                employment => {
                                        required => 1,
                                        array => 1,
                                        length_between => [1, 5],
                                        values => {
                                                hash => 1,
                                                keys => {
                                                        '/^(start|end)_year$/' => {
                                                                required => 1,
                                                                value_between => [1900, 2100],
                                                        },
                                                        employer => {
                                                                required => 1,
                                                                max_length => 20,
                                                        },
                                                        responsibilities => {
                                                                array => 1,
                                                                required => 1,
                                                        },
                                                },
                                        },
                                },
                                other_info => {
                                        hash => 1,
                                        keys => {
                                                bio => {
                                                        hash => 1,
                                                        keys => {
                                                                '/^(en|he|fr)$/' => {
                                                                        length_between => [100, 300],
                                                                },
                                                                fr => {
                                                                        required => 1,
                                                                },
                                                        },
                                                },
                                        },
                                },
                                '/^picture_(\d+)$/' => {
                                        max_length => 5,
                                        validate => sub {
                                                my ($value, $num) = @_;

                                                return $value =~ m!^http://! && $value =~ m!\.(png|jpg)$! ? 1 : undef;
                                        },
                                },
                                picture_1 => {
                                        default => 'http://www.example.com/images/default.png',
                                },
                        },
                        groups => {
                                generate_url => {
                                        params => [qw/id_num name/],
                                        parse => sub {
                                                my ($id_num, $name) = @_;

                                                return { url => "http://www.example.com/?id=${id_num}&$name->{last_name}" };
                                        },
                                },
                                pictures => {
                                        regex => '/^picture_(\d+)$/',
                                        parse => sub {
                                                return { pictures => \@_ };
                                        },
                                },
                        },
                },
                {
                        name => 'complex_inherit',
                        inherits_from => 'complex_scheme',
                        params => {
                                education => {
                                        values => {
                                                keys => {
                                                        honors => {
                                                                array => 1,
                                                                max_length => 5,
                                                                values => {
                                                                        length_between => [5, 15],
                                                                },
                                                        },
                                                },
                                        },
                                },
                                picture_1 => {
                                        required => 1,
                                },
                                other_info => {
                                        keys => {
                                                social => {
                                                        array => 1,
                                                        values => {
                                                                hash => 1,
                                                                keys => {
                                                                        website => {
                                                                                required => 1,
                                                                        },
                                                                        user_id => {
                                                                                required => 1,
                                                                        },
                                                                },
                                                        },
                                                },
                                        },
                                },
                        },
                }, {
                        name => 'complex_inherit_2',
                        inherits_from => 'complex_inherit',
                        params => {
                                some_other_thing => {
                                        validate => sub {
                                                my $value = shift;

                                                return $value =~ m/I'm a little teapot/ ? 1 : undef;
                                        },
                                        parse => sub {
                                                my $value = shift;

                                                $value =~ s/I'm a little teapot/I like to wear women's clothing/;

                                                return { some_other_thing => $value };
                                        },
                                },
                        },
                        groups => {
                                dates => {
                                        params => [qw/birth_date death_date/],
                                        parse => sub {
                                                my ($b, $d) = @_;

                                                return { dates => 'This guy was born '.$b->{year}.'-'.$b->{mon}.'-'.$b->{day}.', unfortunately, he died '.$d->{year}.'-'.$d->{mon}.'-'.$d->{day} };
                                        },
                                },
                        },
                },
                {
                        name => 'complex_inherit_3',
                        inherits_from => ['complex_inherit', 'complex_inherit_2'],
                        params => {
                                employment => {
                                        required => 0,
                                },
                        },
                }
        );

This is a fairly complex example. Our base scheme ('complex_scheme') defines a resume-like structure (well, expect maybe for the 'death_date' parameter). The other schemes are just meant as an example of inheritance.

Now, suppose we have the following input:

        my $input = {
                name => {
                        first_name => 'Some',
                        last_name => 'One',
                },
                birth_date => {
                        day => 32,
                        mon => -5,
                        year => 1984,
                },
                death_date => {
                        day => 12,
                        mon => 12,
                        year => 2112,
                },
                phones => {
                        home => '123-1234567',
                        mobile => 'what?',
                },
                education => [
                        { school => 'First Elementary School of Somewhere', start_year => 1990, end_year => 1996, type => 'Elementary' },
                        { school => 'Sch', start_year => 1996, end_year => 3000, type => 'Fake' },
                ],
                other_info => {
                        bio => { en => "Born, lives, will die.", he => "Nolad, Chai, Yamut." },
                },
                picture_1 => '',
                picture_2 => 'http://www.example.com/images/mypic.jpg',
                picture_3 => 'http://www.example.com/images/mypic.png',
                picture_4 => 'http://www.example.com/images/mypic.gif',
        };

        my $output = $b->process('complex_scheme', $input);

Our $output will be:

        {
                'education' => [
                        {
                                'start_year' => 1990,
                                'type' => 'e',
                                'school' => 'First Elementary School of Somewhere',
                                'end_year' => 1996
                        },
                        {
                                'start_year' => 1996,
                                'type' => 'f',
                                'school' => 'Sch',
                                'end_year' => 3000
                        }
                ],
                'death_date' => {
                        'mon' => 12,
                        'day' => 12,
                        'year' => 2112
                },
                'name' => {
                        'last_name' => 'One',
                        'first_name' => 'Some'
                },
                'id_num' => '012345678',
                'picture_2' => 'http://www.example.com/images/mypic.jpg',
                'phones' => {
                        'mobile' => 'what?',
                        'home' => '123-1234567'
                },
                'birth_date' => {
                        'mon' => -5,
                        'day' => 32,
                        'year' => 1984
                },
                'other_info' => {
                        'bio' => {
                                'en' => 'Born, lives, will die.',
                                'he' => 'Nolad, Chai, Yamut.'
                        }
                },
                '_rejects' => {
                        'education' => {
                                '1' => {
                                        'type' => [
                                                'one_of(Elementary, High School, College/University)'
                                        ],
                                        'school' => [
                                                'min_length(4)'
                                        ],
                                        'end_year' => [
                                                'value_between(1900, 2100)'
                                        ]
                                }
                        },
                        'death_date' => {
                                'year' => [
                                        'value_between(1900, 2100)'
                                ]
                        },
                        'picture_2' => [
                                'max_length(5)'
                        ],
                        'birth_date' => {
                                'mon' => [
                                        'integer(1)',
                                        'value_between(1, 12)'
                                ],
                                'day' => [
                                        'value_between(1, 31)'
                                ]
                        },
                        'phones' => {
                                'mobile' => [
                                        'validate'
                                ]
                        },
                        'employment' => [
                                'required(1)'
                        ],
                        'other_info' => {
                                'bio' => {
                                        'en' => [
                                                'length_between(100, 300)'
                                        ],
                                        'fr' => [
                                                'required(1)'
                                        ],
                                        'he' => [
                                                'length_between(100, 300)'
                                        ]
                                }
                        },
                        'picture_3' => [
                                'max_length(5)'
                        ],
                        'picture_4' => [
                                'max_length(5)',
                                'validate'
                        ]
                },
                'picture_3' => 'http://www.example.com/images/mypic.png',
                'picture_4' => 'http://www.example.com/images/mypic.gif',
                'url' => 'http://www.example.com/?id=012345678&One',
                'pictures' => [
                        'http://www.example.com/images/mypic.jpg',
                        'http://www.example.com/images/mypic.png',
                        'http://www.example.com/images/mypic.gif',
                        'http://www.example.com/images/default.png'
                ],
                'picture_1' => 'http://www.example.com/images/default.png'
        }

Let's now modify the input like so:

        $input->{education}->[0]->{honors} = ['Valedictorian', "Teacher's Pet", "The Dean's Suckup"];
        $input->{education}->[1]->{honors} = 'Woooooeeeee!';
        $input->{other_info}->{social} = [{ website => 'facebook', user_id => 123412341234 }, { website => 'noogie.com', user_id => 'snoogens' }];

Processing this input with the 'complex_inherit' scheme yields:

        {
                'education' => [
                        {
                                'start_year' => 1990,
                                'honors' => [
                                        'Valedictorian',
                                        'Teacher\'s Pet',
                                        'The Dean\'s Suckup'
                                ],
                                'type' => 'e',
                                'school' => 'First Elementary School of Somewhere',
                                'end_year' => 1996
                        },
                        {
                                'start_year' => 1996,
                                'honors' => 'Woooooeeeee!',
                                'type' => 'f',
                                'school' => 'Sch',
                                'end_year' => 3000
                        }
                ],
                'name' => {
                        'last_name' => 'One',
                        'first_name' => 'Some'
                },
                'death_date' => {
                        'mon' => 12,
                        'day' => 12,
                        'year' => 2112
                },
                'id_num' => '012345678',
                'picture_2' => 'http://www.example.com/images/mypic.jpg',
                'birth_date' => {
                        'mon' => -5,
                        'day' => 32,
                        'year' => 1984
                },
                'phones' => {
                        'mobile' => 'what?',
                        'home' => '123-1234567'
                },
                'other_info' => {
                        'social' => [
                                {
                                        'website' => 'facebook',
                                        'user_id' => '123412341234'
                                },
                                {
                                        'website' => 'noogie.com',
                                        'user_id' => 'snoogens'
                                }
                        ],
                        'bio' => {
                                'en' => 'Born, lives, will die.',
                                'he' => 'Nolad, Chai, Yamut.'
                        }
                },
                '_rejects' => {
                        'education' => {
                                '1' => {
                                        'honors' => {
                                                '_self' => [
                                                        'array(1)'
                                                ]
                                        },
                                        'type' => [
                                                'one_of(Elementary, High School, College/University)'
                                        ],
                                        'school' => [
                                                'min_length(4)'
                                        ],
                                        'end_year' => [
                                                'value_between(1900, 2100)'
                                        ]
                                },
                                '0' => {
                                        'honors' => {
                                                '2' => [
                                                        'length_between(5, 15)'
                                                ]
                                        }
                                }
                        },
                        'death_date' => {
                                'year' => [
                                        'value_between(1900, 2100)'
                                ]
                        },
                        'picture_2' => [
                                'max_length(5)'
                        ],
                        'phones' => {
                                'mobile' => [
                                        'validate'
                                ]
                        },
                        'birth_date' => {
                                'mon' => [
                                        'integer(1)',
                                        'value_between(1, 12)'
                                ],
                                'day' => [
                                        'value_between(1, 31)'
                                ]
                        },
                        'employment' => [
                                'required(1)'
                        ],
                        'other_info' => {
                                'bio' => {
                                        'en' => [
                                                'length_between(100, 300)'
                                        ],
                                        'fr' => [
                                                'required(1)'
                                        ],
                                        'he' => [
                                                'length_between(100, 300)'
                                        ]
                                }
                        },
                        'picture_3' => [
                                'max_length(5)'
                        ],
                        'picture_4' => [
                                'max_length(5)',
                                'validate'
                        ],
                        'picture_1' => [
                                'required(1)'
                        ]
                },
                'picture_3' => 'http://www.example.com/images/mypic.png',
                'picture_4' => 'http://www.example.com/images/mypic.gif',
                'url' => 'http://www.example.com/?id=012345678&One',
                'pictures' => [
                        'http://www.example.com/images/mypic.jpg',
                        'http://www.example.com/images/mypic.png',
                        'http://www.example.com/images/mypic.gif',
                        'http://www.example.com/images/default.png'
                ],
                'picture_1' => 'http://www.example.com/images/default.png'
        }

Now let's take the original input (before we changed it), and modify it like so:

        $input->{some_other_thing} = "I'd like to tell the whole world that I'm a little teapot.";

Processing with 'complex_inherit_2' results in the following output:

        {
                'education' => [
                        {
                                'start_year' => 1990,
                                'honors' => [
                                        'Valedictorian',
                                        'Teacher\'s Pet',
                                        'The Dean\'s Suckup'
                                ],
                                'type' => 'e',
                                'school' => 'First Elementary School of Somewhere',
                                'end_year' => 1996
                        },
                        {
                                'start_year' => 1996,
                                'honors' => 'Woooooeeeee!',
                                'type' => 'f',
                                'school' => 'Sch',
                                'end_year' => 3000
                        }
                ],
                'dates' => 'This guy was born 1984--5-32, unfortunately, he died 2112-12-12',
                'name' => {
                        'last_name' => 'One',
                        'first_name' => 'Some'
                },
                'death_date' => {
                        'mon' => 12,
                        'day' => 12,
                        'year' => 2112
                },
                'id_num' => '012345678',
                'picture_2' => 'http://www.example.com/images/mypic.jpg',
                'birth_date' => {
                        'mon' => -5,
                        'day' => 32,
                        'year' => 1984
                },
                'phones' => {
                        'mobile' => 'what?',
                        'home' => '123-1234567'
                },
                'other_info' => {
                        'social' => [
                                {
                                        'website' => 'facebook',
                                        'user_id' => '123412341234'
                                },
                                {
                                        'website' => 'noogie.com',
                                        'user_id' => 'snoogens'
                                }
                        ],
                        'bio' => {
                                'en' => 'Born, lives, will die.',
                                'he' => 'Nolad, Chai, Yamut.'
                        }
                },
                '_rejects' => {
                        'education' => {
                                '1' => {
                                        'honors' => {
                                                '_self' => [
                                                        'array(1)'
                                                ]
                                        },
                                        'type' => [
                                                'one_of(Elementary, High School, College/University)'
                                        ],
                                        'school' => [
                                                'min_length(4)'
                                        ],
                                        'end_year' => [
                                                'value_between(1900, 2100)'
                                        ]
                                },
                                '0' => {
                                        'honors' => {
                                                '2' => [
                                                        'length_between(5, 15)'
                                                ]
                                        }
                                }
                        },
                        'death_date' => {
                                'year' => [
                                        'value_between(1900, 2100)'
                                ]
                        },
                        'picture_2' => [
                                'max_length(5)'
                        ],
                        'phones' => {
                                'mobile' => [
                                        'validate'
                                ]
                        },
                        'birth_date' => {
                                'mon' => [
                                        'integer(1)',
                                        'value_between(1, 12)'
                                ],
                                'day' => [
                                        'value_between(1, 31)'
                                ]
                        },
                        'employment' => [
                                'required(1)'
                        ],
                        'other_info' => {
                                'bio' => {
                                        'en' => [
                                                'length_between(100, 300)'
                                        ],
                                        'fr' => [
                                                'required(1)'
                                        ],
                                        'he' => [
                                                'length_between(100, 300)'
                                        ]
                                }
                        },
                        'picture_3' => [
                                'max_length(5)'
                        ],
                        'picture_4' => [
                                'max_length(5)',
                                'validate'
                        ],
                        'picture_1' => [
                                'required(1)'
                        ]
                },
                'picture_3' => 'http://www.example.com/images/mypic.png',
                'picture_4' => 'http://www.example.com/images/mypic.gif',
                'url' => 'http://www.example.com/?id=012345678&One',
                'some_other_thing' => 'I\'d like to tell the whole world that I like to wear women\'s clothing.',
                'pictures' => [
                        'http://www.example.com/images/mypic.jpg',
                        'http://www.example.com/images/mypic.png',
                        'http://www.example.com/images/mypic.gif',
                        'http://www.example.com/images/default.png'
                ],
                'picture_1' => 'http://www.example.com/images/default.png'
        }