File Coverage

blib/lib/REST/Neo4p/Constraint/Property.pm
Criterion Covered Total %
statement 136 154 88.3
branch 66 86 76.7
condition 15 25 60.0
subroutine 23 24 95.8
pod 4 5 80.0
total 244 294 82.9


line stmt bran cond sub pod time code
1             #$Id$
2             package REST::Neo4p::Constraint::Property;
3 4     4   3150 use base 'REST::Neo4p::Constraint';
  4         10  
  4         454  
4 4     4   29 use strict;
  4         8  
  4         84  
5 4     4   27 use warnings;
  4         8  
  4         151  
6              
7             BEGIN {
8 4     4   5105 $REST::Neo4p::Constraint::Property::VERSION = '0.4000';
9             }
10              
11             sub new_from_constraint_hash {
12 18     18 0 57 my $self = shift;
13 18         34 my ($constraints) = @_;
14 18 50       66 die "tag not defined" unless $self->tag;
15 18 100 66     119 die "constraint hash not defined or not a hashref" unless defined $constraints && (ref $constraints eq 'HASH');
16 17 100       47 if (my $cond = $constraints->{_condition}) {
17 14 50       162 unless (grep(/^$cond$/,qw( all only none ))) {
18 0         0 die "Property constraint condition must be all|only|none";
19             }
20             }
21             else {
22 3         8 $constraints->{_condition} = 'only';
23             }
24 17   100     171 $constraints->{_priority} ||= 0;
25 17         70 $self->{_constraints} = $constraints;
26 17         38 return $self;
27             };
28            
29             sub add_constraint {
30 1     1 1 3 my $self = shift;
31 1         3 my ($key, $value) = @_;
32 1 50 33     13 unless (!ref($key) && ($key=~/^[a-z0-9_]+$/i)) {
33 0         0 REST::Neo4p::LocalException->throw("Property name (arg 1) contains disallowed characters in add_constraint\n");
34             }
35 1 50 33     8 unless (!ref($value) || ref($value) eq 'ARRAY') {
36 0         0 REST::Neo4p::LocalException->throw("Constraint value for '$key' must be string, regex, or arrayref of strings and regexes\n");
37             }
38 1         8 $self->constraints->{$key} = $value;
39 1         2 return 1;
40             }
41              
42             sub remove_constraint {
43 0     0 1 0 my $self = shift;
44 0         0 my ($tag) = @_;
45 0         0 delete $self->constraints->{$tag};
46             }
47              
48             sub set_condition {
49 22     22 1 3266 my $self = shift;
50 22         41 my ($condition) = @_;
51 22 50       174 unless ($condition =~ /^(all|only|none)$/) {
52 0         0 REST::Neo4p::LocalException->throw("Property constraint condition must be all|only|none\n");
53             }
54 22         64 return $self->{_constraints}{_condition} = $condition;
55             }
56              
57             # validate the input property hash or Entity with respect to the
58             # constraint represented by this object
59              
60             sub validate {
61 72     72 1 102 my $self = shift;
62 72         124 my ($prop_hash) = @_;
63 72 50       150 if (ref($prop_hash) eq 'REST::Neo4p::Node') {
64 0         0 $prop_hash = $prop_hash->get_properties();
65             }
66 72 50       136 if (ref($prop_hash) eq 'REST::Neo4p::Relationship') {
67 0         0 my $ph = $prop_hash->get_properties();
68 0         0 $ph->{_relationship_type} = $prop_hash->type; # psuedo property that must match exactly
69 0         0 $prop_hash = $ph;
70             }
71             # otherwise, $prop_hash is hashref as validated in the calling subclass
72 72         102 my $is_valid = 1;
73 72         166 my $condition = $self->condition;
74             FORWARDCHECK:
75 72         223 while (my ($prop,$val) = each %$prop_hash ) {
76 139 50       280 next if ($prop =~ /^_(condition|priority)$/);
77 139         299 my $value_spec = $self->constraints->{$prop};
78 139 100       285 if (defined $value_spec) {
79 109 100       198 unless (_validate_value($prop,$val,$value_spec,$condition)) {
80 26         38 $is_valid = 0;
81 26         53 last FORWARDCHECK;
82             }
83             }
84             else {
85 30 100       86 if ($condition eq 'only') {
86 13         26 $is_valid = 0;
87 13         24 last FORWARDCHECK;
88             }
89             }
90             }
91 72         116 keys %$prop_hash;
92             BACKWARDCHECK:
93 72   100     158 while ( $is_valid && (my ($prop, $value_spec) = each %{$self->constraints}) ) {
  194         416  
94 166 100       487 next if ($prop =~ /^_(condition|priority)$/); ##
95 105         158 my $val = $prop_hash->{$prop};
96 105 100       175 unless (_validate_value($prop,$val,$value_spec,$condition)) {
97 5         9 $is_valid = 0;
98 5         9 last BACKWARDCHECK;
99             }
100             }
101 72         113 keys %{$self->constraints};
  72         144  
102 72         241 return $is_valid;
103             }
104              
105             sub _validate_value {
106 220     220   388 my ($prop,$value,$value_spec,$condition) = @_;
107              
108 220 50 33     822 die "arg1(prop), arg3(value_spec), and arg4(condition) must all be defined" unless defined $prop && defined $value_spec && defined $condition;
      33        
109 220         329 my $is_valid = 1;
110 220         351 for ($value_spec) {
111 220 100       480 ref eq 'ARRAY' && do {
112 33 100       65 if (!@$value_spec) { #empty array
113 20         30 1; # don't care
114             }
115             else {
116 13 50       42 die "single value in arrayref must be scalar" unless ref($value_spec->[0]) =~ /^|Regexp$/;
117 13 50       31 die "single value in arrayref cannot be empty string" unless length $value_spec->[0];
118 13 100       25 if (defined $value) {
119 6         17 $is_valid = _validate_value($prop,$value,$value_spec->[0],$condition);
120             } # otherwise don't care
121             }
122 33         43 last;
123             };
124 187 100       338 ref eq 'Regexp' && do {
125 98 100       335 if ($condition =~ /all|only/) {
126 94 100       167 if (!defined $value) {
127 4         6 $is_valid = 0;
128             }
129             else {
130 90 100       429 $is_valid = 0 unless ($value =~ /$value_spec/);
131             }
132             }
133             else { # $condition eq 'none'
134 4 100       11 if (defined $value) {
135 3 100       18 $is_valid = 0 unless ($value !~ /$value_spec/);
136             }
137             }
138 98         182 last;
139             };
140 89 50       154 (ref eq '') && do { # simple string
141 89 100       164 if (length) {
142 72 100       234 if ($condition =~ /all|only/) {
    50          
143 69 100       122 if (!defined $value) {
144 1         2 $is_valid = 0;
145             }
146             else {
147 68 100 66     174 $is_valid = 0 unless (($value eq $value_spec) ||
148             $value_spec eq '*');
149             }
150             }
151             elsif ($condition eq 'none') {
152 3 100       7 if (defined $value) {
153 2 50       6 $is_valid = 0 unless ($value ne $value_spec);
154             }
155             }
156             else { #fallthru
157 0         0 die "I shouldn't be here in _validate_value";
158             }
159             }
160             else { # empty string means this property is required to be present
161 17 100       60 if ($condition =~ /all|only/) {
    50          
162 14 50       31 if (!defined $value) {
163 0         0 $is_valid = 0;
164             }
165             }
166             elsif ($condition eq 'none') {
167 3 100       7 if (defined $value) {
168 2         3 $is_valid = 0
169             }
170             }
171             else { #fallthru
172 0         0 die "I shouldn't be here in _validate_value";
173             }
174             }
175 89         136 last;
176             };
177             # fallthru
178 0         0 do {
179 0         0 REST::Neo4p::LocalException->throw("Invalid constraint value spec for property '$prop'\n");
180             };
181             }
182 220         715 return $is_valid;
183             }
184              
185             1;
186              
187             package REST::Neo4p::Constraint::NodeProperty;
188 4     4   35 use base 'REST::Neo4p::Constraint::Property';
  4         18  
  4         487  
189 4     4   32 use strict;
  4         10  
  4         172  
190 4     4   27 use warnings;
  4         6  
  4         197  
191             BEGIN {
192 4     4   16 $REST::Neo4p::Constraint::NodeProperty::VERSION='0.4000';
193 4         804 $REST::Neo4p::Constraint::NodeProperty::VERSION='0.4000';
194             }
195              
196             sub new {
197 13     13   2779 my $class = shift;
198 13         70 my $self = $class->SUPER::new(@_);
199 12         27 $self->{_type} = 'node_property';
200 12         60 return $self;
201             }
202              
203             sub validate {
204 60     60   111 my $self = shift;
205 60         97 my ($item) = (@_);
206 60 50       116 return unless defined $item;
207 60 50       282 unless ( ref($item) =~ /Node|HASH$/ ) {
208 0         0 REST::Neo4p::LocalException->throw("validate() requires a single hashref or Node object\n");
209             }
210 60         136 $self->SUPER::validate(@_);
211             }
212             1;
213              
214             package REST::Neo4p::Constraint::RelationshipProperty;
215 4     4   39 use base 'REST::Neo4p::Constraint::Property';
  4         18  
  4         387  
216 4     4   29 use strict;
  4         10  
  4         97  
217 4     4   23 use warnings;
  4         8  
  4         180  
218              
219             BEGIN {
220 4     4   16 $REST::Neo4p::Constraint::RelationshipProperty::VERSION='0.4000';
221 4         1169 $REST::Neo4p::Constraint::RelationshipProperty::VERSION='0.4000';
222             }
223             # relationship_type is added as a pseudoproperty
224              
225             sub new {
226 5     5   23 my $class = shift;
227 5         29 my $self = $class->SUPER::new(@_);
228 5         14 $self->{_type} = 'relationship_property';
229 5         19 return $self;
230             }
231              
232             sub new_from_constraint_hash {
233 5     5   10 my $self = shift;
234 5         25 $self->SUPER::new_from_constraint_hash(@_);
235 5   100     34 $self->constraints->{_relationship_type} ||= [];
236 5         12 return $self;
237             }
238              
239 3     3   12 sub rtype { shift->constraints->{_relationship_type} }
240             sub validate {
241 12     12   19 my $self = shift;
242 12         20 my ($item) = (@_);
243 12 50       28 return unless defined $item;
244 12 50       58 unless ( ref($item) =~ /Neo4p::Relationship|HASH$/ ) {
245 0         0 REST::Neo4p::LocalException->throw("validate() requires a single hashref or Relationship object\n");
246             }
247 12         34 $self->SUPER::validate(@_);
248             }
249              
250             1;
251              
252             =head1 NAME
253              
254             REST::Neo4p::Constraint::Property - Neo4j Property Constraints
255              
256             =head1 SYNOPSIS
257              
258             # use REST::Neo4p::Constrain, it's nicer
259              
260             $npc = REST::Neo4p::Constraint::NodeProperty->new(
261             'soldier' => { _condition => 'all',
262             _priority => 1,
263             name => '',
264             rank => [],
265             serial_number => qr/^[0-9]+$/,
266             army_of => 'one' }
267             );
268              
269             $rpc = REST::Neo4p::Constraint::RelationshipProperty->new(
270             'position' => { _condition => 'only',
271             position => qr/[0-9]+/ }
272             );
273              
274             =head1 DESCRIPTION
275              
276             C and
277             C are classes that
278             represent constraints on the presence and values of Node and
279             Relationship entities.
280              
281             Constraint hash specification:
282              
283             {
284             _condition => constraint_conditions, # ('all'|'only'|'none')
285             _relationship_type => ,
286             _priority => ,
287             prop_0 => [], # may have, no constraint
288             prop_1 => [], # may have, if present must meet
289             prop_2 => '', # must have, no constraint
290             prop_3 => 'value', # must have, value must eq 'value'
291             prop_4 => qr/.alue/, # must have, value must match qr/.alue/,
292             prop_5 => qr/^value1|value2|value3$/ # regexp for enumerations
293             }
294              
295             =head1 METHODS
296              
297             =over
298              
299             =item new()
300              
301             $np = REST::Neo4p::Constraint::NodeProperty->new(
302             $tag => $constraint_hash
303             );
304              
305             $rp = REST::Neo4p::Constraint::RelationshipProperty->new(
306             $tag => $constraint_hash
307             );
308              
309             =item add_constraint()
310              
311             $np->add_constraint( optional_accessory => [qw(tie ascot boutonniere)] );
312              
313             =item remove_constraint()
314              
315             $np->remove_constraint( 'unneeded_property' );
316              
317             =item tag()
318              
319             Returns the constraint tag.
320              
321             =item type()
322              
323             Returns the constraint type ('node_property' or 'relationship_property').
324              
325             =item condition()
326              
327             =item set_condition()
328              
329             Set/get 'all', 'only', 'none' for a given property constraint. See
330             L.
331              
332             =item priority()
333              
334             =item set_priority()
335              
336             Constraints with higher priority will be checked before constraints
337             with lower priority by
338             L|REST::Neo4p::Constraint/Functional
339             interface for validation>.
340              
341             =item constraints()
342              
343             Returns the internal constraint spec hashref.
344              
345             =item validate()
346              
347             $c->validate( $node_object )
348             $c->validate( $relationship_object )
349             $c->validate( { name => 'Steve', instrument => 'banjo } );
350              
351             Returns true if the item meets the constraint, false if not.
352              
353             =back
354              
355             =head1 SEE ALSO
356              
357             L, L, L,
358             L, L,
359             L.
360              
361             =head1 AUTHOR
362              
363             Mark A. Jensen
364             CPAN ID: MAJENSEN
365             majensen -at- cpan -dot- org
366              
367             =head1 LICENSE
368              
369             Copyright (c) 2012-2020 Mark A. Jensen. This program is free software; you
370             can redistribute it and/or modify it under the same terms as Perl
371             itself.
372              
373             =cut
374              
375             1;