File Coverage

blib/lib/Brick.pm
Criterion Covered Total %
statement 72 101 71.2
branch 9 28 32.1
condition 7 17 41.1
subroutine 18 22 81.8
pod 12 13 92.3
total 118 181 65.1


line stmt bran cond sub pod time code
1             package Brick;
2 6     6   1462867 use strict;
  6         13  
  6         279  
3              
4 6     6   2677 use subs qw();
  6         1817  
  6         219  
5 6     6   47 use vars qw($VERSION);
  6         9  
  6         310  
6              
7 6     6   30 use Carp qw( carp croak );
  6         22  
  6         435  
8 6     6   3822 use Data::Dumper;
  6         53274  
  6         513  
9              
10 6     6   2655 use Brick::Profile;
  6         20  
  6         8820  
11              
12             $VERSION = '0.905';
13              
14             =encoding utf8
15              
16             =head1 NAME
17              
18             Brick - Complex business rule data validation
19              
20             =head1 SYNOPSIS
21              
22             use Brick;
23              
24             my $brick = Brick->new( {
25             external_packages => [ qw(Foo::Validator Bar::Validator) ]
26             } );
27              
28             my $profile = Brick::Profile->new( $brick,
29             [ required => sub { .... } => $hash ],
30             [ optional => optional_fields => $hash ],
31              
32             [ inside => in_number => $hash ],
33              
34             [ outside => ex_number => $hash ],
35             );
36              
37             my %input_from_app = (
38             name => 'Joe Snuffy',
39             ...
40             );
41              
42             my $results = $brick->apply( $profile, \%%input_from_app );
43              
44             =head1 DESCRIPTION
45              
46              
47             =head2 Class methods
48              
49             =over 4
50              
51             =item Brick->new
52              
53             Create a new C. Currently this doesn't do anything other than
54             give you an object so you can call methods.
55              
56             Future ideas? Maybe store several buckets or profiles?
57              
58             =cut
59              
60             sub new {
61 3     3 1 200676 my( $class, $args ) = @_;
62              
63 3         8 my $self = bless {}, $class;
64              
65 3         17 $self->init( $args );
66              
67 3         6 $self->_load_external_packages( @{ $args->{external_packages} } );
  3         20  
68              
69 3         16 $self;
70             }
71              
72             sub _load_external_packages {
73 3     3   12 my( $self, @packages ) = @_;
74              
75 3         12 my $bucket_class = $self->bucket_class;
76              
77 3         12 foreach my $package ( @packages ) {
78 0         0 eval "package $bucket_class; require $package; $package->import";
79 0 0       0 croak "Could not load $package: $@" if $@;
80             }
81              
82             }
83              
84             =item Brick->error( MESSAGE )
85              
86             Set the error message from the last things that happened.
87              
88             =item Brick->error_str
89              
90             Get the error message from the last things that happened.
91              
92             =cut
93              
94             {
95             my $Error;
96              
97 1     1 1 715 sub error { $_[0]->_set_error( $_[1] ); croak $_[1]; }
  1         179  
98 1     1 1 881 sub error_str { $Error }
99              
100             # do some stuff to figure out caller, etc
101 1     1   3 sub _set_error { $Error = $_[1] }
102             }
103              
104             =back
105              
106             =head2 Instance methods
107              
108             =over 4
109              
110             =item create_bucket( PROFILE_ARRAYREF )
111              
112             =item create_pool # DEPRECATED
113              
114             This method creates a C instance (or an instance in
115             the package returned by C<$brick->bucket_class> ) based on the profile
116             and returns the bucket instance. Along the way it affects the args
117             hashref in each profile element to add the element name as the key
118             C and the actual coderef (not just the method name) as
119             the key C. The closure generators are allowed to use those keys.
120             For instance, C<__make_constraint>, which is usually the top level
121             closure, uses it to name the closure in the bucket.
122              
123             If the profile doesn't pass C test, this method croaks. You
124             might want to safeguard that by calling C first.
125              
126             my $bucket = do {
127             if( my( $lint ) = $brick->lint( $profile ) )
128             {
129             $brick->create_bucket( $profile );
130             }
131             else
132             {
133             Data::Dumper->Dump( [ $lint ], [qw(lint)] );
134             undef;
135             }
136             };
137              
138             From the profile it extracts the method name to create the closure for
139             it based on its arguments. If the method item is already a code
140             reference it uses it add is, but still adds it to the bucket. This could
141             be handy for using closures from other classes, but I haven't
142             investigated the consequences of that.
143              
144             In scalar context this returns a new bucket instance. If the profile might
145             be bad, use an eval to catch the croak:
146              
147             my $bucket = eval{ $brick->create_bucket( \@profile ) };
148              
149             In list context, it returns the C<$bucket> instance and an anonymous array
150             reference with the stringified closures (which are also the keys in the
151             bucket). The elements in the anonymous array correspond to the elements in
152             the profile. This is handy in C which needs to find the bucket
153             entries for each profile elements. You probably won't need the second
154             argument most of the time.
155              
156             my( $bucket, $refs ) = eval { $brick->create_bucket( \@profile ) };
157              
158             =cut
159              
160 1     1 1 655 sub create_pool { croak "create_pool is now create_bucket!" }
161              
162             sub create_bucket {
163 3     3 1 7 my( $brick, $profile ) = @_;
164              
165 3 50 50     9 unless( 0 == $brick->profile_class->lint( $profile || [] ) ) { # zero but true!
166 0         0 croak "Bad profile for create_bucket! Perhaps you need to check it with lint"
167             };
168              
169 3         9 my $bucket = $brick->bucket_class->new;
170              
171 3         7 my @coderefs = ();
172 3         10 foreach my $entry ( @$profile ) {
173 2         4 my( $name, $method, $args ) = @$entry;
174              
175 2         31 $args->{profile_name} = $name;
176              
177 2         3 $args->{code} = do {
178 2 50 33     2 if( eval { $method->isa( ref {} ) } or
  2 50       19  
    0          
179       0     ref $method eq ref sub {} ) {
180 0         0 $method;
181             }
182 2         7 elsif( my $code = eval{ $bucket->$method( $args ) } ) {
183 2         4 $code;
184             }
185 0         0 elsif( $@ ) { croak $@ }
186             };
187              
188 2         6 push @coderefs, map { "$_" } $bucket->add_to_bucket( $args );
  2         7  
189             }
190              
191 3 50       17 wantarray ? ( $bucket, \@coderefs ) : $bucket;
192             }
193              
194             =item init
195              
196             Initialize the instance, or return it to a pristine state. Normally
197             you don't have to do this because C does it for you, but if you
198             subclass this you might want to override it.
199              
200             =cut
201              
202             sub init {
203 7     7 1 10108 my( $self, $args ) = @_;
204              
205 7         29 my $bucket_class = $self->bucket_class;
206              
207 7         551 eval "require $bucket_class";
208              
209 7         55 $self->{buckets} = [];
210              
211 7 100 100     75 if( defined $args->{external_packages} && ref $args->{external_packages} eq ref [] ) { # defined and array ref
    100 66        
212 1         5 $self->{external_packages} = $args->{external_packages};
213             }
214             elsif( defined $args->{external_packages} &&
215             ! ($args->{external_packages} eq ref []) ) { # defined but not array ref
216 1         270 carp "'external_packages' value must be an anonymous array";
217 1         14 $self->{external_packages} = [];
218             }
219             else { # not defined
220 5         23 $self->{external_packages} = [];
221             }
222             }
223              
224             =item add_validator_packages( PACKAGES )
225              
226             Load external validator packages into the bucket. Each of these packages
227             should export the functions they want to make available. C
228             Cs each package and calls its C routine.
229              
230             =cut
231              
232             sub add_validator_packages
233             {
234 0     0 1 0 my( $self, @packages ) = @_;
235              
236 0         0 $self->_load_external_packages( @packages );
237             }
238              
239             =item clone;
240              
241             Based on the current instance, create another one just like it but not
242             connected to it (in effect forking the instance). After the C
243             you can change new instance without affecting the old one. This is
244             handy in C, for instance, where I want a deep copy for a
245             moment. At least I think I want a deep copy.
246              
247             That's the idea. Right now this just returns the same instance. When
248             not using a copy breaks, I'll fix that.
249              
250             =cut
251              
252             sub clone {
253 0     0 1 0 my( $brick ) = shift;
254              
255 0         0 $brick;
256             }
257              
258             sub explain {
259 0     0 0 0 croak "Who's calling Brick::explain? That's in Brick::Profile now!";
260             }
261              
262             =item apply( PROFILE OBJECT, INPUT_DATA_HASHREF )
263              
264             Apply the profile to the data in the input hash reference. The profile
265             can either be a profile object or an array ref that apply() will use to
266             create the profile object.
267              
268             This returns a results object blessed into the class name returned by
269             results_class(), which is Brick::Result by default. If you don't like
270             that, you can override it in your own subclass.
271              
272             =cut
273              
274             sub apply {
275 1     1 1 5 my( $brick, $profile, $input ) = @_;
276              
277             croak "Did not get a profile object in Brick::apply()!\n"
278 1 50       2 unless eval { $profile->isa( $brick->profile_class ) };
  1         4  
279              
280 1         5 my $bucket = $profile->get_bucket;
281 1         3 my $coderefs = $profile->get_coderefs;
282 1         4 my $array = $profile->get_array;
283              
284             my @entries = map {
285 1         3 my $e = $bucket->get_from_bucket( $_ );
  0         0  
286 0         0 [ map { $e->$_ } qw(get_coderef get_name) ]
  0         0  
287             } @$coderefs;
288              
289 1         3 my @results = ();
290              
291 1         4 foreach my $index ( 0 .. $#entries ) {
292 0         0 my $e = $entries[$index];
293 0         0 my $name = $array->[$index][0];
294              
295 0         0 my $bucket_entry = $bucket->get_from_bucket( "$e->[0]" );
296 0         0 my $sub_name = $bucket_entry->get_name;
297              
298 0         0 my $result = eval{ $e->[0]->( $input ) };
  0         0  
299 0         0 my $eval_error = $@;
300              
301 0 0 0     0 carp "Brick: $sub_name: eval error \$\@ is not a string or hash reference"
302             unless( ! ref $eval_error or ref $eval_error eq ref {} );
303              
304 0 0 0     0 if( defined $eval_error and ref $eval_error eq ref {} ) {
    0          
305 0         0 $result = 0;
306             carp "Brick: $sub_name died with reference, but didn't define 'handler' key"
307 0 0       0 unless exists $eval_error->{handler};
308              
309             carp "Brick: $sub_name died with reference, but didn't define 'message' key"
310 0 0       0 unless exists $eval_error->{message};
311             }
312             elsif( defined $eval_error ) { # but not a reference
313 0         0 $eval_error = {
314             handler => 'program_error',
315             message => $eval_error,
316             program_error => 1,
317             errors => [],
318             };
319             }
320              
321 0         0 my $handler = $array->[$index][1];
322              
323 0         0 my $result_item = $brick->result_class->result_item_class->new(
324             label => $name,
325             method => $handler,
326             result => $result,
327             messages => $eval_error,
328             );
329              
330 0         0 push @results, $result_item;
331             }
332              
333 1         5 return bless \@results, $brick->result_class;
334             }
335              
336             =item bucket_class
337              
338             The namespace where the constraint building blocks are defined. By
339             default this is C. If you don't like that, override
340             this in a subclass. Things that need to work with the bucket class
341             name, such as a factory method, will use the return value of this
342             method.
343              
344             This method also loads the right class, so if you override it,
345             remember to load the class too!
346              
347             =cut
348              
349 25     25 1 2762 sub bucket_class { require Brick::Bucket; 'Brick::Bucket' }
  25         133  
350              
351             =item result_class
352              
353             The namespace that C uses for its result object. By default
354             this is C. If you don't like that, override this in a
355             subclass. Things that need to work with the result class name, such as
356             a factory method, will use the return value of this method.
357              
358             This method also loads the right class, so if you override it,
359             remember to load the class too!
360              
361             =cut
362              
363 2     2 1 698 sub result_class { require Brick::Result; 'Brick::Result' }
  2         15  
364              
365             =item profile_class
366              
367             The namespace for the profile object. By default this is
368             C. If you don't like that, override this in a
369             subclass. Things that need to work with the result class name, such as
370             a factory method, will use the return value of this method.
371              
372             This method also loads the right class, so if you override it,
373             remember to load the class too!
374              
375             =cut
376              
377 16     16 1 6423 sub profile_class { require Brick::Profile; 'Brick::Profile' }
  16         95  
378              
379             =back
380              
381             =head1 TO DO
382              
383             TBA
384              
385             =head1 SEE ALSO
386              
387             L, L
388              
389             =head1 SOURCE AVAILABILITY
390              
391             This source is in Github:
392              
393             https://github.com/briandfoy/brick
394              
395             =head1 AUTHOR
396              
397             brian d foy, C<< >>
398              
399             =head1 COPYRIGHT
400              
401             Copyright © 2007-2026, brian d foy . All rights reserved.
402              
403             You may redistribute this under the terms of the Artistic License 2.0.
404              
405             =cut
406              
407             1;