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

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

=head1 DESCRIPTION

The following document provides examples for L<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.

=head1 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).

=head2 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 C<< $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.

=head2 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, C<$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.

=head2 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);

C<$output2> will be:

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

=head2 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);

C<$output3> will be:

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

=head1 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 C<$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'
	}