File Coverage

blib/lib/IO/Iron/PolicyBase.pm
Criterion Covered Total %
statement 169 194 87.1
branch 21 32 65.6
condition 10 15 66.6
subroutine 24 25 96.0
pod 5 5 100.0
total 229 271 84.5


line stmt bran cond sub pod time code
1             package IO::Iron::PolicyBase;
2              
3             ## no critic (Documentation::RequirePodAtEnd)
4             ## no critic (Documentation::RequirePodSections)
5             ## no critic (Subroutines::RequireArgUnpacking)
6              
7 6     6   3102 use 5.010_000;
  6         21  
8 6     6   30 use strict;
  6         21  
  6         135  
9 6     6   29 use warnings;
  6         11  
  6         143  
10              
11             # Global creator
12       6     BEGIN {
13             # Inherit nothing
14             }
15              
16             # Global destructor
17       6     END {
18             }
19              
20             # ABSTRACT: Base package (inherited) for IO::Iron::IronMQ/Cache/Worker::Policy packages.
21              
22             our $VERSION = '0.14'; # VERSION: generated by DZP::OurPkgVersion
23              
24 6     6   38 use Log::Any qw{$log};
  6         38  
  6         49  
25 6     6   1509 use Hash::Util 0.06 qw{lock_keys unlock_keys};
  6         127  
  6         38  
26 6     6   2509 use Carp::Assert;
  6         5061  
  6         56  
27 6     6   811 use Carp::Assert::More;
  6         12  
  6         1130  
28 6     6   39 use English '-no_match_vars';
  6         16  
  6         34  
29 6     6   2211 use File::Spec ();
  6         13  
  6         139  
30 6     6   2202 use Params::Validate qw(:all);
  6         37104  
  6         1209  
31             use Exception::Class (
32 6         59 'IronPolicyException' => {
33             fields => [ 'policy', 'candidate' ],
34             },
35             'NoIronPolicyException' => {
36             fields => [],
37             },
38             'CharacterGroupNotDefinedIronPolicyException' => {
39             fields => [],
40             }
41 6     6   3115 );
  6         54284  
42              
43 6     6   7458 use IO::Iron::Common ();
  6         17  
  6         131  
44 6     6   2910 use IO::Iron::PolicyBase::CharacterGroup ();
  6         17  
  6         14232  
45              
46             # INTERNAL METHODS
47             # For use in the inheriting subclass
48              
49             # TODO policy character set, list possible alternatives:
50             sub IRON_CLIENT_DEFAULT_POLICIES {
51 2     2 1 24 my %default_policies = (
52             'definition' => {
53             'character_set' => 'ascii', # The only supported character set!
54             'character_group' => {},
55             'no_limitation' => 1, # There is an unlimited number of alternatives.
56             },
57             'queue' => { 'name' => ['[:word:]{1,}'], },
58             'cache' => { 'name' => ['[:word:]{1,}'], 'item_key' => ['[:word:]{1,}'] },
59             'worker' => { 'name' => ['[:word:]{1,}'], },
60             );
61 2         11 return %default_policies;
62             }
63              
64             sub _do_alt {
65 618     618   1004 my $self = shift;
66 618         8840 my %params = validate(
67             @_,
68             {
69             'str' => { type => SCALAR, }, # name/key name.
70             }
71             );
72 618         2526 my $str = $params{'str'};
73 618         1964 $log->tracef( 'Entering _do_alt(%s)', $str );
74 618         44344 assert( length $str > 0, 'String length > 0.' );
75 618         2694 my @processed_alts;
76 618 100 100     5298 if ( $str =~ /^([[:graph:]]*)(\[:[[:graph:]]+:\]\{[[:digit:]]+\,[[:digit:]]+\})([[:graph:]]*)$/sx
      100        
77             || ( $str =~ /^([[:graph:]]*)(\[:[[:graph:]]+:\]\{([[:digit:]]+)\})([[:graph:]]*)$/sx && $3 > 1 ) )
78             {
79 12         44 $log->tracef( 'We need to do recursion.', $str );
80 12         868 my $preceeding_part = $1;
81 12         31 my $group_part = $2;
82 12 100       41 my $succeeding_part = defined $4 ? $4 : $3;
83 12         41 $log->tracef( '$preceeding_part=%s;$group_part=%s;$succeeding_part=%s;', $preceeding_part, $group_part, $succeeding_part );
84 12         979 my @alternatives = _make_ones( $preceeding_part, $group_part, $succeeding_part );
85 12         34 foreach (@alternatives) {
86 13         78 push @processed_alts, $self->_do_alt( 'str' => $_ );
87             }
88             }
89             else {
90 606         1810 $log->tracef( 'We need to create the alternatives.', $str );
91 606 100       41516 if ( $str =~ /^([[:graph:]]*)(\[:[[:graph:]]+:\]\{1\})([[:graph:]]*)$/sx ) {
92 238         437 my @alts;
93 238         485 my $preceeding_part = $1;
94 238         458 my $group_part = $2;
95 238         428 my $succeeding_part = $3;
96 238         662 $log->tracef( '$preceeding_part=%s;$group_part=%s;$succeeding_part=%s;',
97             $preceeding_part, $group_part, $succeeding_part );
98 238 50       19276 if ( $group_part =~ /^(\[:[[:graph:]]+:\])\{([[:digit:]]+)\}$/sx ) {
99 238         502 my $group = $1;
100 238         373 my $lowest_amount = $2;
101 238         366 my $highest_amount = $3;
102 238         688 $log->tracef( '$group=%s;$lowest_amount=%s;$highest_amount=%s;', $group, $lowest_amount, $highest_amount );
103 238         18246 foreach ( $self->_get_character_group_alternatives( 'character_group' => $group ) ) {
104 602         1676 push @alts, $preceeding_part . $_ . $succeeding_part;
105             }
106             }
107 238         822 $log->tracef( '@alts=%s;', \@alts );
108 238         57783 foreach (@alts) {
109 602         1689 push @processed_alts, $self->_do_alt( 'str' => $_ );
110             }
111             }
112             else {
113 368         761 push @processed_alts, $str;
114             }
115             }
116 618         1892 $log->tracef( 'Exiting _do_alt():%s', \@processed_alts );
117 618         146456 return @processed_alts;
118             }
119              
120             sub _make_ones {
121 12     12   25 my $preceeding_part = $_[0];
122 12         18 my $group_part = $_[1];
123 12         22 my $succeeding_part = $_[2];
124 12         46 $log->tracef( '_make_ones():$preceeding_part=%s;$group_part=%s;$succeeding_part=%s;',
125             $preceeding_part, $group_part, $succeeding_part );
126 12         901 $log->tracef( '$group_part=%s;', $group_part );
127 12         841 my @alternatives;
128 12 100       103 if ( $group_part =~ /^(\[:[[:graph:]]+:\])\{([[:digit:]]+)\,([[:digit:]]+)\}$/msx ) {
    50          
129 1         5 my $group = $1;
130 1         3 my $lowest_amount = $2;
131 1         4 my $highest_amount = $3;
132 1         7 $log->tracef( '$group=%s;$lowest_amount=%s;$highest_amount=%s;', $group, $lowest_amount, $highest_amount );
133 1         139 for ( $lowest_amount .. $highest_amount ) {
134 2         34 my $group_str = $group . '{1}';
135 2         20 push @alternatives, $preceeding_part . $group_str x $_ . $succeeding_part;
136             }
137             }
138             elsif ( $group_part =~ /^(\[:[[:graph:]]+:\])\{([[:digit:]]+)\}$/msx ) {
139 11         31 my $group = $1;
140 11         18 my $lowest_amount = $2;
141 11         19 my $highest_amount = $2;
142 11         34 $log->tracef( '$group=%s;$lowest_amount=%s;$highest_amount=%s;', $group, $lowest_amount, $highest_amount );
143 11         856 for ( my $i = $lowest_amount ; $i < $highest_amount + 1 ; $i++ ) {
144 11         35 my $group_str = $group . '{1}';
145 11         55 push @alternatives, $preceeding_part . $group_str x $i . $succeeding_part;
146             }
147             }
148             else {
149 0         0 $log->fatalf( 'Illegal string \'%s\'.', $group_part );
150             }
151 12         60 $log->tracef( '@alternatives=%s;', \@alternatives );
152 12         3024 return @alternatives;
153             }
154              
155             sub _get_character_group_alternatives {
156 254     254   414 my $self = shift;
157 254         4602 my %params = validate(
158             @_,
159             {
160             'character_group' => { type => SCALAR, regex => qr/^[[:graph:]]+$/msx, }, # name/key name.
161             'keep_posix_group' => { type => BOOLEAN, optional => 1, }, # Keep POSIX (subset) group name and return it.
162             },
163             );
164 254         4738 my $chars;
165              
166             # Predefined groups (subset of POSIX) first!
167 254         806 $chars = IO::Iron::PolicyBase::CharacterGroup::group( 'character_group' => $params{'character_group'} );
168 254 100 100     681 if ( $chars && $params{'keep_posix_group'} ) {
169 16         31 $chars = $params{'character_group'}; # Put the group name back.
170             }
171 254 100       559 if ( !$chars ) {
172 237         733 $chars = $self->{'policy'}->{'definition'}->{'character_group'}->{ $params{'character_group'} };
173             }
174 254 50       541 if ($chars) {
175 254         615 $log->tracef( '$chars=%s;', $chars );
176             }
177             else {
178 0         0 $log->fatalf( 'Character group \'%s\' not defined.', $params{'character_group'} );
179             CharacterGroupNotDefinedIronPolicyException->throw(
180             error => 'CharacterGroupNotDefinedIronPolicyException: Character group \''
181 0         0 . $params{'character_group'}
182             . '\' not defined!', );
183             }
184 254         17592 return split //msx, $chars;
185             }
186              
187             sub alternatives {
188 3     3 1 9 my $self = shift;
189 3         67 my %params = validate(
190             @_,
191             {
192             'required_policy' => { type => SCALAR, }, # name/key name.
193             }
194             );
195 3         30 assert_hashref( $self->{'policy'}, 'self->{required_policy} is a reference to a list.' );
196 3         41 $log->tracef( 'Entering alternatives(%s)', \%params );
197              
198 3 50 33     931 if ( defined $self->{'policy'}->{'definition'}->{'no_limitation'}
199             && $self->{'policy'}->{'definition'}->{'no_limitation'} == 1 )
200             {
201 0         0 NoIronPolicyException->throw( error => 'NoIronPolicyException: Cannot list alternatives, unlimited number!', );
202             }
203 3         37 my $templates = $self->{'policy'}->{ $params{'required_policy'} };
204 3         26 assert_listref( $templates, 'templates is a reference to a list' );
205 3         66 my @template_alternatives;
206 3         9 foreach ( @{$templates} ) {
  3         13  
207 3         13 $log->tracef( 'alternatives(): Template:\"%s\".)', $_ );
208 3         271 push @template_alternatives, $self->_do_alt( 'str' => $_ );
209             }
210 3         18 $log->tracef( 'Exiting alternatives():%s', \@template_alternatives );
211 3         1391 return @template_alternatives;
212             }
213              
214             sub _get_chars_or_remain_posix_group {
215 16     16   26 my $self = shift;
216 16         55 $log->tracef( 'Entering _get_chars_or_remain_posix_group(%s)', \@_ );
217 16         3692 my $group = $_[0];
218 16         49 $log->tracef( '_get_chars_or_remain_posix_group(): Ask for Group alternatives for :%s', $group );
219 16         1102 my @chars = $self->_get_character_group_alternatives( 'character_group' => $group, 'keep_posix_group' => 1 );
220 16         51 $log->tracef( '_get_chars_or_remain_posix_group(): Group alternatives:%s', \@chars );
221 16         4029 my $group_chars = join q{}, @chars;
222 16         66 $log->tracef( 'Exiting _get_chars_or_remain_posix_group():%s', ( '[' . $group_chars . ']' ) );
223 16         1144 return '[' . $group_chars . ']';
224             }
225              
226             sub _convert_policy_to_normal_regexp {
227 22     22   51 my $self = shift;
228 22         89 $log->tracef( 'Entering _convert_policy_to_normal_regexp(%s)', \@_ );
229 22         5684 my $policy_regexp = $_[0];
230 22         132 $policy_regexp =~ s/(\[:[[:graph:]]+?:\])/$self->_get_chars_or_remain_posix_group($1)/egsx;
  16         54  
231 22         127 $log->tracef( 'Exiting _convert_policy_to_normal_regexp():%s', $policy_regexp );
232 22         1568 return $policy_regexp;
233             }
234              
235             sub is_valid_policy {
236 0     0 1 0 my $self = shift;
237 0         0 my %params = validate(
238             @_,
239             {
240             'policy' => { type => SCALAR, }, # name/key name.
241             'candidate' => { type => SCALAR, }, # string to check.
242             }
243             );
244 0         0 assert_listref( $self->{'policy'}, 'self->{policy} is a reference to a list.' );
245 0         0 $log->tracef( 'Entering is_valid_policy(%s)', \%params );
246              
247 0         0 my $validity = 0;
248 0 0 0     0 if ( defined $self->{'policy'}->{'definition'}->{'no_limitation'}
249             && $self->{'policy'}->{'definition'}->{'no_limitation'} == 1 )
250             {
251 0         0 $log->trace( 'is_valid_policy', 'no_limitation: no policy check!' );
252 0         0 $validity = 1;
253             }
254             else {
255 0         0 my $templates = $self->{'policy'}->{ $params{'policy'} };
256 0         0 assert_listref( $templates, "templates is a reference to a list" );
257 0         0 foreach ( @{$templates} ) {
  0         0  
258 0         0 $log->tracef( 'is_valid_policy(): Going to comparing with raw template:\"%s\".)', $_ );
259 0         0 my $template = $self->_convert_policy_to_normal_regexp($_);
260 0         0 $log->tracef( 'is_valid_policy(): Comparing with template:\"%s\".)', $template );
261 0 0       0 if ( $params{'candidate'} =~ /^$template$/xgsm ) {
262 0         0 $validity = 1;
263 0         0 last;
264             }
265             }
266             }
267 0         0 $log->tracef( 'Exiting is_valid_policy():%d', $validity );
268 0         0 return $validity;
269             }
270              
271             # This method throws an exception of type IronPolicyException.
272              
273             sub validate_with_policy {
274 9     9 1 23 my $self = shift;
275 9         205 my %params = validate(
276             @_,
277             {
278             'policy' => { type => SCALAR, }, # name/key name.
279             'candidate' => { type => SCALAR, }, # string to check.
280             }
281             );
282 9         81 assert_hashref( $self->{'policy'}, 'self->{policy} is a reference to a hash.' );
283 9         109 $log->tracef( 'Entering validate_with_policy(%s)', \%params );
284 9         2550 my $validity = 0;
285 9         28 my $templates = $self->{'policy'}->{ $params{'policy'} };
286 9         40 assert_listref( $templates, 'templates is a reference to a list' );
287 9         82 foreach ( @{$templates} ) {
  9         45  
288 22         95 my $template = $self->_convert_policy_to_normal_regexp($_);
289 22         71 $log->tracef( 'validate_with_policy(): Comparing with template:\"%s\".)', $template );
290 22 100       2113 if ( $params{'candidate'} =~ /^$template$/xgsm ) {
291 4         15 $validity = 1;
292 4         12 last;
293             }
294             }
295 9 100       35 if ( $validity == 0 ) {
296             $log->tracef( 'Throwing exception in validate_with_policy(): policy=%s, candidate=%s',
297 5         35 $params{'policy'}, $params{'candidate'} );
298             IronPolicyException->throw(
299             policy => $params{'policy'},
300             candidate => $params{'candidate'},
301 5         424 error => 'IronPolicyException: policy=' . $params{'policy'} . ' candidate=' . $params{'candidate'},
302             );
303             }
304 4         20 $log->tracef( 'Exiting validate_with_policy():%d', $validity );
305 4         347 return $validity;
306             }
307              
308             sub get_policies { ## no critic (Subroutines::RequireArgUnpacking)
309 2     2 1 14 my $self = shift;
310 2         39 my %params = validate(
311             @_,
312             {
313             'policies' => { type => SCALAR | UNDEF, optional => 0, },
314             }
315             );
316 2         19 $log->tracef( 'Entering get_policies(%s)', \%params );
317 2         461 my %all_policies = IRON_CLIENT_DEFAULT_POLICIES(); ## Preset default policies.
318 2         10 $log->tracef( 'Default policies: %s', \%all_policies );
319 2 50       626 if ( defined $params{'policies'} ) { # policies file specified when creating the object, if given.
320             IO::Iron::Common::_read_iron_config_file( \%all_policies,
321             File::Spec->file_name_is_absolute( $params{'policies'} )
322             ? $params{'policies'}
323 0 0       0 : File::Spec->catfile( File::Spec->curdir(), $params{'policies'} ) );
324             }
325 2         7 my %policies = %{ $all_policies{ $self->_THIS_POLICY() } };
  2         29  
326 2         7 $policies{'definition'} = $all_policies{'definition'};
327 2         11 $log->tracef( 'Exiting get_policies: %s', \%policies );
328 2         517 return \%policies;
329             }
330              
331             1;
332              
333             __END__
334              
335             =pod
336              
337             =encoding UTF-8
338              
339             =head1 NAME
340              
341             IO::Iron::PolicyBase - Base package (inherited) for IO::Iron::IronMQ/Cache/Worker::Policy packages.
342              
343             =head1 VERSION
344              
345             version 0.14
346              
347             =head1 SYNOPSIS
348              
349             This class is for internal use only.
350              
351             =for stopwords Iron.io Params params API Mikko Koivunalho TODO
352              
353             =for stopwords NoIronPolicyException IronPolicyException
354              
355             =head1 METHODS
356              
357             =head2 IRON_CLIENT_DEFAULT_POLICIES
358              
359             Default policies for all clients.
360             These policies allow everything.
361              
362             =head2 alternatives
363              
364             Return all possible alternatives.
365              
366             Parameters:
367              
368             =over 8
369              
370             =item required_policy, name/key name
371              
372             =back
373              
374             Return: List of possible alternatives if validation is successful.
375             If the policy is not set, throws a NoIronPolicyException.
376              
377             =head2 is_valid_policy
378              
379             Is this policy valid?
380              
381             Parameters:
382              
383             =over 8
384              
385             =item policy, name/key name.
386              
387             =item candidate, proposed string.
388              
389             =back
390              
391             Return: Boolean.
392              
393             =head2 validate_with_policy
394              
395             Validate a candidate string. Same as method is_valid_policy() but this method throws an exception of type IronPolicyException if the validation fails.
396              
397             Parameters:
398              
399             =over 8
400              
401             =item policy, name/key name.
402              
403             =item candidate, proposed string.
404              
405             =back
406              
407             Return: Boolean True if validation is successful, otherwise throws an exception.
408              
409             =head2 get_policies
410              
411             Get the policies from file or use the defaults. This function is for internal use.
412              
413             The configuration is constructed as follows:
414              
415             =over 8
416              
417             =item 1. The global defaults.
418              
419             =item 5. The policies file specified when instantiating the client library overwrites everything before it according to the file hierarchy.
420              
421             =item 6. Return only the policies connected to this client (specify in derived class with method _THIS_POLICY).
422              
423             =back
424              
425             Return: ref to policies.
426              
427             =head1 AUTHOR
428              
429             Mikko Koivunalho <mikko.koivunalho@iki.fi>
430              
431             =head1 BUGS
432              
433             Please report any bugs or feature requests to bug-io-iron@rt.cpan.org or through the web interface at:
434             http://rt.cpan.org/Public/Dist/Display.html?Name=IO-Iron
435              
436             =head1 COPYRIGHT AND LICENSE
437              
438             This software is copyright (c) 2023 by Mikko Koivunalho.
439              
440             This is free software; you can redistribute it and/or modify it under
441             the same terms as the Perl 5 programming language system itself.
442              
443             The full text of the license can be found in the
444             F<LICENSE> file included with this distribution.
445              
446             =cut