NAME

Params::Validate::Dependencies::Extending - how to extend Params::Validate::Dependencies

DESCRIPTION

How to extend Params::Validate::Dependencies, and a discussion of its internals.

PRE-REQUISITES

Before even thinking about extending this module you should understand references, in particular code-references and closures, and also understand objects in perl.

WHAT THE *_of FUNCTIONS REALLY DO

If you've read the documentation for Params::Validate::Dependencies, then you'd think that it returns a code-ref which is a closure over the original arguments, and that they're implemented something like this:

sub any_of {
  my @options = @_;
  return sub {
    my $hashref = shift;
    ... do stuff with @options and $hashref ...
  }
}

In version 1, that was exactly what was happening. But then I wanted to make them self-documenting.

Now, they still return a code-ref, but that code-ref is blessed into a class - Params::Validate::Dependencies::any_of in the case of the closures generated by any_of - so that I can attach a little bit of extra metadata. They now look something like this:

sub any_of {
  my @options = @_;
  bless sub {
    ... some extra stuff ...
    my $hashref = shift;
    ... do stuff with @options and $hashref ...
  }, 'Params::Validate::Dependencies::any_of';
}

There are four such classes, one for each of the *_of functions.

HOW THE AUTO-DOCUMENTATION WORKS

Each of those four classes is a sub-class of Params::Validate::Dependencies::Documenter. When you call Params::Validate::Dependencies::document() and pass it one of those objects, it calls the object's _document method. That puts P::V::D into "documentation mode" by setting a global variable to the object itself, and then executes the object's underlying code-ref.

In the example above, where it says "... some extra stuff ...", we actually have this:

if($Params::Validate::Dependencies::DOC) {
  return $Params::Validate::Dependencies::DOC->_doc_me(list => \@options);
}

so, now that <$DOC> has a value, the code-ref doesn't bother validating anything, it instead calls its _doc_me method, and passes it the options that the closure was originally created with.

Finally, _doc_me, which now has both the data that was originally closed over, and the object itself, it can construct a useful string of documentation. To do this it gets the name of the original factory function by calling the object's name method - in the case of a Params::Validate::Dependencies::any_of object this returns 'any_of' - and, so that it can use 'and' or 'or' when constructing lists, it gets that by calling the join_with method.

It then iterates over the original list of options, scalars first followed by code-reffy objects, recursing into objects and getting them to document themselves.

The end result of that, given a code-ref created thus:

any_of(
  qw(alpha beta),
  all_of(
    qw(foo bar),
    none_of('barf')
  ),
  one_of(qw(quux garbleflux))
)

You will get back documentation like this:

any of ('alpha', 'beta', all of ('foo', 'bar' and none of ('barf')) or one of ('quux' or 'garbleflux'))

which is admittedly not perfect but is better than having to write the blasted stuff yourself.

ADDING YOUR OWN VALIDATORS WITHOUT DOCUMENTATION

If you can't be bothered with making your validators self-documenting, then you can just have them return a closure and be done with it:

sub two_of {
  my @options = @_;
  return sub {
    my $hashref = shift;
    my $count = 0;
    foreach my $option (@options) {
      $count++ if(
        (!ref($option) && exists($hashref->{$option})) ||
        (ref($option) && $option->($hashref))
      );
    }
    return ($count == 2);
  }
}

There is an example of this in t/05-extra-validator-without-doco.t. In fact it's this very example.

ADDING YOUR OWN VALIDATORS WITH AUTO-DOCUMENTATION

If you want to do the full-fat implementation complete with auto-doc, then you will need to implement one teeny-tiny class, as well as modify your factory function to return an object of that class and to look out for "documentation mode" being turned on. The example above would be modifed thus:

sub two_of {
  my @options = @_;
  return bless sub {
    if($Params::Validate::Dependencies::DOC) {
      return $Params::Validate::Dependencies::DOC->_doc_me(list => \@options);
    }
    ...
  }, 'Params::Validate::Dependencies::two_of'
}

and the corresponding class would look like:

package Params::Validate::Dependencies::two_of;
use base qw(Params::Validate::Dependencies::Documenter);
sub join_with { return 'or'; }
sub name { return 'two_of'; }

It is suggested that you combine class and function together, and have it export the function, as can be seen in the example in t/lib/Params/Validate/Dependencies/two_of.pm, which is used in t/06-extra-validator-with-doco.t.

CUSTOM AUTO-DOCUMENTATION

If the _doc_me method described above can't cope with your new validation function then your class need not bother implementing the name or join_with methods, but your function must, when <$Params::Validate::Dependencies::DOC> is set, return a string. Anything that you want the exclusively() validator to pay attention to needs to be single quoted, with any embedded single quotes preceded by a back-slash.

Best practice is to return something that looks very similar to how your function was invoked in the first place.

For an example see how the exclusively() function documents itself.

RESERVED CLASSES

You may not write your own validators or classes for the following, as they are implemented by Params::Validate::Dependencies itself. If you try implementing them, strange things may happen:

none_of
one_of
any_of
all_of
exclusively

SEE ALSO

Params::Validate::Dependencies

SOURCE CODE REPOSITORY

git://github.com/DrHyde/perl-modules-Params-Validate-Dependencies.git

https://github.com/DrHyde/perl-modules-Params-Validate-Dependencies/

COPYRIGHT and LICENCE

Copyright 2024 David Cantrell <david@cantrell.org.uk>

This documentation is free-as-in-speech software. It may be used, distributed, and modified under the terms of the Creative Commons Attribution-Share Alike 2.0 UK: England & Wales License, whose text you may read at http://creativecommons.org/licenses/by-sa/2.0/uk/.

CONSPIRACY

This documentation is also free-as-in-mason.