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   3969 use base 'REST::Neo4p::Constraint';
  4         12  
  4         475  
4 4     4   36 use strict;
  4         9  
  4         99  
5 4     4   31 use warnings;
  4         11  
  4         139  
6              
7             BEGIN {
8 4     4   5807 $REST::Neo4p::Constraint::Property::VERSION = '0.4001';
9             }
10              
11             sub new_from_constraint_hash {
12 18     18 0 33 my $self = shift;
13 18         33 my ($constraints) = @_;
14 18 50       75 die "tag not defined" unless $self->tag;
15 18 100 66     112 die "constraint hash not defined or not a hashref" unless defined $constraints && (ref $constraints eq 'HASH');
16 17 100       44 if (my $cond = $constraints->{_condition}) {
17 14 50       151 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         9 $constraints->{_condition} = 'only';
23             }
24 17   100     95 $constraints->{_priority} ||= 0;
25 17         63 $self->{_constraints} = $constraints;
26 17         37 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     12 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     7 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         3 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 3346 my $self = shift;
50 22         39 my ($condition) = @_;
51 22 50       130 unless ($condition =~ /^(all|only|none)$/) {
52 0         0 REST::Neo4p::LocalException->throw("Property constraint condition must be all|only|none\n");
53             }
54 22         65 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 80     80 1 103 my $self = shift;
62 80         141 my ($prop_hash) = @_;
63 80 50       161 if (ref($prop_hash) eq 'REST::Neo4p::Node') {
64 0         0 $prop_hash = $prop_hash->get_properties();
65             }
66 80 50       147 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 80         104 my $is_valid = 1;
73 80         194 my $condition = $self->condition;
74             FORWARDCHECK:
75 80         244 while (my ($prop,$val) = each %$prop_hash ) {
76 158 50       293 next if ($prop =~ /^_(condition|priority)$/);
77 158         321 my $value_spec = $self->constraints->{$prop};
78 158 100       334 if (defined $value_spec) {
79 121 100       227 unless (_validate_value($prop,$val,$value_spec,$condition)) {
80 34         48 $is_valid = 0;
81 34         61 last FORWARDCHECK;
82             }
83             }
84             else {
85 37 100       110 if ($condition eq 'only') {
86 13         19 $is_valid = 0;
87 13         24 last FORWARDCHECK;
88             }
89             }
90             }
91 80         146 keys %$prop_hash;
92             BACKWARDCHECK:
93 80   100     168 while ( $is_valid && (my ($prop, $value_spec) = each %{$self->constraints}) ) {
  191         403  
94 163 100       437 next if ($prop =~ /^_(condition|priority)$/); ##
95 103         153 my $val = $prop_hash->{$prop};
96 103 100       173 unless (_validate_value($prop,$val,$value_spec,$condition)) {
97 5         10 $is_valid = 0;
98 5         9 last BACKWARDCHECK;
99             }
100             }
101 80         114 keys %{$self->constraints};
  80         183  
102 80         282 return $is_valid;
103             }
104              
105             sub _validate_value {
106 230     230   403 my ($prop,$value,$value_spec,$condition) = @_;
107              
108 230 50 33     900 die "arg1(prop), arg3(value_spec), and arg4(condition) must all be defined" unless defined $prop && defined $value_spec && defined $condition;
      33        
109 230         326 my $is_valid = 1;
110 230         360 for ($value_spec) {
111 230 100       437 ref eq 'ARRAY' && do {
112 35 100       66 if (!@$value_spec) { #empty array
113 23         29 1; # don't care
114             }
115             else {
116 12 50       50 die "single value in arrayref must be scalar" unless ref($value_spec->[0]) =~ /^|Regexp$/;
117 12 50       31 die "single value in arrayref cannot be empty string" unless length $value_spec->[0];
118 12 100       21 if (defined $value) {
119 6         16 $is_valid = _validate_value($prop,$value,$value_spec->[0],$condition);
120             } # otherwise don't care
121             }
122 35         47 last;
123             };
124 195 100       353 ref eq 'Regexp' && do {
125 96 100       310 if ($condition =~ /all|only/) {
126 92 100       166 if (!defined $value) {
127 2         3 $is_valid = 0;
128             }
129             else {
130 90 100       466 $is_valid = 0 unless ($value =~ /$value_spec/);
131             }
132             }
133             else { # $condition eq 'none'
134 4 100       20 if (defined $value) {
135 3 100       19 $is_valid = 0 unless ($value !~ /$value_spec/);
136             }
137             }
138 96         162 last;
139             };
140 99 50       168 (ref eq '') && do { # simple string
141 99 100       177 if (length) {
142 82 100       315 if ($condition =~ /all|only/) {
    50          
143 79 100       146 if (!defined $value) {
144 3         12 $is_valid = 0;
145             }
146             else {
147 76 100 66     206 $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       7 $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       30 if (!defined $value) {
163 0         0 $is_valid = 0;
164             }
165             }
166             elsif ($condition eq 'none') {
167 3 100       9 if (defined $value) {
168 2         5 $is_valid = 0
169             }
170             }
171             else { #fallthru
172 0         0 die "I shouldn't be here in _validate_value";
173             }
174             }
175 99         145 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 230         713 return $is_valid;
183             }
184              
185             1;
186              
187             package REST::Neo4p::Constraint::NodeProperty;
188 4     4   49 use base 'REST::Neo4p::Constraint::Property';
  4         18  
  4         483  
189 4     4   31 use strict;
  4         32  
  4         146  
190 4     4   47 use warnings;
  4         9  
  4         266  
191             BEGIN {
192 4     4   24 $REST::Neo4p::Constraint::NodeProperty::VERSION='0.4001';
193 4         878 $REST::Neo4p::Constraint::NodeProperty::VERSION='0.4001';
194             }
195              
196             sub new {
197 13     13   2809 my $class = shift;
198 13         61 my $self = $class->SUPER::new(@_);
199 12         159 $self->{_type} = 'node_property';
200 12         60 return $self;
201             }
202              
203             sub validate {
204 68     68   119 my $self = shift;
205 68         105 my ($item) = (@_);
206 68 50       128 return unless defined $item;
207 68 50       293 unless ( ref($item) =~ /Node|HASH$/ ) {
208 0         0 REST::Neo4p::LocalException->throw("validate() requires a single hashref or Node object\n");
209             }
210 68         172 $self->SUPER::validate(@_);
211             }
212             1;
213              
214             package REST::Neo4p::Constraint::RelationshipProperty;
215 4     4   35 use base 'REST::Neo4p::Constraint::Property';
  4         8  
  4         436  
216 4     4   31 use strict;
  4         13  
  4         106  
217 4     4   32 use warnings;
  4         21  
  4         222  
218              
219             BEGIN {
220 4     4   45 $REST::Neo4p::Constraint::RelationshipProperty::VERSION='0.4001';
221 4         1280 $REST::Neo4p::Constraint::RelationshipProperty::VERSION='0.4001';
222             }
223             # relationship_type is added as a pseudoproperty
224              
225             sub new {
226 5     5   38 my $class = shift;
227 5         30 my $self = $class->SUPER::new(@_);
228 5         13 $self->{_type} = 'relationship_property';
229 5         22 return $self;
230             }
231              
232             sub new_from_constraint_hash {
233 5     5   14 my $self = shift;
234 5         21 $self->SUPER::new_from_constraint_hash(@_);
235 5   100     26 $self->constraints->{_relationship_type} ||= [];
236 5         11 return $self;
237             }
238              
239 3     3   14 sub rtype { shift->constraints->{_relationship_type} }
240             sub validate {
241 12     12   24 my $self = shift;
242 12         52 my ($item) = (@_);
243 12 50       27 return unless defined $item;
244 12 50       64 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         31 $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-2021 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;