NAME
Brannigan - Flexible library for validating and processing input.
SYNOPSIS
use Brannigan;
my %schema1 = ( params => ... );
my %schema2 = ( params => ... );
my %schema3 = ( params => ... );
# use the OO interface
my $b = Brannigan->new();
$b->register_schema('schema1', \%schema1);
$b->register_schema('schema2', \%schema2);
$b->register_schema('schema3', \%schema3);
my $rejects = $b->process('schema1', \%params);
if ($rejects) {
die $rejects;
}
# %params is valid and ready for use.
# Or use the functional interface
my $rejects = Brannigan::process(\%schema1, \%params);
if ($rejects) {
die $rejects;
}
For a more comprehensive example, see "MANUAL".
DESCRIPTION
Brannigan is an attempt to ease the pain of collecting, validating and processing input parameters in user-facing applications. It's designed to answer both of the main problems that such applications face:
Simple User Input
Brannigan can validate and process simple, "flat" user input, possibly coming from web forms.
Complex Data Structures
Brannigan can validate and process complex data structures, possibly deserialized from JSON or XML data sent to web services and APIs.
Brannigan's approach to data validation is as follows: define a schema of parameters and their validation rules, and let the module automatically examine input parameters against this structure. Brannigan provides you with common validators that are used everywhere, and also allows you to create custom validators easily. This structure also defines how, if at all, the input should be processed. This is akin to schema-based validations such as XSD, but much more functional, and most of all flexible.
Check the next section for an example of such a schema. Schemas can extend other schemas, allowing you to be much more flexible in certain situations. Imagine you have a blogging application. A base schema might define all validations and processing needed in order to create a new blog post from user input. When editing a post, however, some parameters that were required when creating the post might not be required now, and maybe new parameters are introduced. Inheritance helps you avoid repeating yourself.
MANUAL
Let's look at a complete usage example. Do not be alarmed by the size of these schemas, as they showcases almost all features of Brannigan.
package MyApp;
use strict;
use warnings;
use Brannigan;
# Create a new Brannigan object
my $b = Brannigan->new({ handle_unknown => "ignore" });
# Create a custom 'forbid_words' validator that can be used in any schema.
$b->register_validator('forbid_words', sub {
my $value = shift;
foreach (@_) {
return 0 if $value =~ m/$_/;
}
return 1;
});
# Create a schema for validating input to a create_post function
$b->register_schema('create_post', {
params => {
subject => {
required => 1,
length_between => [3, 40],
},
text => {
required => 1,
min_length => 10,
validate => sub {
my $value = shift;
return defined $value && $value =~ m/^lorem ipsum/ ? 1 : 0;
}
},
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],
postprocess => sub {
my $val = shift;
return $val == 1 ? 'reviews' :
$val == 2 ? 'receips' : 'general';
},
},
id => {
required => 1,
exact_length => 10,
value_between => [1000000000, 2000000000],
},
array_of_ints => {
array => 1,
min_length => 3,
values => {
integer => 1,
},
preprocess => sub {
# Sometimes you'll find that input that is supposed to be
# an array is received as a single non-array item, most
# often because deserializers do not know the item should
# be in an array. This is common in XML inputs. A
# preprocess function can be used to fix that.
my $val = shift;
return [$val]
if defined $val && ref $val ne 'ARRAY';
return $val;
}
},
hash_of_langs => {
hash => 1,
keys => {
en => {
required => 1,
},
},
},
},
});
# Create a schema for validating input to an edit_post function. The schema
# inherits the create_post schema with one small change.
$b->register_schema('edit_post', {
inherits_from => 'create_post',
params => {
subject => {
required => 0, # subject is no longer required
}
}
});
# Now use Brannigan to validate input in your application:
sub create_post {
my ($self, $params) = @_;
# Process and validate the parameters with the 'post' schema
my $rejects = $b->process('create_post', $params);
if ($rejects) {
# Turn validation errors into a structure that fits your application
die list_errors($rejects);
}
# Validation and processing suceeded, save the parameters to a database
$self->_save_post_to_db($params);
}
sub edit_post {
my ($self, $id, $params) = @_;
# Process and validate the parameters with the 'edit_post' schema
my $rejects = $b->process('edit_post', $params);
if ($rejects) {
# Turn validation errors into a structure that fits your application
die list_errors($rejects);
}
# Validation and processing succeeded, update the post in the database
$self->_update_post_in_db($params);
}
HOW BRANNIGAN WORKS
In essence, Brannigan works in five stages (which all boil down to one single command):
- 1. SCHEMA PREPARATION
-
Brannigan receives the name of a validation schema, and a hash reference of input parameters. Brannigan then loads the schema and prepares it (merging it with inherited schemas, if any) for later processing. Finalized schemas are cached for improved performance.
- 2. DATA PREPROCESSING
-
Brannigan invokes all
preprocess
functions defined in the schema on the input data, if there are any. These functions are allowed to modify the input.Configured default values will also be provided to their respective parameters in this stage as well, if those parameters are not provided in the input.
- 3. DATA VALIDATION
-
Brannigan invokes all validation methods defined in the schema on the input data, and generates a hash reference of rejected parameters, if there were any. For every parameter in this hash-ref, an array-ref of failed validations is created.
If one or more parameters failed validation, the next step (data postprocessing) will be skipped.
- 4. DATA POSTPROCESSING
-
If the previous stage (validation) did not fail, Brannigan will call every
postprocess
function defined in the schema. There are two types ofpostprocess
functions:parameter-specific
These are defined on specific parameters. They get the parameter's value and should return a new value for the parameter (possibly the same one, but they must return a value).
global
The schema may also have one global
postprocess
function. This function gets the entire parameter hash-ref as input. It is free to modify the hash-ref as it sees fit. The function should not return any value.
- 5. FINAL RESULT
-
If all input parameters passed validation, an undefined value is returned to the caller. Otherwise, a hash-reference of rejects is returned. This is a flattened structure where keys are "fully qualified" parameter names (meaning dot notation is used for nested parameters), and values are hash-references containing the validators for which the parameter had failed. For example, let's look at the following rejects hash-ref:
{ 'subject' => { required => 1 }, 'text' => { max_length => 500 }, 'pictures.2' => { matches => qr!^http://! }, 'phone.mobile' => { required => 1 } }
This hash-ref tells us:
HOW SCHEMAS LOOK
The validation/processing schema defines the structure of the data you're expecting to receive, along with information about the way it should be validated and processed. Schemas are created by passing them to the Brannigan constructor. You can pass as many schemas as you like, and these schemas can inherit from other schemas.
A schema is a hash-ref that contains the following keys:
inherits_from
Either a scalar naming a different schema or an array-ref of schema names. The new schema will inherit all the properties of the schema(s) defined by this key. If an array-ref is provided, the schema will inherit their properties in the order they are defined. See the "CAVEATS" section for some "heads-up" about inheritance.
params
Defines the expected input. This key takes a hash-ref whose keys are the names of input parameters as they are expected to be received. The values are also hash references which define the necessary validation functions to assert for the parameters, and other optional settings such as default values, post- and pre- processing functions, and custom validation functions.
For example, if a certain parameter, let's say 'subject', must be between 3 to 10 characters long, then your schema will contain:
subject => { length_between => [3, 10] }
If a "subject" parameter sent to your application fails the "length_between" validator, then the rejects hash-ref described earlier will have the exact same key-value pair as above:
subject => { length_between => [3, 10] }
The following extra keys can also be used in a parameter's configuration:
validate: Used to create a custom validation function for the parameter. Accepts a subroutine reference. The subroutine accepts the value from the input as its only parameter, and returns a boolean value indicating whether the value passed the validation or not.
For example, this custom validation function requires that the 'subject' input parameter will always begin with the string "lorem ipsum":
subject => { length_between => [3, 10], validate => sub { my $value = shift; return $value =~ m/^lorem ipsum/ ? 1 : 0; } }
If a parameter fails a custom validation function, 'validate' will be added to the failed validations hash-ref of the parameter in the rejects hash-ref:
subject => { length_between => [3, 10], validate => 1 }
default: Used to set a default value for parameters that are not required and are not provided in the input hash-ref. Accepts a scalar value or a subroutine reference. In the latter case, the subroutine will be called with no parameters, and it should return the generated default value.
subject => { length_between => [3, 10], default => 'lorem ipsum' } # Or... subject => { length_between => [3, 10], default => sub { UUID->new->hex } }
Note that default values are given to missing parameters before the validation stage, meaning they must conform with the parameters' validators.
preprocess: Used to process parameter values before validation functions are called. This can be useful to trim leading or trailing whitespace from string values, or turning scalars into arrays (a common task for XML inputs where the deserializer cannot tell whether an item actually belongs in an array or not). Accepts a subroutine reference with the parameter's value from the input. The function must return the new value for the parameter, even if it had decided not to do any actual changes.
postprocess: Similar to
preprocess
, but happens after validation functions had been called.subject => { required => 1, length_between => [3, 10], preprocess => sub { # Trim whitespace before validating my $value = shift; $value =~ s/^\s\*//; $value =~ s/\s\*$//; return $value; } validate => sub { # Ensure value does not start with "lorem ipsum" my $value = shift; return $value =~ m/^lorem ipsum/ ? 0 : 1; }, postprocess => sub { # Lowercase the value my $value = shift; return lc $value; } }
postprocess
Global postprocessing function. If provided, it will be called after all preprocessing, input validation, and parameter-specific postprocessing had completed. As opposed to parameter-specific postprocess functions, this one receives the complete parameter hash-ref as its only input. It is not expected to return any values. It may modify the parameter hash-ref as it sees fit.
BUILT-IN VALIDATORS
{ required => $boolean }
If $boolean
has a true value, this method will check that a required parameter was indeed provided; otherwise (i.e. if $boolean
is not true) this method will simply return a true value to indicate success.
You should note that if a parameter is required, and a non-true value is received (i.e. 0 or the empty string ""), this method considers the requirement as fulfilled (i.e. it will return true). If you need to make sure your parameters receive true values, take a look at the is_true()
validation method.
Please note that if a parameter is not required and indeed isn't provided with the input parameters, any other validation methods defined on the parameter will not be checked.
{ is_true => $boolean }
If $boolean
has a true value, this method will check that $value
has a true value (so, $value
cannot be 0 or the empty string); otherwise (i.e. if $boolean
has a false value), this method does nothing and simply returns true.
{ length_between => [ $min_length, $max_length ] }
Makes sure the value's length (stringwise) is inside the range of $min_length
-$max_length
, or, if the value is an array reference, makes sure it has between $min_length
and $max_length
items.
{ min_length => $min_length }
Makes sure the value's length (stringwise) is at least $min_length
, or, if the value is an array reference, makes sure it has at least $min_length
items.
{ max_length => $max_length }
Makes sure the value's length (stringwise) is no more than $max_length
, or, if the value is an array reference, makes sure it has no more than $max_length
items.
{ exact_length => $length }
Makes sure the value's length (stringwise) is exactly $length
, or, if the value is an array reference, makes sure it has exactly $exact_length
items.
{ integer => $boolean }
If boolean is true, makes sure the value is an integer.
{ function => $boolean }
If boolean is true, makes sure the value is a function (subroutine reference).
{ value_between => [ $min_value, $max_value ] }
Makes sure the value is between $min_value
and $max_value
.
{ min_value => $min_value }
Makes sure the value is at least $min_value
.
{ max_value => $max_value }
Makes sure the value is no more than $max_value
.
{ array => $boolean }
If $boolean
is true, makes sure the value is actually an array reference.
{ hash => $boolean }
If $boolean
is true, makes sure the value is actually a hash reference.
{ one_of => \@values }
Makes sure a parameter's value is one of the provided acceptable values.
{ matches => $regex }
Returns true if $value
matches the regular express (qr//
) provided. Will return false if $regex
is not a regular expression.
{ min_alpha => $integer }
Returns a true value if $value
is a string that has at least $integer
alphabetic (A-Z
and a-z
) characters.
{ max_alpha => $integer }
Returns a true value if $value
is a string that has at most $integer
alphabetic (A-Z
and a-z
) characters.
{ min_digits => $integer }
Returns a true value if $value
is a string that has at least $integer
digits (0-9
).
{ max_digits => $integer }
Returns a true value if $value
is a string that has at most $integer
digits (0-9
).
{ min_signs => $integer }
Returns a true value if $value
has at least $integer
special or sign characters (e.g. %^&!@#
, or basically anything that isn't A-Za-z0-9
).
{ max_signs => $integer }
Returns a true value if $value
has at most $integer
special or sign characters (e.g. %^&!@#
, or basically anything that isn't A-Za-z0-9
).
{ max_consec => $integer }
Returns a true value if $value
does not have a sequence of consecutive characters longer than $integer
. Consequtive characters are either alphabetic (e.g. abcd
) or numeric (e.g. 1234
).
{ max_reps => $integer }
Returns a true value if $value
does not contain a sequence of a repeated character longer than $integer
. So, for example, if $integer
is 3, then "aaa901" will return true (even though there's a repetition of the 'a' character it is not longer than three), while "9bbbb01" will return false.
ADVANCED FEATURES AND TIPS
COMPLEX DATA STRUCTURES
Brannigan can validate and process hash references of arbitrary complexity. Input parameters may also be hash or array references.
For arrays, the parameter needs to be marked with array => 1
. The validations and processing for the array's values are then provided as a hash reference named values
. For example:
pictures => {
array => 1,
length_between => [1, 5],
values => {
min_length => 3,
validate => sub {
my $value = shift;
return $value =~ m!^http://! ? 1 : 0;
}
}
}
In this example, "pictures" is an array parameter. When provided, the array must contain between 1 and 5 items. Every item in the array must be a string of 3 characters or more, and must begin with the prefix "http://".
For hashes, the parameter needs to be marked with hash => 1
. The validations and processing for the hash's attributes are then provided as a hash reference named keys
. For example:
name => {
hash => 1,
keys => {
first_name => {
length_between => [3, 10],
},
last_name => {
required => 1,
min_length => 3
}
}
}
In this example, "name" is a hash paremeter. When provided, it must contain an attribute called "first_name", which is an optional string between 3 or 10 characters long, and "last_name", which is a required string at least 3 characters longs.
Array and hash parameters can also accept default values:
complex_param => {
hash => 1,
keys => {
...
},
default => { key1 => 'def1', key2 => 'def2' }
}
Hash and arrays can fail validation in two ways: they can fail as a unit (for example, schemas can enforce that an array will have between 2 and 5 items), and specific items within them can fail (for example, schemas can enforce that items in an array will be integers lower than 100).
An array that failed as a unit will appear in the rejects hash-ref with its own name. A specific array item or hash key that failed validation will appear with dot notation:
'name.first_name' => { length_between => [3, 10] },
'name.last_name' => { required => 1 },
'pictures' => { exact_length => 3 },
'numbers.1' => { max_value => 10 },
In this example, specific keys failed in the "name" hash parameter. The "pictures" array parameter failed as a unit (it should have exactly 3 items). The second item in the "numbers" array parameter failed the "max_value" validator too.
Brannigan's data structure support is infinitely recursive:
pictures => {
array => 1,
values => {
hash => 1,
keys => {
filename => {
min_length => 5,
},
source => {
hash => 1,
keys => {
website => {
validate => sub { ... },
},
license => {
one_of => [qw/GPL FDL CC/],
},
},
},
},
},
}
CROSS-SCHEMA CUSTOM VALIDATION METHODS
Ad-hoc validate
functions are nice, but when you want to use the same custom validation function in multiple places inside your schema (or in multiple schemas), this can become unwieldy.
Brannigan provides a simple mechanism to create custom, named validation functions that can be used across schemas as if they were internal methods.
This example creates a validation function called "forbid_words", which fails string parameters that contain certain words:
my $b = Brannigan->new();
$b->register_validator('forbid_words', sub {
my ($value, @forbidden) = @_;
foreach (@forbidden) {
return 0 if $value =~ m/$_/;
}
return 1;
});
$b->register_schema('user_input', {
params => {
text => {
required => 1,
forbid_words => ['curse_word', 'bad_word', 'ugly_word'],
}
}
});
Note how the custom validation function accepts the value provided in the input, and whatever was provided to 'forbid_words' in the configuration of the specific parameter. In this case, the parameter called "text" forbids the words "curse_word", "bad_word" and "ugly_word".
If a parameter fails a named custom validation function, it will be added to the rejects hash-ref like any other built-in validation function:
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.
Note that you do not have to register a named validator before you register a schema that uses it. You can register the schema first.
REPEATING RULES FOR MULTIPLE PARAMETERS
In previous versions, Brannigan allowed providing rules to multiple parameters via regular expressions. This feature has been removed in version 2.0. Instead, users can take advantage of the fact that schemas are simply Perl structures and reuse rules via variables:
my $date = { required => 1, matches => qr/^\d{4}-\d{2}-\d{2}$/ };
my $schema = {
name => 'person',
params => {
birth_date => $date,
death_date => $date
}
};
CONSTRUCTOR
new( [ %options ] )
Creates a new instance of Brannigan. Schemas must be registered separately using the register_schema
method.
Options:
handle_unknown
What to do with input parameters that are not defined in the processing schema. Values: 'ignore' (default, keep unknown parameters as they are), 'remove' (delete unknown parameters from the input), 'reject' (add to rejects and fail the processing).
OBJECT METHODS
register_schema( $name, \%schema )
Registers a validation schema with the given name. If a schema with the same name already exists, it will be overridden. The schema hash-ref should not contain a name
key as it's provided separately. Returns the Brannigan
object itself for chain-ability.
register_validator( $name, $code )
Registers a new named validator function. $code
is a reference to a subroutine that receives a value as a parameter and returns a boolean value indicating whether the value is valid or not. The method can be used to override built-in validation functions.
handle_unknown( [$value] )
Gets or sets the behavior for handling unknown input parameters. Accepted values: 'ignore', 'remove', 'reject'.
process( $schema, \%params )
Receives the name of a schema and a hash reference of input parameters. Performs pre-processing, validation and post-processing as described in the manual.
Any processing that modifies the input is performed in-place.
Returns an undefined value if there were no rejects. Returns a hash reference of rejects if there were any.
FUNCTIONAL INTERFACE
process( \%schema, \%params )
Accepts a schema hash-ref and an input hash-ref, and performs pre-processing, validation and post-processing. If no parameters failed validation, an undefined value is returned. Otherwise a hash reference of rejects is returned.
Note that this interface does not allow for custom validation functions and schema inheritance. You are not required to give the schema a name when using this interface.
my $rejects = Brannigan::process( $schema, $params );
UPGRADING FROM 1.x TO 2.0
Version 2.0 of Brannigan includes significant breaking changes. This guide will help you upgrade your existing code.
BREAKING CHANGES
Constructor and Schema Registration
Old (1.x):
my $b = Brannigan->new(
{ name => 'schema1', params => { ... } },
{ name => 'schema2', params => { ... } }
);
New (2.0):
my $b = Brannigan->new(); # No schemas in constructor
$b->register_schema('schema_name', { params => { ... } });
$b->register_schema('another_schema', { params => { ... } });
Method Names
Several methods have been renamed for clarity:
add_scheme()
=>register_schema()
custom_validation()
=>register_validator()
Return Value Changes
The process()
method now returns different values:
Old (1.x): Always returned a hash-ref with processed parameters and optional _rejects
key.
New (2.0): Returns undef
on success, hash-ref of rejects on failure. Processing happens in-place on the input hash-ref.
# Old style
my $result = $b->process('schema', \%params);
if ($result->{_rejects}) {
# Handle errors
}
# New style
my $rejects = $b->process('schema', \%params);
if ($rejects) {
# Handle $rejects hash-ref directly
}
# %params is modified in-place with processed values
Error Structure Changes
The structure of validation errors has changed significantly:
Old (1.x):
{
_rejects => {
parameter => ['required(1)', 'min_length(5)'],
nested => { path => { param => ['max_value(100)'] } }
}
}
New (2.0):
{
'parameter' => { required => 1, min_length => 5 },
'nested.path.param' => { max_value => 100 }
}
Key changes:
Error paths are flattened using dot notation
Validator names and arguments are returned as key-value pairs
No more
_rejects
wrapperUnknown parameters are reported with
{ unknown => 1 }
Processing Function Changes
parse
functions →postprocess
functionsDefault values are now calculated before validation (they can fail validation)
postprocess
functions must return a replacement value, not a hash-ref
Old (1.x):
parse => sub {
my $value = shift;
return { parameter_name => process($value) };
}
New (2.0):
postprocess => sub {
my $value = shift;
return process($value); # Return the processed value directly
}
NEW FEATURES
Preprocessing
You can now preprocess input before validation:
params => {
username => {
preprocess => sub { lc }, # Lowercase parameter value
required => 1,
min_length => 3
}
}
Global Postprocessing
Add a global postprocess function to your schema:
{
params => { ... },
postprocess => sub {
my $params = shift;
$params->{computed_field} = calculate($params);
# Modify $params in-place, no return value needed
}
}
Unknown Parameter Handling
Control how unknown parameters are handled:
my $b = Brannigan->new();
$b->handle_unknown('ignore'); # Default: preserve unknown params
$b->handle_unknown('remove'); # Remove unknown params
$b->handle_unknown('reject'); # Fail validation on unknown params
This works at all nesting levels (top-level, nested hashes, array items).
Enhanced Default Values
Default values now work in nested structures:
params => {
users => {
array => 1,
values => {
hash => 1,
keys => {
name => { required => 1 },
role => { default => 'user' }, # Applied to each array item
active => { default => 1 }
}
}
}
}
Improved Schema Inheritance
Schema inheritance now works recursively and merges parameter definitions:
$b->register_schema('base', {
params => {
name => { required => 1, max_length => 50 }
}
});
$b->register_schema('extended', {
inherits => 'base',
params => {
name => { min_length => 2 }, # Merges with base constraints
email => { required => 1 } # Additional parameter
}
});
REMOVED FEATURES
The following features have been removed:
Parameter groups (use global
postprocess
instead)Regular expression parameter definitions
Scope-local "_all" validators
max_dict
validatorforbidden
validator (usehandle_unknown => 'reject'
instead)ignore_missing
schema option (usehandle_unknown
instead)
AUTHOR
Ido Perlmuter, <ido at ido50 dot net>
BUGS
Please report any bugs or feature requests to https://github.com/ido50/Brannigan.
SUPPORT
You can find documentation for this module with the perldoc command.
perldoc Brannigan
ACKNOWLEDGEMENTS
Brannigan was inspired by Oogly (Al Newkirk) and the "Ketchup" jQuery validation plugin (http://demos.usejquery.com/ketchup-plugin/).
LICENSE AND COPYRIGHT
Copyright 2025 Ido Perlmuter
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
1 POD Error
The following errors were encountered while parsing the POD:
- Around line 1669:
Non-ASCII character seen before =encoding in '→'. Assuming UTF-8