NAME

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

VERSION

version 0.3

DESCRIPTION

The following document provides examples for Brannigan usage. Brannigan is a system for validatin 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 distrubutions.

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 new_post 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'
}

Umm, well, I'll probably complete this document soon.