Declare::Constraints::Simple - Declarative Validation of Data Structures
use Declare::Constraints::Simple-All;
my $profile = IsHashRef( -keys => HasLength, -values => IsArrayRef( IsObject ));
my $result1 = $profile->(undef); print $result1->message, "\n"; # 'Not a HashRef'
my $result2 = $profile->({foo => [23]});
print $result2->message, "\n"; # 'Not an Object'
print $result2->path, "\n"; # 'IsHashRef[val foo].IsArrayRef[0].IsObject'
The main purpose of this module is to provide an easy way to build a profile to validate a data structure. It does this by giving you a set of declarative keywords in the importing namespace.
This is just a brief intro. For details read the documents mentioned in SEE ALSO.
use Declare::Constraints::Simple-All;
The above command imports all constraint generators in the library into
the current namespace. If you want only a selection, use only:
use Declare::Constraints::Simple Only => qw(IsInt Matches And);
You can find all constraints (and constraint-like generators, like
operators. In fact, And above is an operator. They're both implemented
equally, so the distinction is a merely philosophical one) documented in
the the Declare::Constraints::Simple::Library manpage pod. In that document you
will also find the exact parameters for their usage, so this here is just
a brief Intro and not a coverage of all possibilities.
You can use these constraints by building a tree that describes what data structure you expect. Every constraint can be used as sub-constraint, as parent, if it accepts other constraints, or stand-alone. If you'd just say
my $check = IsInt; print "yes!\n" if $check->(23);
it will work too. This also allows predefining tree segments, and nesting them:
my $id_to_objects = IsArrayRef(IsObject);
Here $id_to_objects would give it's OK on an array reference
containing a list of objects. But what if we now decide that we actually
want a hashref containing two lists of objects? Behold:
my $object_lists = IsHashRef( HasAllKeys( qw(good bad) ), OnHashKeys( good => $id_to_objects, bad => $id_to_objects ));
As you can see, constraints like IsArrayRef and IsHashRef allow you
to apply constraints to their keys and values. With this, you can step
down in the data structure.
Constraints return just code references that can be applied to one value (and only one value) like this:
my $result = $object_lists->($value);
After this call $result contains a
the Declare::Constraints::Simple::Result manpage object. The first think one wants
to know is if the validation succeeded:
if ($result->is_valid) { ... }
This is pretty straight forward. To shorten things the result object also
the overload manpages it's boolean context. This means you can alternatively
just say
if ($result) { ... }
However, if the result indicates a invalid data structure, we have a few
options to find out what went wrong. There's a human parsable message in
the message accessor. You can override these by forcing it to a
message in a subtree with the Message declaration. The stack
contains the name of the chain of constraints up to the point of failure.
You can use the path accessor for a joined string path representing
the stack.
You can declare a package as a library with
use Declare::Constraints::Simple-Library;
which will install the base class and helper methods to define constraints. For a complete list read the documentation in the Declare::Constraints::Simple::Library::Base manpage. You can use other libraries as base classes to include their constraints in your export possibilities. This means that with a package setup like
package MyLibrary; use warnings; use strict;
use Declare::Constraints::Simple-Library; use base 'Declare::Constraints::Simple::Library';
constraint 'MyConstraint', sub { return _result(($_[0] >= 12), 'Value too small') };
1;
you can do
use MyLibrary-All;
and have all constraints, from the default library and yours from above, installed into your requesting namespace. You can override a constraint just by redeclaring it in a subclass.
Sometimes you want to validate parts of a data structure depending on another part of it. As of version 2.0 you can declare scopes and store results in them. Here is a complete example:
my $constraint = Scope('foo', And( HasAllKeys( qw(cmd data) ), OnHashKeys( cmd => Or( SetResult('foo', 'cmd_a', IsEq('FOO_A')), SetResult('foo', 'cmd_b', IsEq('FOO_B')) ), data => Or( And( IsValid('foo', 'cmd_a'), IsArrayRef( IsInt )), And( IsValid('foo', 'cmd_b'), IsRegex )) )));
This profile would accept a hash references with the keys cmd and
data. If cmd is set to FOO_A, then data has to be an array
ref of integers. But if cmd is set to FOO_B, a regular expression
is expected.
the Declare::Constraints::Simple::Library manpage, the Declare::Constraints::Simple::Result manpage, the Declare::Constraints::Simple::Base manpage, the Module::Install manpage
the Carp::Clan manpage, the aliased manpage, the Class::Inspector manpage, the Scalar::Util manpage, the overload manpage and the Test::More manpage (for build).
Examples.
A list of questions that might come up, together with their answers.
A Custom constraint that takes a code reference.
Create stack objects that stringify to the current form, but can hold more data.
Give the Message constraint the ability to get the generated
constraint inserted in the message. A possibility would be to replace
__Value__ and __Message__. It might also accept code references, which
return strings.
Allow the IsCodeRef constraint to accept further constraints. One
might like to check, for example, the refaddr of a closure.
A Captures constraint that takes a regex and can apply other
constraints to the matches.
???
Profit.
perl Makefile.PL make make test make install
For details read the Module::Install manpage.
Robert 'phaylon' Sedlacek <phaylon@dunkelheit.at>
This module is free software, you can redistribute it and/or modify it under the same terms as perl itself.