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