File Coverage

blib/lib/Brannigan.pm
Criterion Covered Total %
statement 33 34 97.0
branch 8 12 66.6
condition 7 18 38.8
subroutine 8 8 100.0
pod 4 4 100.0
total 60 76 78.9


line stmt bran cond sub pod time code
1             package Brannigan;
2              
3             # ABSTRACT: Comprehensive, flexible system for validating and parsing input, mainly targeted at web applications.
4              
5             our $VERSION = "1.100001";
6             $VERSION = eval $VERSION;
7              
8 3     3   40629 use warnings;
  3         4  
  3         84  
9 3     3   9 use strict;
  3         4  
  3         49  
10 3     3   1120 use Brannigan::Tree;
  3         6  
  3         1232  
11              
12             =head1 NAME
13              
14             Brannigan - Comprehensive, flexible system for validating and parsing input, mainly targeted at web applications.
15              
16             =head1 SYNOPSIS
17              
18             use Brannigan;
19              
20             my %scheme1 = ( name => 'scheme1', params => ... );
21             my %scheme2 = ( name => 'scheme2', params => ... );
22             my %scheme3 = ( name => 'scheme3', params => ... );
23              
24             # use the OO interface
25             my $b = Brannigan->new(\%scheme1, \%scheme2);
26             $b->add_scheme(\%scheme3);
27              
28             my $parsed = $b->process('scheme1', \%params);
29             if ($parsed->{_rejects}) {
30             die $parsed->{_rejects};
31             } else {
32             return $parsed;
33             }
34              
35             # Or use the functional interface
36             my $parsed = Brannigan::process(\%scheme1, \%params);
37             if ($parsed->{_rejects}) {
38             die $parsed->{_rejects};
39             } else {
40             return $parsed;
41             }
42              
43             For a more comprehensive example, see L in this document
44             or the L document.
45              
46             =head1 DESCRIPTION
47              
48             Brannigan is an attempt to ease the pain of collecting, validating and
49             parsing input parameters in web applications. It's designed to answer both of
50             the main problems that web applications face:
51              
52             =over 2
53              
54             =item * Simple user input
55              
56             Brannigan can validate and parse simple, "flat", user input, possibly
57             coming from web forms.
58              
59             =item * Complex data structures
60              
61             Brannigan can validate and parse complex data structures, possibly
62             deserialized from JSON or XML data sent to web services and APIs.
63              
64             =back
65              
66             Brannigan's approach to data validation is as follows: define a structure
67             of parameters and their needed validations, and let the module automatically
68             examine input parameters against this structure. Brannigan provides you
69             with common validation methods that are used everywhere, and also allows
70             you to create custom validations easily. This structure also defines how,
71             if at all, the input should be parsed. This is akin to schema-based
72             validations such as XSD, but much more functional, and most of all
73             flexible.
74              
75             Check the next section for an example of such a structure. I call this
76             structure a validation/parsing scheme. Schemes can inherit all the properties
77             of other schemes, which allows you to be much more flexible in certain
78             situations. Imagine you have a blogging application. A base scheme might
79             define all validations and parsing needed to create a new blog post from
80             a user's input. When editing a post, however, some parameters that were
81             required when creating the post might not be required now (so you can
82             just use older values), and maybe new parameters are introduced. Inheritance
83             helps you avoid repeating yourself. You can another scheme which gets
84             all the properties of the base scheme, only changing whatever it is needs
85             changing (and possibly adding specific properties that don't exist in
86             the base scheme).
87              
88             =head1 MANUAL
89              
90             In the following manual, we will look at the following example. It is based
91             on L, but should be fairly understandable for non-Catalyst users.
92             Do not be alarmed by the size of this, this is only because it displays
93             basically every aspect of Brannigan.
94              
95             This example uses L, but should be pretty self explanatory. It's
96             fairly complex, since it details pretty much all of the available Brannigan
97             functionality, so don't be alarmed by the size of this thing.
98              
99             package MyApp::Controller::Post;
100              
101             use strict;
102             use warnings;
103             use Brannigan;
104              
105             # create a new Brannigan object with two validation/parsing schemes:
106             my $b = Brannigan->new({
107             name => 'post',
108             ignore_missing => 1,
109             params => {
110             subject => {
111             required => 1,
112             length_between => [3, 40],
113             },
114             text => {
115             required => 1,
116             min_length => 10,
117             validate => sub {
118             my $value = shift;
119              
120             return undef unless $value;
121            
122             return $value =~ m/^lorem ipsum/ ? 1 : undef;
123             }
124             },
125             day => {
126             required => 0,
127             integer => 1,
128             value_between => [1, 31],
129             },
130             mon => {
131             required => 0,
132             integer => 1,
133             value_between => [1, 12],
134             },
135             year => {
136             required => 0,
137             integer => 1,
138             value_between => [1900, 2900],
139             },
140             section => {
141             required => 1,
142             integer => 1,
143             value_between => [1, 3],
144             parse => sub {
145             my $val = shift;
146            
147             my $ret = $val == 1 ? 'reviews' :
148             $val == 2 ? 'receips' :
149             'general';
150            
151             return { section => $ret };
152             },
153             },
154             id => {
155             required => 1,
156             exact_length => 10,
157             value_between => [1000000000, 2000000000],
158             },
159             '/^picture_(\d+)$/' => {
160             length_between => [3, 100],
161             validate => sub {
162             my ($value, $num) = @_;
163              
164             ...
165             },
166             },
167             picture_1 => {
168             default => 'http://www.example.com/avatar.png',
169             },
170             array_of_ints => {
171             array => 1,
172             min_length => 3,
173             values => {
174             integer => 1,
175             },
176             },
177             hash_of_langs => {
178             hash => 1,
179             keys => {
180             _all => {
181             exact_length => 10,
182             },
183             en => {
184             required => 1,
185             },
186             },
187             },
188             },
189             groups => {
190             date => {
191             params => [qw/year mon day/],
192             parse => sub {
193             my ($year, $mon, $day) = @_;
194             return undef unless $year && $mon && $day;
195             return { date => $year.'-'.$mon.'-'.$day };
196             },
197             },
198             tags => {
199             regex => '/^tags_(en|he|fr)$/',
200             forbid_words => ['bad_word', 'very_bad_word'],
201             parse => sub {
202             return { tags => \@_ };
203             },
204             },
205             },
206             }, {
207             name => 'edit_post',
208             inherits_from => 'post',
209             params => {
210             subject => {
211             required => 0, # subject is no longer required
212             },
213             id => {
214             forbidden => 1,
215             },
216             },
217             });
218              
219             # create the custom 'forbid_words' validation method
220             $b->custom_validation('forbid_words', sub {
221             my $value = shift;
222              
223             foreach (@_) {
224             return 0 if $value =~ m/$_/;
225             }
226              
227             return 1;
228             });
229              
230             # post a new blog post
231             sub new_post : Local {
232             my ($self, $c) = @_;
233              
234             # get input parameters hash-ref
235             my $params = $c->request->params;
236              
237             # process the parameters
238             my $parsed_params = $b->process('post', $params);
239              
240             if ($parsed_params->{_rejects}) {
241             die $c->list_errors($parsed_params);
242             } else {
243             $c->model('DB::BlogPost')->create($parsed_params);
244             }
245             }
246              
247             # edit a blog post
248             sub edit_post : Local {
249             my ($self, $c, $id) = @_;
250              
251             my $params = $b->process('edit_posts', $c->req->params);
252              
253             if ($params->{_rejects}) {
254             die $c->list_errors($params);
255             } else {
256             $c->model('DB::BlogPosts')->find($id)->update($params);
257             }
258             }
259              
260             =head2 HOW BRANNIGAN WORKS
261              
262             In essence, Brannigan works in three stages (which all boil down to one
263             single command):
264              
265             =over
266              
267             =item * Input stage and preparation
268              
269             Brannigan receives a hash-ref of input parameters, or a hash-ref based
270             data structure, and the name of a scheme to validate against. Brannigan
271             then loads the scheme and prepares it (by merging it with inherited schemes)
272             for later processing.
273              
274             =item * Data validation
275              
276             Brannigan invokes all validation methods defined in the scheme on the
277             input data, and generates a hash-ref of rejected parameters. For every
278             parameter in this hash-ref, a list of failed validations is created in an
279             array-ref.
280              
281             =item * Data parsing
282              
283             Regardless of the previous stage, every parsing method defined in the scheme
284             is applied on the relevant data. The data resulting from these parsing
285             methods, along with the values of all input parameters for which no parsing
286             methods were defined, is returned to the user in a hash-ref. This hash-ref
287             also includes a _rejects key whose value is the rejects hash created in
288             the previous stage.
289              
290             The reason I say this stage isn't dependant on the previous stage is
291             simple. First of all, it's possible no parameters failed validation, but
292             the truth is this stage doesn't care if a parameter failed validation. It
293             will still parse it and return it to the user, and no errors are ever
294             raised by Brannigan. It is the developer's (i.e. you) job to decide what
295             to do in case rejects are present.
296              
297             =back
298              
299             =head2 HOW SCHEMES LOOK
300              
301             The validation/parsing scheme defines the structure of the data you're
302             expecting to receive, along with information about the way it should be
303             validated and parsed. Schemes are created by passing them to the Brannigan
304             constructor. You can pass as many schemes as you like, and these schemes
305             can inherit from one another. You can create the Brannigan object that
306             gets these schemes wherever you want. Maybe in a controller of your web
307             app that will directly use this object to validate and parse input it
308             gets, or maybe in a special validation class that will hold all schemes.
309             It doesn't matter where, as long as you make the object available for
310             your application.
311              
312             A scheme is a hash-ref based data structure that has the following keys:
313              
314             =over
315              
316             =item * name
317              
318             Defines the name of the scheme. Required.
319              
320             =item * ignore_missing
321              
322             Boolean value indicating whether input parameters that are not referenced
323             in the scheme should be added to the parsed output or not. Optional,
324             defaults to false (i.e. parameters missing from the scheme will be added
325             to the output as-is). You might find it is probably a good idea to turn
326             this on, so any input parameters you're not expecting to receive from users
327             are ignored.
328              
329             =item * inherits_from
330              
331             Either a scalar naming a different scheme or an array-ref of scheme names.
332             The new scheme will inherit all the properties of the scheme(s) defined
333             by this key. If an array-ref is provided, the scheme will inherit their
334             properties in the order they are defined. See the L section for some
335             "heads-up" about inheritance.
336              
337             =item * params
338              
339             The params key is the most important part of the scheme, as it defines
340             the expected input. This key takes a hash-ref containing the names of
341             input parameters. Every such name (i.e. key) in itself is also a hash-ref.
342             This hash-ref defines the necessary validation methods to assert for this
343             parameter, and optionally a 'parse' and 'default' method. The idea is this: use the name
344             of the validation method as the key, and the appropriate values for this
345             method as the value of this key. For example, if a certain parameter, let's
346             say 'subject', must be between 3 to 10 characters long, then your scheme
347             will contain:
348              
349             subject => {
350             length_between => [3, 10]
351             }
352              
353             The 'subject' parameter's value (from the user input), along with both of
354             the values defined above (3 and 10) will be passed to the C validation
355             method. Now, suppose a certain subject sent to your app failed the
356             C validation; then the rejects hash-ref described
357             earlier will have something like this:
358              
359             subject => ['length_between(3, 10)']
360              
361             Notice the values of the C validation method were added
362             to the string, so you can easily know why the parameter failed the validation.
363              
364             B Aside for the built-in validation methods
365             that come with Brannigan, a custom validation method can be defined for
366             each parameter. This is done by adding a 'validate' key to the parameter,
367             and an anonymous subroutine as the value. As with built-in methods, the
368             parameter's value will be automatically sent to this method. So, for
369             example, if the subject parameter from above must start with the words
370             'lorem ipsum', then we can define the subject parameter like so:
371              
372             subject => {
373             length_between => [3, 10],
374             validate => sub {
375             my $value = shift;
376              
377             return $value =~ m/^lorem ipsum/ ? 1 : 0;
378             }
379             }
380              
381             Custom validation methods, just like built-in ones, are expected to return
382             a true value if the parameter passed the validation, or a false value
383             otherwise. If a parameter failed a custom validation method, then 'validate'
384             will be added to the list of failed validations for this parameter. So,
385             in our 'subject' example, the rejects hash-ref will have something like this:
386              
387             subject => ['length_between(3, 10)', 'validate']
388              
389             B For your convenience, Brannigan allows you to set default
390             values for parameters that are not required (so, if you set a default
391             value for a parameter, don't add the C validation method to
392             it). There are two ways to add a default value: either directly, or
393             through an anonymous subroutine (just like the custom validation method).
394             For example, maybe we'd like the 'subject' parameter to have a default
395             value of 'lorem ipsum dolor sit amet'. Then we can have the following definition:
396              
397             subject => {
398             length_between => [3, 10],
399             validate => sub {
400             my $value = shift;
401              
402             return $value =~ m/^lorem ipsum/ ? 1 : 0;
403             },
404             default => 'lorem ipsum dolor sit amet'
405             }
406              
407             Alternatively, you can give a parameter a generated default value by using
408             an anonymous subroutine, like so:
409              
410             subject => {
411             length_between => [3, 10],
412             validate => sub {
413             my $value = shift;
414              
415             return $value =~ m/^lorem ipsum/ ? 1 : 0;
416             },
417             default => sub {
418             return int(rand(100000000));
419             }
420             }
421              
422             Notice that default values are added to missing parameters only at the
423             parsing stage (i.e. stage 3 - after the validation stage), so validation
424             methods do not apply to default values.
425              
426             B It is more than possible that the way input parameters are passed to your
427             application will not be exactly the way you'll eventually use them. That's
428             where parsing methods can come in handy. Brannigan doesn't have any
429             built-in parsing methods (obviously), so you must create these by yourself,
430             just like custom validation methods. All you need to do is add a 'parse'
431             key to the parameter's definition, with an anonymous subroutine. This
432             subroutine also receives the value of the parameter automatically,
433             and is expected to return a hash-ref of key-value pairs. You will probably
434             find it that most of the time this hash-ref will only contain one key-value
435             pair, and that the key will probably just be the name of the parameter. But
436             note that when a parse method exists, Brannigan makes absolutely no assumptions
437             of what else to do with that parameter, so you must tell it exactly how to
438             return it. After all parameters were parsed by Brannigan, all these little hash-refs are
439             merged into one hash-ref that is returned to the caller. If a parse
440             method doesn't exist for a paramter, Brannigan will simply add it "as-is"
441             to the resulting hash-ref. Returning to our subject example (which we
442             defined must start with 'lorem ipsum'), let's say we want to substitute
443             'lorem ipsum' with 'effing awesome' before using this parameter. Then the
444             subject definition will now look like this:
445              
446             subject => {
447             length_between => [3, 10],
448             validate => sub {
449             my $value = shift;
450              
451             return $value =~ m/^lorem ipsum/ ? 1 : 0;
452             },
453             default => 'lorem ipsum dolor sit amet',
454             parse => sub {
455             my $value = shift;
456              
457             $value =~ s/^lorem ipsum/effing awesome/;
458            
459             return { subject => $value };
460             }
461             }
462              
463             If you're still not sure what happens when no parse method exists, then
464             you can imagine Brannigan uses the following default parse method:
465              
466             param => {
467             parse => sub {
468             my $value = shift;
469              
470             return { param => $value };
471             }
472             }
473              
474             B As of version 0.3, parameter names can also be regular expressions in the
475             form C<'/regex/'>. Sometimes you cannot know the names of all parameters passed
476             to your app. For example, you might have a dynamic web form which starts with
477             a single field called 'url_1', but your app allows your visitors to dynamically
478             add more fields, such as 'url_2', 'url_3', etc. Regular expressions are
479             handy in such situations. Your parameter key can be C<'/^url_(\d+)$/'>, and
480             all such fields will be matched. Regex params have a special feature: if
481             your regex uses capturing, then captured values will be passed to the
482             custom C and C methods (in their order) after the parameter's
483             value. For example:
484              
485             '/^url_(\d+)$/' => {
486             validate => sub {
487             my ($value, $num) = @_;
488            
489             # $num has the value captured by (\d+) in the regex
490              
491             return $value =~ m!^http://! ? 1 : undef;
492             },
493             parse => sub {
494             my ($value, $num) = @_;
495              
496             return { urls => { $num => $value } };
497             },
498             }
499              
500             Please note that a regex must be defined with a starting and trailing
501             slash, in single quotes, otherwise it won't work. It is also important to
502             note what happens when a parameter matches a regex rule (or perhaps rules),
503             and also has a direct reference in the scheme. For example, let's say
504             we have the following rules in our scheme:
505              
506             '/^sub(ject|headline)$/' => {
507             required => 1,
508             length_between => [3, 10],
509             },
510             subject => {
511             required => 0,
512             }
513              
514             When validating and parsing the 'subject' parameter, Brannigan will
515             automatically merge both of these references to the subject parameter,
516             giving preference to the direct reference, so the actual structure on
517             which the parameter will be validated is as follows:
518              
519             subject => {
520             required => 0,
521             length_between => [3, 10],
522             }
523              
524             If your parameter matches more than one regex rule, they will all be
525             merged, but there's no way (yet) to ensure in which order these regex
526             rules will be merged.
527              
528             B As previously stated, Brannigan can also validate and parse a little more
529             complex data structures. So, your parameter no longer has to be just a
530             string or a number, but maybe a hash-ref or an array-ref. In the first
531             case, you tell Brannigan the paramter is a hash-ref by adding a 'hash'
532             key with a true value, and a 'keys' key with a hash-ref which is just
533             like the 'params' hash-ref. For example, suppose you're receiving a 'name'
534             parameter from the user as a hash-ref containing first and last names.
535             That's how the 'name' parameter might be defined:
536              
537             name => {
538             hash => 1,
539             required => 1,
540             keys => {
541             first_name => {
542             length_between => [3, 10],
543             },
544             last_name => {
545             required => 1,
546             min_length => 3,
547             },
548             }
549             }
550              
551             What are we seeing here? We see that the 'name' parameter must be a
552             hash-ref, that it's required, and that it has two keys: first_name, whose
553             length must be between 3 to 10 if it's present, and last_name, which must
554             be 3 characters or more, and must be present.
555              
556             An array parameter, on the other hand, is a little different. Similar to hashes,
557             you define the parameter as an array-ref with the 'array' key with a true
558             value, and a 'values' key. This key has a hash-ref of validation and parse
559             methods that will be applied to EVERY value inside this array. For example,
560             suppose you're receiving a 'pictures' parameter from the user as an array-ref
561             containing URLs to pictures on the web. That's how the 'pictures' parameter
562             might be defined:
563              
564             pictures => {
565             array => 1,
566             length_between => [1, 5],
567             values => {
568             min_length => 3,
569             validate => sub {
570             my $value = shift;
571              
572             return $value =~ m!^http://! ? 1 : 0;
573             },
574             },
575             }
576              
577             What are we seeing this time? We see that the 'pictures' parameter must
578             be an array, with no less than one item (i.e. value) and no more than five
579             items (notice that we're using the same C method from
580             before, but in the context of an array, it doesn't validate against
581             character count but item count). We also see that every value in the
582             'pictures' array must have a minimum length of three (this time it is
583             characterwise), and must match 'http://' in its beginning.
584              
585             Since complex data structures are supported, you can define default values
586             for parameters that aren't just strings or numbers (or methods), for example:
587              
588             complex_param => {
589             hash => 1,
590             keys => {
591             ...
592             },
593             default => { key1 => 'def1', key2 => 'def2' }
594             }
595              
596             What Brannigan returns for such structures when they fail validations is
597             a little different than before. Instead of an array-ref of failed validations,
598             Brannigan will return a hash-ref. This hash-ref might contain a '_self' key
599             with an array-ref of validations that failed specifically on the 'pictures'
600             parameter (such as the 'required' validation for the 'name' parameter or
601             the 'length_between' validation for the 'pictures' parameter), and/or
602             keys for each value in these structures that failed validation. If it's a
603             hash, then the key will simply be the name of that key. If it's an array,
604             it will be its index. For example, let's say the 'first_name' key under
605             the 'name' parameter failed the C validation method,
606             and that the 'last_name' key was not present (and hence failed the
607             C validation). Also, let's say the 'pictures' parameter failed
608             the C validation (for the sake of the argument, let's
609             say it had 6 items instead of the maximum allowed 5), and that the 2nd
610             item failed the C validation, and the 6th item failed the
611             custom validate method. Then our rejects hash-ref will have something like
612             this:
613              
614             name => {
615             first_name => ['length_between(3, 10)'],
616             last_name => ['required(1)'],
617             },
618             pictures => {
619             _self => ['length_between(1, 5)'],
620             1 => ['min_length(3)'],
621             5 => ['validate'],
622             }
623              
624             Notice the '_self' key under 'pictures' and that the numbering of the
625             items of the 'pictures' array starts at zero (obviously).
626              
627             The beauty of Brannigan's data structure support is that it's recursive.
628             So, it's not that a parameter can be a hash-ref and that's it. Every key
629             in that hash-ref might be in itself a hash-ref, and every key in that
630             hash-ref might be an array-ref, and every value in that array-ref might
631             be a hash-ref... well, you get the idea. How might that look like? Well,
632             just take a look at this:
633              
634             pictures => {
635             array => 1,
636             values => {
637             hash => 1,
638             keys => {
639             filename => {
640             min_length => 5,
641             },
642             source => {
643             hash => 1,
644             keys => {
645             website => {
646             validate => sub { ... },
647             },
648             license => {
649             one_of => [qw/GPL FDL CC/],
650             },
651             },
652             },
653             },
654             },
655             }
656              
657             So, we have a pictures array that every value in it is a hash-ref with a
658             filename key and a source key whose value is a hash-ref with a website
659             key and a license key.
660              
661             B The _all "parameter" can be used in a scheme to define rules that apply
662             to all of the parameters in a certain level. This can either be used directly
663             in the 'params' key of the scheme, or in the 'keys' key of a hash parameter.
664              
665             _all => {
666             required => 1
667             },
668             subject => {
669             length_between => [3, 255]
670             },
671             text => {
672             min_length => 10
673             }
674              
675             In the above example, both 'subject' and 'text' receive the C
676             validation methods.
677              
678             =item * groups
679              
680             Groups are very useful to parse parameters that are somehow related
681             together. This key takes a hash-ref containing the names of the groups
682             (names are irrelevant, they're more for you). Every group will also take
683             a hash-ref, with a rule defining which parameters are members of this group,
684             and a parse method to use with these parameters (just like our custom
685             parse method from the 'params' key). This parse method will
686             automatically receive the values of all the parameters in the group, in
687             the order they were defined.
688              
689             For example, suppose our app gets a user's birth date by using three web
690             form fields: day, month and year. And suppose our app saves this date
691             in a database in the format 'YYYY-MM-DD'. Then we can define a group,
692             say 'date', that automatically does this. For example:
693              
694             date => {
695             params => [qw/year month day/],
696             parse => sub {
697             my ($year, $month, $day) = @_;
698              
699             $month = '0'.$month if $month < 10;
700             $day = '0'.$day if $day < 10;
701              
702             return { date => $year.'-'.$month.'-'.$day };
703             },
704             }
705              
706             Alternative to the 'params' key, you can define a 'regex' key that takes
707             a regex. All parameters whose name matches this regex will be parsed as
708             a group. As oppose to using regexes in the 'params' key of the scheme,
709             captured values in the regexes will not be passed to the parse method,
710             only the values of the parameters will. Also, please note that there's no
711             way to know in which order the values will be provided when using regexes
712             for groups.
713              
714             For example, let's say our app receives one or more URLs (to whatever
715             type of resource) in the input, in parameters named 'url_1', 'url_2',
716             'url_3' and so on, and that there's no limit on the number of such
717             parameters we can receive. Now, suppose we want to create an array
718             of all of these URLs, possibly to push it to a database. Then we can
719             create a 'urls' group such as this:
720              
721             urls => {
722             regex => '/^url_(\d+)$/',
723             parse => sub {
724             my @urls = @_;
725              
726             return { urls => \@urls };
727             }
728             }
729              
730             =back
731              
732             =head2 BUILT-IN VALIDATION METHODS
733              
734             As mentioned earlier, Brannigan comes with a set of built-in validation
735             methods which are most common and useful everywhere. For a list of all
736             validation methods provided by Brannigan, check L.
737              
738             =head2 CROSS-SCHEME CUSTOM VALIDATION METHODS
739              
740             Custom C methods are nice, but when you want to use the same
741             custom validation method in different places inside your scheme, or more
742             likely in different schemes altogether, repeating the definition of each
743             custom method in every place you want to use it is not very comfortable.
744             Brannigan provides a simple mechanism to create custom, named validation
745             methods that can be used across schemes as if they were internal methods.
746              
747             The process is simple: when creating your schemes, give the names of the
748             custom validation methods and their relevant supplement values as with
749             every built-in validation method. For example, suppose we want to create
750             a custom validation method named 'forbid_words', that makes sure a certain
751             text does not contain any words we don't like it to contain. Suppose this
752             will be true for a parameter named 'text'. Then we define 'text' like so:
753              
754             text => {
755             required => 1,
756             forbid_words => ['curse_word', 'bad_word', 'ugly_word'],
757             }
758              
759             As you can see, we have provided the name of our custom method, and the words
760             we want to forbid. Now we need to actually create this C
761             method. We do this after we've created our Brannigan object, by using the
762             C method, as in this example:
763              
764             $b->custom_validation('forbid_words', sub {
765             my ($value, @forbidden) = @_;
766              
767             foreach (@forbidden) {
768             return 0 if $value =~ m/$_/;
769             }
770              
771             return 1;
772             });
773              
774             We give the C method the name of our new method, and
775             an anonymous subroutine, just like in "local" custom validation methods.
776              
777             And that's it. Now we can use the C validation method
778             across our schemes. If a paremeter failed our custom method, it will be
779             added to the rejects like built-in methods. So, if 'text' failed our new
780             method, our rejects hash-ref will contain:
781              
782             text => [ 'forbid_words(curse_word, bad_word, ugly_word)' ]
783              
784             As an added bonus, you can use this mechanism to override Brannigan's
785             built-in validations. Just give the name of the validation method you wish
786             to override, along with the new code for this method. Brannigan gives
787             precedence to cross-scheme custom validations, so your method will be used
788             instead of the internal one.
789              
790             =head2 NOTES ABOUT PARSE METHODS
791              
792             As stated earlier, your C methods are expected to return a hash-ref
793             of key-value pairs. Brannigan collects all of these key-value pairs
794             and merges them into one big hash-ref (along with all the non-parsed
795             parameters).
796              
797             Brannigan actually allows you to have your C methods be two-leveled.
798             This means that a value in a key-value pair in itself can be a hash-ref
799             or an array-ref. This allows you to use the same key in different places,
800             and Brannigan will automatically aggregate all of these occurrences, just like
801             in the first level. So, for example, suppose your scheme has a regex
802             rule that matches parameters like 'tag_en' and 'tag_he'. Your parse
803             method might return something like C<< { tags => { en => 'an english tag' } } >>
804             when it matches the 'tag_en' parameter, and something like
805             C<< { tags => { he => 'a hebrew tag' } } >> when it matches the 'tag_he'
806             parameter. The resulting hash-ref from the process method will thus
807             include C<< { tags => { en => 'an english tag', he => 'a hebrew tag' } } >>.
808              
809             Similarly, let's say your scheme has a regex rule that matches parameters
810             like 'url_1', 'url_2', etc. Your parse method might return something like
811             C<< { urls => [$url_1] } >> for 'url_1' and C<< { urls => [$url_2] } >>
812             for 'url_2'. The resulting hash-ref in this case will be
813             C<< { urls => [$url_1, $url_2] } >>.
814              
815             Take note however that only two-levels are supported, so don't go crazy
816             with this.
817              
818             =head2 SO HOW DO I PROCESS INPUT?
819              
820             OK, so we have created our scheme(s), we know how schemes look and work,
821             but what now?
822              
823             Well, that's the easy part. All you need to do is call the C
824             method on the Brannigan object, passing it the name of the scheme to
825             enforce and a hash-ref of the input parameters/data structure. This method
826             will return a hash-ref back, with all the parameters after parsing. If any
827             validations failed, this hash-ref will have a '_rejects' key, with the
828             rejects hash-ref described earlier. Remember: Brannigan doesn't raise
829             any errors. It's your job to decide what to do, and that's a good thing.
830              
831             Example schemes, input and output can be seen in L.
832              
833             =head1 CONSTRUCTOR
834              
835             =head2 new( \%scheme | @schemes )
836              
837             Creates a new instance of Brannigan, with the provided scheme(s) (see
838             L for more info on schemes).
839              
840             =cut
841              
842             sub new {
843 2     2 1 137760 my $class = shift;
844              
845 2         6 return bless { map { $_->{name} => $_ } @_ }, $class;
  6         16  
846             }
847              
848             =head1 OBJECT METHODS
849              
850             =head2 add_scheme( \%scheme | @schemes )
851              
852             Adds one or more schemes to the object. Every scheme hash-ref should have
853             a C key with the name of the scheme. Existing schemes will be overridden.
854             Returns the object itself for chainability.
855              
856             =cut
857              
858             sub add_scheme {
859 1     1 1 381 my $self = shift;
860              
861 1         3 foreach (@_) {
862 1         3 $self->{$_->{name}} = $_;
863             }
864              
865 1         2 return $self;
866             }
867              
868             =head2 process( $scheme, \%params )
869              
870             Receives the name of a scheme and a hash-ref of input parameters (or a data
871             structure), and validates and parses these paremeters according to the
872             scheme (see L for detailed information about this process).
873              
874             Returns a hash-ref of parsed parameters according to the parsing scheme,
875             possibly containing a list of failed validations for each parameter.
876              
877             Actual processing is done by L.
878              
879             =head2 process( \%scheme, \%params )
880              
881             Same as above, but takes a scheme hash-ref instead of a name hash-ref. That
882             basically gives you a functional interface for Brannigan, so you don't have
883             to go through the regular object oriented interface. The only downsides to this
884             are that you cannot define custom validations using the C
885             method (defined below) and that your scheme must be standalone (it cannot inherit
886             from other schemes). Note that when directly passing a scheme, you don't need
887             to give the scheme a name.
888              
889             =cut
890              
891             sub process {
892 9 100   9 1 11946 if (ref $_[0] eq 'Brannigan') {
893 8         14 my ($self, $scheme, $params) = @_;
894              
895 8 50 33     76 return unless $scheme && $params && ref $params eq 'HASH' && $self->{$scheme};
      33        
      33        
896 8         26 $self->_build_tree($scheme, $self->{validations})->process($params);
897             } else {
898 1         4 Brannigan::Tree->new($_[0])->process($_[1]);
899             }
900             }
901              
902             =head2 custom_validation( $name, $code )
903              
904             Receives the name of a custom validation method (C<$name>), and a reference to an
905             anonymous subroutine (C<$code>), and creates a new validation method with
906             that name and code, to be used across schemes in the Brannigan object as
907             if they were internal methods. You can even use this to override internal
908             validation methods, just give the name of the method you want to override
909             and the new code.
910              
911             =cut
912              
913             sub custom_validation {
914 2     2 1 661 my ($self, $name, $code) = @_;
915              
916 2 50 33     23 return unless $name && $code && ref $code eq 'CODE';
      33        
917              
918 2         4 $self->{validations}->{$name} = $code;
919             }
920              
921             ############################
922             ##### INTERNAL METHODS #####
923             ############################
924              
925             # _build_tree( $scheme, [ \%custom_validations ] )
926             # ------------------------------------------------
927             # Builds the final "tree" of validations and parsing methods to be performed
928             # on the parameters hash during processing. Optionally receives a hash-ref
929             # of cross-scheme custom validation methods defined in the Brannigan object
930             # (see L for more info).
931              
932             sub _build_tree {
933 13     13   18 my ($self, $scheme, $customs) = @_;
934              
935 13         13 my @trees;
936              
937             # get a list of all schemes to inherit from
938 13 100 66     62 my @schemes = $self->{$scheme}->{inherits_from} && ref $self->{$scheme}->{inherits_from} eq 'ARRAY' ? @{$self->{$scheme}->{inherits_from}} : $self->{$scheme}->{inherits_from} ? ($self->{$scheme}->{inherits_from}) : ();
  0 50       0  
939              
940 13         26 foreach (@schemes) {
941 5 50       13 next unless $self->{$_};
942 5         20 push(@trees, $self->_build_tree($_));
943             }
944              
945 13         47 my $tree = Brannigan::Tree->new(@trees, $self->{$scheme});
946 13         20 $tree->{_custom_validations} = $customs;
947              
948 13         53 return $tree;
949             }
950              
951             =head1 CAVEATS
952              
953             Brannigan is still in an early stage. Currently, no checks are made to
954             validate the schemes built, so if you incorrectly define your schemes,
955             Brannigan will not croak and processing will probably fail. Also, there
956             is no support yet for recursive inheritance or any crazy inheritance
957             situation. While deep inheritance is supported, it hasn't been tested
958             extensively. Also bugs are popping up as I go along, so keep in mind that
959             you might encounter bugs (and please report any if that happens).
960              
961             =head1 IDEAS FOR THE FUTURE
962              
963             The following list of ideas may or may not be implemented in future
964             versions of Brannigan:
965              
966             =over
967              
968             =item * Cross-scheme custom parsing methods
969              
970             Add an option to define custom parse methods in the Brannigan object that
971             can be used in the schemes as if they were built-in methods (cross-scheme
972             custom validations are already supported, next up is parse methods).
973              
974             =item * Support for third-party validation methods
975              
976             Add support for loading validation methods defined in third-party modules
977             (written like L) and using them in schemes as if they
978             were built-in methods.
979              
980             =item * Validate schemes by yourself
981              
982             Have Brannigan use itself to validate the schemes it receives from the
983             developers (i.e. users of this module).
984              
985             =item * Support loading schemes from JSON/XML
986              
987             Allow loading schemes from JSON/XML files or any other source. Does that
988             make any sense?
989              
990             =item * Something to aid rejects traversal
991              
992             Find something that would make traversal of the rejects list easier or
993             whatever. Plus, printing the name of the validation method and its supplement
994             values in the rejects list isn't always a good idea. For example, if we
995             use the C validation method with a big list of say 100 options,
996             our rejects list will contain all these 100 options, and that's not nice.
997             So, think about something there.
998              
999             =back
1000              
1001             =head1 SEE ALSO
1002              
1003             L, L, L.
1004              
1005             =head1 AUTHOR
1006              
1007             Ido Perlmuter, C<< >>
1008              
1009             =head1 BUGS
1010              
1011             Please report any bugs or feature requests to C, or through
1012             the web interface at L. I will be notified, and then you'll
1013             automatically be notified of progress on your bug as I make changes.
1014              
1015             =head1 SUPPORT
1016              
1017             You can find documentation for this module with the perldoc command.
1018              
1019             perldoc Brannigan
1020              
1021             You can also look for information at:
1022              
1023             =over 4
1024              
1025             =item * RT: CPAN's request tracker
1026              
1027             L
1028              
1029             =item * AnnoCPAN: Annotated CPAN documentation
1030              
1031             L
1032              
1033             =item * CPAN Ratings
1034              
1035             L
1036              
1037             =item * Search CPAN
1038              
1039             L
1040              
1041             =back
1042              
1043             =head1 ACKNOWLEDGEMENTS
1044              
1045             Brannigan was inspired by L (Al Newkirk) and the "Ketchup" jQuery
1046             validation plugin (L).
1047              
1048             =head1 LICENSE AND COPYRIGHT
1049              
1050             Copyright 2017 Ido Perlmuter
1051              
1052             Licensed under the Apache License, Version 2.0 (the "License");
1053             you may not use this file except in compliance with the License.
1054             You may obtain a copy of the License at
1055              
1056             http://www.apache.org/licenses/LICENSE-2.0
1057              
1058             Unless required by applicable law or agreed to in writing, software
1059             distributed under the License is distributed on an "AS IS" BASIS,
1060             WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1061             See the License for the specific language governing permissions and
1062             limitations under the License.
1063              
1064             =cut
1065              
1066             1;
1067             __END__