File Coverage

blib/lib/ZMQ/Declare/ZDCF/Validator.pm
Criterion Covered Total %
statement 4 6 66.6
branch n/a
condition n/a
subroutine 2 2 100.0
pod n/a
total 6 8 75.0


line stmt bran cond sub pod time code
1             package ZMQ::Declare::ZDCF::Validator;
2             {
3             $ZMQ::Declare::ZDCF::Validator::VERSION = '0.03';
4             }
5 1     1   21270 use 5.008001;
  1         4  
  1         37  
6 1     1   1986 use Moose;
  0            
  0            
7              
8             use Data::Rx;
9             use Clone ();
10              
11             # Scope for schema snippets
12             SCOPE: {
13             # The following spec snippets are shared between ZDCF 0.1 and ZDCF 1.0
14             my $context_schema = { # the top level context obj/hash
15             type => '//rec',
16             optional => { # can have these properties
17             iothreads => { type => '//int', range => {min => 1} },
18             verbose => '//bool',
19             },
20             };
21             my $option_schema = {
22             type => '//rec',
23             optional => {
24             "hwm" => { type => '//int' },
25             "swap" => { type => '//int' },
26             "affinity" => { type => '//int' },
27             "identity" => { type => '//str' },
28             "subscribe" => { type => '//str' },
29             "rate" => { type => '//int' },
30             "recovery_ivl" => { type => '//int' },
31             "mcast_loop" => { type => '//bool' },
32             "sndbuf" => { type => '//int' },
33             "rcvbuf" => { type => '//int' },
34             },
35             };
36             my $string_or_value_ary_schema = {
37             type => '//any',
38             of => [
39             { type => '//str' },
40             { type => '//arr', length => {min => 1}, contents => "//str" },
41             ]
42             };
43             my $socket_type_schema = {
44             type => '//any',
45             of => [
46             map {
47             { type => '//str', value => $_ },
48             { type => '//str', value => uc($_) }
49             } qw(sub pub req rep xreq xrep push pull pair router dealer)
50             ]
51             };
52             my $socket_schema = {
53             type => '//any',
54             of => [
55             {
56             type => '//rec',
57             required => {
58             type => $socket_type_schema,
59             bind => $string_or_value_ary_schema,
60             },
61             optional => {
62             connect => $string_or_value_ary_schema,
63             option => $option_schema,
64             },
65             },
66             {
67             type => '//rec',
68             required => {
69             type => $socket_type_schema,
70             connect => $string_or_value_ary_schema,
71             },
72             optional => {
73             bind => $string_or_value_ary_schema,
74             option => $option_schema,
75             },
76             }
77             ]
78             };
79              
80             # The following are versioned
81             # First for ZDCF 0.1
82             my $device_schema_0 = {
83             type => '//rec',
84             # device must have property called 'type'
85             required => { 'type' => {type => '//str'} },
86             rest => {type => '//map', values => $socket_schema}, # anything else is a socket (sigh)
87             };
88             my $base_zdcf_schema_0 = {
89             type => '//rec',
90             optional => {
91             context => $context_schema,
92             version => { type => '//num', range => {min => 0} },
93             },
94             rest => {type => '//map', values => $device_schema_0}, # anything but the context is a device
95             };
96              
97             # Now ZDCF 1.0
98             my $device_schema_1 = {
99             type => '//rec',
100             optional => {
101             # device CAN have property called 'type' (no longer required)
102             'type' => {type => '//str'},
103             'sockets' => {
104             type => '//map',
105             values => $socket_schema
106             },
107             },
108             };
109             my $app_schema_1 = {
110             type => '//rec',
111             optional => {
112             context => $context_schema,
113             devices => { type => '//map', values => $device_schema_1 },
114             },
115             };
116             my $base_zdcf_schema_1 = {
117             type => '//rec',
118             required => {
119             version => { type => '//num', range => {min => 0} },
120             },
121             optional => {
122             apps => { type => '//map', values => $app_schema_1 },
123             },
124             };
125              
126             # A single Rx object is enough
127             my $rx = Data::Rx->new;
128              
129             my %validator_schemata; # schema cache
130             sub _get_validator {
131             my $version = shift;
132              
133             # normalize version
134             my $major_version = int($version||0);
135              
136             if (not exists $validator_schemata{$major_version}) {
137             if ($major_version == 0) {
138             my $validator_schema = $rx->make_schema($base_zdcf_schema_0);
139             $validator_schemata{$major_version} = $validator_schema;
140             }
141             elsif ($major_version == 1) {
142             my $validator_schema = $rx->make_schema($base_zdcf_schema_1);
143             $validator_schemata{$major_version} = $validator_schema;
144             }
145             else {
146             die __PACKAGE__ . " does not support ZDCF specification version $version";
147             }
148             }
149              
150             return $validator_schemata{$major_version};
151             }
152             } # end SCOPE
153              
154             sub validate {
155             my ($self, $structure, $force_version) = @_;
156              
157             # Just extract the spec version so we use the right validation code
158             my $version = defined $force_version ? $force_version : $self->find_spec_version($structure);
159              
160             return _get_validator($version)->check($structure);
161             }
162              
163             sub upgrade_structure {
164             my ($self, $structure) = @_;
165             my $major_version = int( $self->find_spec_version($structure) );
166              
167             if ($major_version == 0 and keys %$structure) {
168             # introduce "apps", "devices", and "sockets" intermediate layers
169              
170             # add "apps" layer
171             my $app = {};
172             foreach my $key (keys %$structure) {
173             next if $key eq 'version';
174             $app->{$key} = delete $structure->{$key};
175             }
176             $structure->{apps} = {"" => $app};
177              
178             # add "devices" layer
179             my $devices = {};
180             foreach my $key (keys %$app) {
181             next if $key eq 'context';
182             my $device = $devices->{$key} = delete $app->{$key};
183             $devices->{$key} = $device;
184              
185             # add "sockets" layer
186             my $sockets = {};
187             foreach my $key (keys %$device) {
188             next if $key eq 'type';
189             $sockets->{$key} = delete $device->{$key};
190             }
191             $device->{sockets} = $sockets;
192              
193             } # end foreach key in application
194             $app->{devices} = $devices;
195              
196             $structure->{version} = 1.0;
197             } # end if have to upgrade from v0
198             }
199              
200             sub find_spec_version {
201             my ($self, $structure) = @_;
202              
203             return undef if not ref($structure) eq 'HASH';
204             my $spec_version = $structure->{version} || '0'; # 0 == pre-versioned spec
205             return $spec_version;
206             }
207              
208             sub validate_and_upgrade {
209             my ($self, $structure) = @_;
210             return undef if not $self->validate($structure);
211             my $copy = Clone::clone($structure);
212             $self->upgrade_structure($copy);
213             return $copy;
214             }
215              
216             no Moose;
217             __PACKAGE__->meta->make_immutable;
218              
219             __END__
220              
221             =head1 NAME
222              
223             ZMQ::Declare::ZDCF::Validator - ZDCF validator
224              
225             =head1 SYNOPSIS
226              
227             use ZMQ::Declare;
228             my $validator = ZMQ::Declare::ZDCF::Validator->new;
229             unless ($validator->validate($datastructure)) {
230             die "Input data structure is not ZDCF!"
231             }
232              
233             =head1 DESCRIPTION
234              
235             Validates that a given nested Perl data structure (arrays, hashes, scalars)
236             is actually a valid ZDCF tree.
237              
238             =head1 METHODS
239              
240             =head2 validate
241              
242             Returns true if the given Perl data structure is a valid ZDCF tree, false
243             otherwise.
244              
245             Dies if the specification version of the ZDCF tree is unsupported.
246              
247             The second parameter to this method can optionally be a major ZDCF
248             specification version to use for validation instead of auto-detection.
249              
250             =head2 validate_and_upgrade
251              
252             Validates the input ZDCF structure, then attempts to upgrade
253             it to the newest supported spec version. Returns a cloned copy
254             of the input structure on success or undef on failure.
255              
256             =head2 upgrade_structure
257              
258             Given a ZDCF structure, determines the specification version and
259             tries to upgrade it to the most recent supported version.
260              
261             Does not validate the input and works in-place.
262              
263             =head2 find_spec_version
264              
265             Returns the version of the provided specification.
266              
267             Returns undef on failure.
268              
269             =head1 SEE ALSO
270              
271             The ZDCF RFC L<http://rfc.zeromq.org/spec:17>
272              
273             L<Data::Rx>, L<http://rx.codesimply.com/index.html>
274              
275             L<ZeroMQ>
276              
277             =head1 AUTHOR
278              
279             Steffen Mueller E<lt>smueller@cpan.orgE<gt>
280              
281             =head1 COPYRIGHT AND LICENSE
282              
283             Copyright (C) 2012 by Steffen Mueller
284              
285             This library is free software; you can redistribute it and/or modify
286             it under the same terms as Perl itself, either Perl version 5.8.1 or,
287             at your option, any later version of Perl 5 you may have available.
288              
289             =cut