logo
Free, unlimited AI code reviews that run on commit
git-lrc git-lrc GitHub Install Now We'd appreciate a star git-lrc - Free, unlimited AI code reviews that run on commit | Product Hunt git-lrc - Free, unlimited AI code reviews that run on commit | Product Hunt

Declare::Constraints::Simple - Declarative Validation of Data Structures

Author

       Robert 'phaylon' Sedlacek "<phaylon@dunkelheit.at>"

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.

Installation

         perl Makefile.PL
         make
         make test
         make install

       For details read Module::Install.

Name

       Declare::Constraints::Simple - Declarative Validation of Data Structures

Requires

       Carp::Clan, aliased, Class::Inspector, Scalar::Util, overload and Test::More (for build).

See Also

       Declare::Constraints::Simple::Library, Declare::Constraints::Simple::Result,
       Declare::Constraints::Simple::Base, Module::Install

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'

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.

Usage

       This is just a brief intro. For details read the documents mentioned in "SEE ALSO".

   ConstraintImport
         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.

   BuildingaProfile
       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.

   ApplyingaProfiletoaDataStructure
       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.

   CreatingyourownLibraries
       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