File Coverage

blib/lib/Data/Rx.pm
Criterion Covered Total %
statement 67 72 93.0
branch 23 32 71.8
condition 2 4 50.0
subroutine 12 13 92.3
pod 6 8 75.0
total 110 129 85.2


line stmt bran cond sub pod time code
1 1     1   83466 use strict;
  1         3  
  1         40  
2 1     1   6 use warnings;
  1         2  
  1         57  
3             package Data::Rx;
4             # ABSTRACT: perl implementation of Rx schema system
5             $Data::Rx::VERSION = '0.200006';
6 1     1   574 use Data::Rx::Util;
  1         17  
  1         32  
7 1     1   699 use Data::Rx::TypeBundle::Core;
  1         3  
  1         1179  
8              
9             #pod =head1 SYNOPSIS
10             #pod
11             #pod my $rx = Data::Rx->new;
12             #pod
13             #pod my $success = {
14             #pod type => '//rec',
15             #pod required => {
16             #pod location => '//str',
17             #pod status => { type => '//int', value => 201 },
18             #pod },
19             #pod optional => {
20             #pod comments => {
21             #pod type => '//arr',
22             #pod contents => '//str',
23             #pod },
24             #pod },
25             #pod };
26             #pod
27             #pod my $schema = $rx->make_schema($success);
28             #pod
29             #pod my $reply = $json->decode( $agent->get($http_request) );
30             #pod
31             #pod die "invalid reply" unless $schema->check($reply);
32             #pod
33             #pod =head1 COMPLEX CHECKS
34             #pod
35             #pod Note that a "schema" can be represented either as a name or as a definition.
36             #pod In the L above, note that we have both, '//str' and
37             #pod C<{ type =E '//int', value =E 201 }>.
38             #pod With the L
39             #pod provided by Rx, you can validate many complex structures. See L
40             #pod for how to teach your Rx schema object about the new types you create.
41             #pod
42             #pod When required, see L for details on creating a
43             #pod custom type plugin as a Perl module.
44             #pod
45             #pod =head1 SEE ALSO
46             #pod
47             #pod L
48             #pod
49             #pod =cut
50              
51             sub _expand_uri {
52 119     119   316 my ($self, $str) = @_;
53 119 100       356 return $str if $str =~ /\A\w+:/;
54              
55 112 50       603 if ($str =~ m{\A/(.*?)/(.+)\z}) {
56 112         425 my ($prefix, $rest) = ($1, $2);
57            
58 112         313 my $lookup = $self->{prefix};
59 112 50       422 Carp::croak "unknown prefix '$prefix' in type name '$str'"
60             unless exists $lookup->{$prefix};
61              
62 112         392 return "$lookup->{$prefix}$rest";
63             }
64              
65 0         0 Carp::croak "couldn't understand Rx type name '$str'";
66             }
67              
68             #pod =method new
69             #pod
70             #pod my $rx = Data::Rx->new(\%arg);
71             #pod
72             #pod This returns a new Data::Rx object.
73             #pod
74             #pod Valid arguments are:
75             #pod
76             #pod prefix - optional; a hashref of prefix pairs for type shorthand
77             #pod type_plugins - optional; an arrayref of type or type bundle plugins
78             #pod no_core_types - optional; if true, core type bundle is not loaded
79             #pod sort_keys - optional; see the sort_keys section.
80             #pod
81             #pod The prefix hashref should look something like this:
82             #pod
83             #pod {
84             #pod 'pobox' => 'tag:pobox.com,1995:rx/core/',
85             #pod 'skynet' => 'tag:skynet.mil,1997-08-29:types/rx/',
86             #pod }
87             #pod
88             #pod =cut
89              
90             sub new {
91 54     54 1 16834 my ($class, $arg) = @_;
92 54   50     168 $arg ||= {};
93 54   50     336 $arg->{prefix} ||= {};
94              
95 54 50       79 my @plugins = @{ $arg->{type_plugins} || [] };
  54         330  
96 54 50       327 unshift @plugins, $class->core_bundle unless $arg->{no_core_bundle};
97              
98 54         296 my $self = {
99             prefix => { },
100             handler => { },
101             sort_keys => !!$arg->{sort_keys},
102             };
103              
104 54         178 bless $self => $class;
105              
106 54         247 $self->register_type_plugin($_) for @plugins;
107              
108 54         188 $self->add_prefix($_ => $arg->{prefix}{ $_ }) for keys %{ $arg->{prefix} };
  54         218  
109              
110 54         216 return $self;
111             }
112              
113             #pod =method make_schema
114             #pod
115             #pod my $schema = $rx->make_schema($schema);
116             #pod
117             #pod This returns a new schema checker method for the given Rx input. This object
118             #pod will have C and C methods to test data with.
119             #pod
120             #pod =cut
121              
122             sub make_schema {
123 121     121 1 631 my ($self, $schema) = @_;
124              
125 121 100       327 $schema = { type => "$schema" } unless ref $schema;
126              
127 121 100       600 Carp::croak("no type name given") unless my $type = $schema->{type};
128              
129 119         265 my $type_uri = $self->_expand_uri($type);
130 119 100       391 die "unknown type uri: $type_uri" unless exists $self->{handler}{$type_uri};
131              
132 117         247 my $handler = $self->{handler}{$type_uri};
133              
134 117         514 my $schema_arg = {%$schema};
135 117         276 delete $schema_arg->{type};
136              
137 117         156 my $checker;
138              
139 117 100       198 if (ref $handler) {
140 2 50       8 if (keys %$schema_arg) {
141 0         0 Carp::croak("composed type does not take check arguments");
142             }
143 2         7 $checker = $self->make_schema($handler->{'schema'});
144             } else {
145 115         621 $checker = $handler->new_checker($schema_arg, $self, $type);
146             }
147              
148 103         525 return $checker;
149             }
150              
151             #pod =method register_type_plugin
152             #pod
153             #pod $rx->register_type_plugin($type_or_bundle);
154             #pod
155             #pod Given a type plugin, this registers the plugin with the Data::Rx object.
156             #pod Bundles are expanded recursively and all their plugins are registered.
157             #pod
158             #pod Type plugins must have a C method and a C method.
159             #pod See L for details.
160             #pod
161             #pod =cut
162              
163             sub register_type_plugin {
164 54     54 1 96 my ($self, $starting_plugin) = @_;
165              
166 54         332 my @plugins = ($starting_plugin);
167 54         193 PLUGIN: while (my $plugin = shift @plugins) {
168 810 100       6702 if ($plugin->isa('Data::Rx::TypeBundle')) {
169 54         263 my %pairs = $plugin->prefix_pairs;
170 54         318 $self->add_prefix($_ => $pairs{ $_ }) for keys %pairs;
171              
172 54         232 unshift @plugins, $plugin->type_plugins;
173             } else {
174 756         2803 my $uri = $plugin->type_uri;
175              
176 756 50       2224 Carp::confess("a type plugin is already registered for $uri")
177             if $self->{handler}{ $uri };
178            
179 756         4065 $self->{handler}{ $uri } = $plugin;
180             }
181             }
182             }
183              
184             #pod =method learn_type
185             #pod
186             #pod $rx->learn_type($uri, $schema);
187             #pod
188             #pod This defines a new type as a schema composed of other types.
189             #pod
190             #pod For example:
191             #pod
192             #pod $rx->learn_type('tag:www.example.com:rx/person',
193             #pod { type => '//rec',
194             #pod required => {
195             #pod firstname => '//str',
196             #pod lastname => '//str',
197             #pod },
198             #pod optional => {
199             #pod middlename => '//str',
200             #pod },
201             #pod },
202             #pod );
203             #pod
204             #pod =cut
205              
206             sub learn_type {
207 4     4 1 44 my ($self, $uri, $schema) = @_;
208              
209 4 50       19 Carp::confess("a type handler is already registered for $uri")
210             if $self->{handler}{ $uri };
211              
212             die "invalid schema for '$uri': $@"
213 4 100       6 unless eval { $self->make_schema($schema) };
  4         12  
214              
215 2         29 $self->{handler}{ $uri } = { schema => $schema };
216             }
217              
218             #pod =method add_prefix
219             #pod
220             #pod $rx->add_prefix($name => $prefix_string);
221             #pod
222             #pod For example:
223             #pod
224             #pod $rx->add_prefix('.meta' => 'tag:codesimply.com,2008:rx/meta/');
225             #pod
226             #pod =cut
227              
228             sub add_prefix {
229 109     109 1 194 my ($self, $name, $base) = @_;
230              
231 109 50       280 Carp::confess("the prefix $name is already registered")
232             if $self->{prefix}{ $name };
233              
234 109         420 $self->{prefix}{ $name } = $base;
235             }
236              
237             #pod =method sort_keys
238             #pod
239             #pod $rx->sort_keys(1);
240             #pod
241             #pod When sort_keys is enabled, causes Rx checkers for //rec and //map to
242             #pod sort the keys before validating. This results in failures being
243             #pod produced in a consistent order.
244             #pod
245             #pod =cut
246              
247             sub sort_keys {
248 80     80 1 123 my $self = shift;
249              
250 80 50       296 $self->{sort_keys} = !!$_[0] if @_;
251              
252 80         656 return $self->{sort_keys};
253             }
254              
255             sub core_bundle {
256 54     54 0 173 return 'Data::Rx::TypeBundle::Core';
257             }
258              
259             sub core_type_plugins {
260 0     0 0   my ($self) = @_;
261              
262 0           Carp::cluck("core_type_plugins deprecated; use Data::Rx::TypeBundle::Core");
263              
264 0           Data::Rx::TypeBundle::Core->type_plugins;
265             }
266              
267             1;
268              
269             __END__