NAME
Declare::Constraints::Simple - Declarative Validation of Data Structures
SYNOPSIS
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'
DESCRIPTION
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.
USAGE
This is just a brief intro. For details read the documents mentioned in
"SEE ALSO".
Constraint Import
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 Declare::Constraints::Simple::Library 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.
Building a Profile
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.
Applying a Profile to a 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 Declare::Constraints::Simple::Result
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 overloads it's "bool"ean 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.
Creating your own Libraries
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
Declare::Constraints::Simple::Library::Base. 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.
Scoping
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.
SEE ALSO
Declare::Constraints::Simple::Library,
Declare::Constraints::Simple::Result,
Declare::Constraints::Simple::Base, Module::Install
REQUIRES
Carp::Clan, aliased, Class::Inspector, Scalar::Util, overload and
Test::More (for build).
TODO
* 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.
INSTALLATION
perl Makefile.PL
make
make test
make install
For details read Module::Install.
AUTHOR
Robert 'phaylon' Sedlacek "<phaylon@dunkelheit.at>"
LICENSE AND COPYRIGHT
This module is free software, you can redistribute it and/or modify it
under the same terms as perl itself.