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   44741 use strict;
  1         2  
  1         52  
2 1     1   24 use warnings;
  1         1  
  1         36  
3             package Data::Rx;
4             # ABSTRACT: perl implementation of Rx schema system
5             $Data::Rx::VERSION = '0.200007';
6 1     1   446 use Data::Rx::Util;
  1         11  
  1         23  
7 1     1   364 use Data::Rx::TypeBundle::Core;
  1         1  
  1         904  
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 SCHEMA METHODS
46             #pod
47             #pod The objects returned by C should provide the methods detailed in
48             #pod this section.
49             #pod
50             #pod =head2 check
51             #pod
52             #pod my $ok = $schema->check($input);
53             #pod
54             #pod This method just returns true if the input is valid under the given schema, and
55             #pod false otherwise. For more information, see C.
56             #pod
57             #pod =head2 assert_valid
58             #pod
59             #pod $schema->assert_valid($input);
60             #pod
61             #pod This method will throw an exception if the input is not valid under the schema.
62             #pod The exception will be a L. This has two important
63             #pod methods: C and C. The first provides a string form of the
64             #pod failure. C returns a list of L objects.
65             #pod
66             #pod Failure objects have a few methods of note:
67             #pod
68             #pod error_string - a human-friendly description of what went wrong
69             #pod stringify - a stringification of the error, data, and check string
70             #pod error_types - a list of types for the error; like tags
71             #pod
72             #pod data_string - a string describing where in the input the error occured
73             #pod value - the value found at the data path
74             #pod
75             #pod check_string - a string describing which part of the schema found the error
76             #pod
77             #pod =head1 SEE ALSO
78             #pod
79             #pod L
80             #pod
81             #pod =cut
82              
83             sub _expand_uri {
84 119     119   136 my ($self, $str) = @_;
85 119 100       425 return $str if $str =~ /\A\w+:/;
86              
87 112 50       466 if ($str =~ m{\A/(.*?)/(.+)\z}) {
88 112         243 my ($prefix, $rest) = ($1, $2);
89            
90 112         139 my $lookup = $self->{prefix};
91 112 50       198 Carp::croak "unknown prefix '$prefix' in type name '$str'"
92             unless exists $lookup->{$prefix};
93              
94 112         354 return "$lookup->{$prefix}$rest";
95             }
96              
97 0         0 Carp::croak "couldn't understand Rx type name '$str'";
98             }
99              
100             #pod =method new
101             #pod
102             #pod my $rx = Data::Rx->new(\%arg);
103             #pod
104             #pod This returns a new Data::Rx object.
105             #pod
106             #pod Valid arguments are:
107             #pod
108             #pod prefix - optional; a hashref of prefix pairs for type shorthand
109             #pod type_plugins - optional; an arrayref of type or type bundle plugins
110             #pod no_core_types - optional; if true, core type bundle is not loaded
111             #pod sort_keys - optional; see the sort_keys section.
112             #pod
113             #pod The prefix hashref should look something like this:
114             #pod
115             #pod {
116             #pod 'pobox' => 'tag:pobox.com,1995:rx/core/',
117             #pod 'skynet' => 'tag:skynet.mil,1997-08-29:types/rx/',
118             #pod }
119             #pod
120             #pod =cut
121              
122             sub new {
123 54     54 1 8552 my ($class, $arg) = @_;
124 54   50     127 $arg ||= {};
125 54   50     228 $arg->{prefix} ||= {};
126              
127 54 50       51 my @plugins = @{ $arg->{type_plugins} || [] };
  54         235  
128 54 50       206 unshift @plugins, $class->core_bundle unless $arg->{no_core_bundle};
129              
130 54         208 my $self = {
131             prefix => { },
132             handler => { },
133             sort_keys => !!$arg->{sort_keys},
134             };
135              
136 54         115 bless $self => $class;
137              
138 54         173 $self->register_type_plugin($_) for @plugins;
139              
140 54         70 $self->add_prefix($_ => $arg->{prefix}{ $_ }) for keys %{ $arg->{prefix} };
  54         171  
141              
142 54         144 return $self;
143             }
144              
145             #pod =method make_schema
146             #pod
147             #pod my $schema = $rx->make_schema($schema);
148             #pod
149             #pod This returns a new schema checker method for the given Rx input. This object
150             #pod will have C and C methods to test data with.
151             #pod
152             #pod =cut
153              
154             sub make_schema {
155 121     121 1 414 my ($self, $schema) = @_;
156              
157 121 100       238 $schema = { type => "$schema" } unless ref $schema;
158              
159 121 100       364 Carp::croak("no type name given") unless my $type = $schema->{type};
160              
161 119         178 my $type_uri = $self->_expand_uri($type);
162 119 100       300 die "unknown type uri: $type_uri" unless exists $self->{handler}{$type_uri};
163              
164 117         192 my $handler = $self->{handler}{$type_uri};
165              
166 117         295 my $schema_arg = {%$schema};
167 117         209 delete $schema_arg->{type};
168              
169 117         77 my $checker;
170              
171 117 100       200 if (ref $handler) {
172 2 50       5 if (keys %$schema_arg) {
173 0         0 Carp::croak("composed type does not take check arguments");
174             }
175 2         4 $checker = $self->make_schema($handler->{'schema'});
176             } else {
177 115         420 $checker = $handler->new_checker($schema_arg, $self, $type);
178             }
179              
180 103         361 return $checker;
181             }
182              
183             #pod =method register_type_plugin
184             #pod
185             #pod $rx->register_type_plugin($type_or_bundle);
186             #pod
187             #pod Given a type plugin, this registers the plugin with the Data::Rx object.
188             #pod Bundles are expanded recursively and all their plugins are registered.
189             #pod
190             #pod Type plugins must have a C method and a C method.
191             #pod See L for details.
192             #pod
193             #pod =cut
194              
195             sub register_type_plugin {
196 54     54 1 98 my ($self, $starting_plugin) = @_;
197              
198 54         85 my @plugins = ($starting_plugin);
199 54         153 PLUGIN: while (my $plugin = shift @plugins) {
200 810 100       3246 if ($plugin->isa('Data::Rx::TypeBundle')) {
201 54         190 my %pairs = $plugin->prefix_pairs;
202 54         231 $self->add_prefix($_ => $pairs{ $_ }) for keys %pairs;
203              
204 54         248 unshift @plugins, $plugin->type_plugins;
205             } else {
206 756         1320 my $uri = $plugin->type_uri;
207              
208 756 50       1339 Carp::confess("a type plugin is already registered for $uri")
209             if $self->{handler}{ $uri };
210            
211 756         1945 $self->{handler}{ $uri } = $plugin;
212             }
213             }
214             }
215              
216             #pod =method learn_type
217             #pod
218             #pod $rx->learn_type($uri, $schema);
219             #pod
220             #pod This defines a new type as a schema composed of other types.
221             #pod
222             #pod For example:
223             #pod
224             #pod $rx->learn_type('tag:www.example.com:rx/person',
225             #pod { type => '//rec',
226             #pod required => {
227             #pod firstname => '//str',
228             #pod lastname => '//str',
229             #pod },
230             #pod optional => {
231             #pod middlename => '//str',
232             #pod },
233             #pod },
234             #pod );
235             #pod
236             #pod =cut
237              
238             sub learn_type {
239 4     4 1 36 my ($self, $uri, $schema) = @_;
240              
241 4 50       13 Carp::confess("a type handler is already registered for $uri")
242             if $self->{handler}{ $uri };
243              
244             die "invalid schema for '$uri': $@"
245 4 100       5 unless eval { $self->make_schema($schema) };
  4         12  
246              
247 2         21 $self->{handler}{ $uri } = { schema => $schema };
248             }
249              
250             #pod =method add_prefix
251             #pod
252             #pod $rx->add_prefix($name => $prefix_string);
253             #pod
254             #pod For example:
255             #pod
256             #pod $rx->add_prefix('.meta' => 'tag:codesimply.com,2008:rx/meta/');
257             #pod
258             #pod =cut
259              
260             sub add_prefix {
261 109     109 1 112 my ($self, $name, $base) = @_;
262              
263 109 50       233 Carp::confess("the prefix $name is already registered")
264             if $self->{prefix}{ $name };
265              
266 109         290 $self->{prefix}{ $name } = $base;
267             }
268              
269             #pod =method sort_keys
270             #pod
271             #pod $rx->sort_keys(1);
272             #pod
273             #pod When sort_keys is enabled, causes Rx checkers for //rec and //map to
274             #pod sort the keys before validating. This results in failures being
275             #pod produced in a consistent order.
276             #pod
277             #pod =cut
278              
279             sub sort_keys {
280 80     80 1 91 my $self = shift;
281              
282 80 50       120 $self->{sort_keys} = !!$_[0] if @_;
283              
284 80         328 return $self->{sort_keys};
285             }
286              
287             sub core_bundle {
288 54     54 0 138 return 'Data::Rx::TypeBundle::Core';
289             }
290              
291             sub core_type_plugins {
292 0     0 0   my ($self) = @_;
293              
294 0           Carp::cluck("core_type_plugins deprecated; use Data::Rx::TypeBundle::Core");
295              
296 0           Data::Rx::TypeBundle::Core->type_plugins;
297             }
298              
299             1;
300              
301             __END__