File Coverage

blib/lib/Brick/Profile.pm
Criterion Covered Total %
statement 78 80 97.5
branch 13 20 65.0
condition 8 18 44.4
subroutine 15 17 88.2
pod 10 10 100.0
total 124 145 85.5


line stmt bran cond sub pod time code
1             package Brick::Profile;
2 6     6   128530 use strict;
  6         13  
  6         275  
3 6     6   31 use warnings;
  6         16  
  6         370  
4              
5 6     6   37 use vars qw($VERSION);
  6         13  
  6         318  
6              
7 6     6   29 use Carp qw(carp);
  6         12  
  6         363  
8              
9 6     6   631 use Brick;
  6         11  
  6         6745  
10              
11             $VERSION = '0.904';
12              
13             =encoding utf8
14              
15             =head1 NAME
16              
17             Brick::Profile - the validation profile for Brick
18              
19             =head1 SYNOPSIS
20              
21              
22              
23             =head1 DESCRIPTION
24              
25             This class turns a profile description into a ready-to-use profile object
26             that has created all of the code it needs to validate input. In Brick
27             parlance, it creates the bucket and the bricks that need to go into the
28             bucket based on the validation description.
29              
30             =head2 Validation profile
31              
32             The validation profile is an array of arrays. Each item in the array specifies
33             three things: a label for that item, the name of the method to run to
34             validate the item, and optional arguments to pass to the the method.
35              
36             For instance, here's a simple validation description to check if a user
37             is registered with the system. This profile has one item:
38              
39             @profile = (
40             [ username => is_registered => { field => form_name } ],
41             );
42              
43             The label for the item is C. When Brick reports the results
44             of the validation, the label C will be attached to the
45             result for this part of the validation.
46              
47             The method that validates this item is C. When you
48             create the profile, Brick will look for that method in either it's
49             included methods in Brick::Bucket classes or the ones you load with
50             C. This method is called a "brick"
51             because it's one piece of the entire validation.
52              
53             Additionally, Brick will pass the optional arguments, in this case C<{
54             field => form_name }>, to C. A brick merely creates
55             a closure that will run later, so the optional arguments are for
56             the initialization of that closure. The validation doesn't happen
57             until you C it.
58              
59             =head2 Class methods
60              
61             =over 4
62              
63             =item new( BRICK, ARRAY_OF_ARRAYS )
64              
65             Create a new profile object tied to the Brick object.
66              
67             =cut
68              
69             sub new {
70 4     4 1 14 my( $class, $brick, $array_ref ) = @_;
71              
72 4 50       17 unless( $brick->isa( $class->brick_class ) ) {
73 0         0 carp "First argument to \$class->new() must be a brick object. " .
74             "Got [$brick]\n";
75 0         0 return;
76             }
77              
78 4         14 my $self = bless {}, $class;
79              
80 4         15 my $lint_errors = $class->lint( $array_ref );
81              
82 4 100 66     25 if( ! defined $lint_errors or $lint_errors ) {
83 1         342 carp "Profile did not validate!";
84 1         14 return;
85             }
86              
87 3         19 my( $bucket, $refs ) = $brick->create_bucket( $array_ref );
88              
89 3         15 $self->set_bucket( $bucket );
90 3         12 $self->set_coderefs( $refs );
91 3         11 $self->set_array( $array_ref );
92              
93 3         12 return $self;
94             }
95              
96             =item brick_class()
97              
98             Return the class name to use to access class methods (such as
99             bucket_class) in the Brick namespace. If you want to provide
100             an alternate Brick class for your profile, override this method.
101              
102             =cut
103              
104 14     14 1 57 sub brick_class { require Brick; 'Brick' }
  14         64  
105              
106             =back
107              
108             =head2 Instance methods
109              
110             =over
111              
112             =item lint( PROFILE_ARRAYREF );
113              
114             Examine the profile and complain about irregularities in format. This
115             only checks the format; it does not try to determine if the profile
116             works or makes sense. It returns a hash whose key is the index of the
117             profile element and whose value is an anonymous hash to indicate what
118             had the error:
119              
120             format - the element is an arrayref
121             name - the name is a scalar
122             method - is a code ref or can be found in the package
123             $brick->bucket_class returns
124             args - the last element is a hash reference
125              
126             If the profile is not an array reference, C immediately returns
127             undef or the empty list. In scalar context, C returns 0 for
128             format success and the number of errors (so true) for format failures.
129             If there is a format error (e.g. an element is not an array ref), it
130             immediately returns the number of errors up to that point.
131              
132             my $lint = $brick->profile_class->lint( \@profile );
133              
134             print do {
135             if( not defined $lint ) { "Profile must be an array ref\n" }
136             elsif( $lint ) { "Did not validate, had $lint problems" }
137             else { "Woo hoo! Everything's good!" }
138             };
139              
140             In list context, it returns a hash (a list of one element). The result
141             will look something like this hash, which has keys for the elements
142             that lint thinks are bad, and the values are anonymous hashes with
143             keys for the parts that failed:
144              
145             %lint = (
146             1 => {
147             method => "Could not find method foo in package",
148             },
149             4 => {
150             args => "Arguments should be a hash ref, but it was a scalar",
151             }
152             );
153              
154             If you are using C to generate some of the methods at
155             runtime (i.e. after C has a chance to check for it), use a
156             C method to let C know that it will be available later.
157              
158             TO DO:
159              
160             Errors for duplicate names?
161              
162             =cut
163              
164             sub lint {
165 12     12 1 29 my( $class, $array ) = @_;
166              
167             return unless(
168 12 50 33     23 eval { $array->isa( ref [] ) } or
  12         174  
169             UNIVERSAL::isa( $array, ref [] )
170             );
171              
172 12         31 my $lint = {};
173              
174 12         41 foreach my $index ( 0 .. $#$array ) {
175 12         40 my $h = $lint->{$index} = {};
176              
177 12 100 66     24 unless( eval { $array->[$index]->isa( ref [] ) } or
  12         121  
178             UNIVERSAL::isa( $array->[$index], ref [] )
179             ) {
180 2         5 $h->{format} = "Not an array reference!";
181 2         6 last;
182             }
183              
184 10         22 my( $name, $method, $args ) = @{ $array->[$index] };
  10         27  
185              
186 10 50       24 $h->{name} = "Profile name is not a simple scalar!" if ref $name;
187              
188             $h->{args} = "Couldn't find method [$method]" unless
189 10     0   202 eval { $method->isa( ref sub {} ) } or
190       0     UNIVERSAL::isa( $method, sub {} ) or
191 10 50 33     18 eval { $class->brick_class->bucket_class->can( $method ) };
  10   33     33  
192              
193             $h->{args} = "Args is not a hash reference" unless
194 10 50 33     55 eval { $args->isa( ref {} ) } or
  10         106  
195             UNIVERSAL::isa( $args, ref {} );
196              
197             # args needs what?
198              
199 10 50       24 delete $lint->{$index} if 0 == keys %{$lint->{$index}};
  10         56  
200             }
201              
202 12 100       63 wantarray ? %$lint : ( scalar keys %$lint );
203             }
204              
205             =item explain()
206              
207             Turn the profile into a textual description without applying it to any
208             data. This does not add the profile to instance and it does not add
209             the constraints to the bucket.
210              
211             If everything goes right, this returns a single string that represents
212             the profile.
213              
214             If the profile does not pass the C test, this returns undef or the
215             empty list.
216              
217             If you want to do something with a datastructure, you probably want to
218             write a different method very similar to this instead of trying to parse
219             the output.
220              
221             Future notes: maybe this is just really a dispatcher to things that do
222             it in different ways (text output, hash output).
223              
224             =cut
225              
226             sub explain {
227 2     2 1 7 my( $profile ) = @_;
228              
229 2         8 my $bucket = $profile->get_bucket;
230 2         7 my $coderefs = $profile->get_coderefs;
231 2         6 my $array = $profile->get_array;
232              
233             my @entries = map {
234 2         5 my $e = $bucket->get_from_bucket( $_ );
  2         9  
235 2         5 [ map { $e->$_ } qw(get_coderef get_name) ]
  4         16  
236             } @$coderefs;
237              
238             #print STDERR Data::Dumper->Dump( [ \@entries ], [qw(entries)] );
239              
240 2         4 my $level = 0;
241 2         5 my $str = '';
242 2         9 foreach my $index ( 0 .. $#entries ) {
243 2         5 my $tuple = $entries[$index];
244              
245 2         6 my @uses = ( [ $level, $tuple->[0] ] );
246              
247             #print STDERR Data::Dumper->Dump( [ \@uses ], [qw(uses)] );
248              
249 2         7 while( my $pair = shift @uses ) {
250 14         32 my $entry = $bucket->get_from_bucket( $pair->[1] );
251             #print Data::Dumper->Dump( [ $entry ], [qw(entry)] );
252 14 50       47 next unless $entry;
253              
254 14         37 $str .= "\t" x $pair->[0] . $entry->get_name . "\n";
255              
256             unshift @uses, map {
257 12         41 [ $pair->[0] + 1, $_ ]
258 14         24 } @{ $entry->get_comprises( $pair->[1] ) };
  14         31  
259             #print Data::Dumper->Dump( [ \@uses ], [qw(uses)] );
260             }
261              
262 2         7 $str.= "\n";
263             }
264              
265 2         10 $str;
266             }
267              
268             =item get_bucket
269              
270             =cut
271              
272             sub get_bucket {
273             $_[0]->{bucket}
274 3     3 1 9 }
275              
276             =item set_bucket
277              
278             =cut
279              
280             sub set_bucket {
281 3     3 1 16 $_[0]->{bucket} = $_[1];
282             }
283              
284             =item get_coderefs
285              
286             =cut
287              
288             sub get_coderefs {
289 3     3 1 7 $_[0]->{coderefs};
290             }
291              
292             =item set_coderefs
293              
294             =cut
295              
296             sub set_coderefs {
297 3     3 1 10 $_[0]->{coderefs} = $_[1];
298             }
299              
300             =item get_array
301              
302             =cut
303              
304             sub get_array {
305 3     3 1 9 $_[0]->{array};
306             }
307              
308             =item set_array
309              
310             =cut
311              
312             sub set_array {
313 3     3 1 8 $_[0]->{array} = $_[1];
314             }
315              
316             =back
317              
318             =head2 Using a different class
319              
320             If you don't want to use this class, you can specify a different class
321             to use in your Brick subclass. Override the Brick::profile_class()
322             method to specify the name of the class that you want to use instead.
323             That might be a subclass or an unrelated class. Your class will need
324             to use the same interface even though it does things differently.
325              
326             =head1 TO DO
327              
328             TBA
329              
330             =head1 SEE ALSO
331              
332             L, L
333              
334             =head1 SOURCE AVAILABILITY
335              
336             This source is in Github:
337              
338             https://github.com/briandfoy/brick
339              
340             =head1 AUTHOR
341              
342             brian d foy, C<< >>
343              
344             =head1 COPYRIGHT
345              
346             Copyright © 2007-2025, brian d foy . All rights reserved.
347              
348             You may redistribute this under the terms of the Artistic License 2.0.
349              
350             =cut
351              
352             1;