1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204
|
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.
|