File Coverage

lib/LIVR/Contract.pm
Criterion Covered Total %
statement 72 75 96.0
branch 11 22 50.0
condition 8 12 66.6
subroutine 20 20 100.0
pod 0 3 0.0
total 111 132 84.0


line stmt bran cond sub pod time code
1             package LIVR::Contract;
2              
3 1     1   64858 use strict;
  1         2  
  1         44  
4 1     1   5 use warnings;
  1         1  
  1         95  
5              
6 1     1   5 use Exporter 'import';
  1         6  
  1         30  
7 1     1   5 use Carp qw/croak/;
  1         2  
  1         47  
8              
9 1     1   1287 use Validator::LIVR;
  1         186999  
  1         35  
10 1     1   1151 use Class::Method::Modifiers qw/install_modifier/;
  1         1728  
  1         75  
11 1     1   533 use LIVR::Contract::Exception;
  1         5  
  1         38  
12 1     1   8 use Scalar::Util qw/blessed/;
  1         12  
  1         1175  
13              
14             our @EXPORT = ( 'contract' );
15             our @CARP_NOT = (__PACKAGE__);
16              
17             our $VERSION = '0.04';
18              
19             sub contract {
20 2     2 0 603 my ( $subname, %args ) = @_;
21 2 50       9 croak "Subname is required" unless $subname;
22              
23 2         10 return __PACKAGE__->new(
24             'subname' => $subname,
25             'package' => scalar( caller ),
26             'ensures' => $args{ensures},
27             'requires' => $args{requires},
28             )->enable();
29             }
30              
31             sub new {
32 2     2 0 7 my ( $class, %args ) = @_;
33              
34 2 50       7 croak '"subname" is required' unless $args{subname};
35 2 50       6 croak '"package" is required' unless $args{package};
36              
37 2         21 my $self = bless {
38             subname => $args{subname},
39             package => $args{package},
40             ensures => $args{ensures},
41             requires => $args{requires},
42             input_preparator => $args{input_preparator},
43             output_preparator => $args{output_preparator},
44             on_fail => $args{on_fail},
45             input_validator => undef,
46             output_validator => undef,
47             }, $class;
48              
49 2         9 return $self;
50             }
51              
52             sub enable {
53 2     2 0 3 my $self = shift;
54              
55             install_modifier(
56             $self->{package},
57             'around',
58             $self->{subname},
59             sub {
60 3     3   4978 my $orig = shift;
61              
62 3         14 my $input = $self->_get_input_preparator->( @_ );
63 3         14 $self->_validate_input( $input );
64              
65 1         77 my $is_wantarray = wantarray;
66 1 50       8 my $output = $is_wantarray ? [ $orig->( @_ ) ] : $orig->( @_ );
67              
68 1 50       16 my $prepared_output = $self->_get_output_preparator->( $is_wantarray ? @$output : $output );
69 1         5 $self->_validate_output( $prepared_output );
70              
71 0 0       0 return $is_wantarray ? @$output : $output;
72             }
73 2         26 );
74             }
75              
76             sub _validate_input {
77 3     3   6 my ( $self, $input ) = @_;
78 3 50       11 return unless $self->{requires};
79              
80 3   66     32 my $validator = $self->{input_validator} ||= Validator::LIVR->new( $self->{requires} )->prepare();
81              
82 3 100       579 if ( !$validator->validate( $input ) ) {
83 2         158 $self->_get_on_fail->( 'input', $self->{package}, $self->{subname}, $validator->get_errors() );
84             }
85             }
86              
87             sub _validate_output {
88 1     1   2 my ( $self, $output ) = @_;
89 1 50       5 return unless $self->{ensures};
90              
91 1   33     10 my $validator = $self->{output_validator} ||= Validator::LIVR->new( $self->{ensures} )->prepare();
92              
93 1 50       202 if ( !$validator->validate( $output ) ) {
94 1         53 $self->_get_on_fail->( 'output', $self->{package}, $self->{subname}, $validator->get_errors() );
95             }
96             }
97              
98             sub _get_input_preparator {
99 3     3   7 my $self = shift;
100              
101             return $self->{input_preparator} ||= sub {
102 3     3   4 my %numbered;
103 3         12 for (my $i=0; $i< @_; $i++) {
104 10         40 $numbered{$i} = $_[$i];
105             }
106              
107 3         4 my %named;
108 3         16 foreach ( my $i = @_ % 2; $i < @_; $i += 2 ) {
109 4 50       11 if ( ref $_[$i] ) {
110 0         0 undef %named;
111 0         0 last;
112             } else {
113 4         22 $named{ $_[$i] } = $_[$i+1];
114             }
115             }
116              
117 3         21 return {%numbered, %named};
118 3   100     39 };
119             }
120              
121             sub _get_output_preparator {
122 1     1   2 my $self = shift;
123              
124             return $self->{output_preparator} ||= sub {
125 1     1   2 my %numbered;
126              
127 1         5 for (my $i=0; $i< @_; $i++) {
128 1         5 $numbered{$i} = $_[$i];
129             }
130              
131 1         3 return \%numbered;
132 1   50     15 };
133             }
134              
135             sub _get_on_fail {
136 3     3   18 my $self = shift;
137              
138             return $self->{on_fail} ||= sub {
139 3     3   7 my ( $type, $package, $subname, $errors ) = @_;
140              
141 3         10 local $Carp::Internal{ (__PACKAGE__) } = 1;
142              
143 3         23 die LIVR::Contract::Exception->new(
144             type => $type,
145             package => $package,
146             subname => $subname,
147             errors => $errors
148             );
149             }
150 3   100     28 }
151              
152              
153             =head1 NAME
154              
155             LIVR::Contract - Design by Contract in Perl with Language Independent Validation Rules (LIVR).
156              
157             =head1 SYNOPSIS
158              
159             # Common usage
160             use LIVR::Contract;
161              
162             # Positional arguments
163             contract 'my_method1' => (
164             requires => {
165             0 => [ 'required' ]
166             1 => [ 'required', 'positive_integer' ]
167             2 => [ 'required' ],
168             },
169             ensures => {
170             0 => ['required', 'positive_integer' ]
171             }
172             );
173              
174             # Named arguments
175             contract 'my_method2' => (
176             requires => {
177             0 => [ 'required' ],
178             id => [ 'required', 'positive_integer' ],
179             name => [ 'required' ],
180             },
181             ensures => {
182             0 => ['required', 'positive_integer' ]
183             }
184             );
185              
186             # Named arguments in hashref
187             contract 'my_method3' => (
188             requires => {
189             0 => [ 'required' ],
190             1 => [ 'required', { nested_object => {
191             id => [ 'required', 'positive_integer' ],
192             name => [ 'required' ],
193             }
194             }
195             },
196             ensures => {
197             0 => ['required', 'positive_integer' ]
198             }
199             );
200            
201             sub my_method1 {
202             my ($self, $id, $name) = @_;
203             return 100;
204             }
205              
206             sub my_method2 {
207             my ($self, %named_args) = @_;
208             return 100;
209             }
210              
211             sub my_method3 {
212             my ($self, $named_args_hashref) = @_;
213             return 100;
214             }
215              
216             # Somewhere in your code
217             $self->my_method1( 100, 'Some Name');
218              
219             # Somewhere in your code
220             $self->my_method2(
221             id => 100,
222             name => 'Some Name',
223             );
224              
225             # Somewhere in your code
226             $self->my_method3({
227             id => 100,
228             name => 'Some Name',
229             });
230              
231              
232              
233             =head1 WARNING
234              
235             B
236             quality. Things might be broken, not all
237             features have been implemented, and APIs are likely to change. YOU
238             HAVE BEEN WARNED.>
239              
240             =head1 DESCRIPTION
241              
242             L design by Contract in Perl with Language Independent Validation Rules (LIVR). Uses L underneath.
243              
244             See L for rules descriptions.
245              
246             =head1 TODO
247              
248             =over 4
249              
250             =item * Contracts in separate files (Roles)
251              
252             =back
253              
254             =head1 AUTHOR
255              
256             Viktor Turskyi, C<< >>
257              
258             =head1 BUGS
259              
260             Please report any bugs or feature requests to Github L
261              
262              
263             =head1 SUPPORT
264              
265             You can find documentation for this module with the perldoc command.
266              
267             perldoc LIVR::Contract
268              
269             =head1 ACKNOWLEDGEMENTS
270              
271              
272             =head1 LICENSE AND COPYRIGHT
273              
274             Copyright 2013 Viktor Turskyi.
275              
276             This program is free software; you can redistribute it and/or modify it
277             under the terms of the the Artistic License (2.0). You may obtain a
278             copy of the full license at:
279              
280             L
281              
282             Any use, modification, and distribution of the Standard or Modified
283             Versions is governed by this Artistic License. By using, modifying or
284             distributing the Package, you accept this license. Do not use, modify,
285             or distribute the Package, if you do not accept this license.
286              
287             If your Modified Version has been derived from a Modified Version made
288             by someone other than you, you are nevertheless required to ensure that
289             your Modified Version complies with the requirements of this license.
290              
291             This license does not grant you the right to use any trademark, service
292             mark, tradename, or logo of the Copyright Holder.
293              
294             This license includes the non-exclusive, worldwide, free-of-charge
295             patent license to make, have made, use, offer to sell, sell, import and
296             otherwise transfer the Package with respect to any patent claims
297             licensable by the Copyright Holder that are necessarily infringed by the
298             Package. If you institute patent litigation (including a cross-claim or
299             counterclaim) against any party alleging that the Package constitutes
300             direct or contributory patent infringement, then this Artistic License
301             to you shall terminate on the date that such litigation is filed.
302              
303             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER
304             AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
305             THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
306             PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY
307             YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR
308             CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR
309             CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE,
310             EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
311              
312              
313             =cut
314              
315             1;