File Coverage

blib/lib/JSON/Schema.pm
Criterion Covered Total %
statement 43 88 48.8
branch 2 32 6.2
condition 2 8 25.0
subroutine 15 17 88.2
pod 6 6 100.0
total 68 151 45.0


line stmt bran cond sub pod time code
1             package JSON::Schema;
2              
3 9     9   209811 use 5.010;
  9         33  
4 9     9   44 use strict;
  9         16  
  9         196  
5              
6 9     9   41 use Carp;
  9         21  
  9         780  
7 9     9   7084 use HTTP::Link::Parser qw[parse_links_to_rdfjson relationship_uri];
  9         219916  
  9         645  
8 9     9   10048 use JSON;
  9         117304  
  9         42  
9 9     9   7985 use JSON::Hyper;
  9         541484  
  9         365  
10 9     9   5280 use JSON::Schema::Error;
  9         21  
  9         260  
11 9     9   5237 use JSON::Schema::Helper;
  9         32  
  9         317  
12 9     9   4812 use JSON::Schema::Result;
  9         38  
  9         254  
13 9     9   44 use LWP::UserAgent;
  9         15  
  9         3842  
14              
15             our $AUTHORITY = 'cpan:TOBYINK';
16             our $VERSION = '0.016';
17             our %FORMATS;
18              
19             BEGIN {
20             %FORMATS = (
21             'date-time' => qr/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/i,
22             'date' => qr/^\d{4}-\d{2}-\d{2}$/i,
23             'time' => qr/^\d{2}:\d{2}:\d{2}Z?$/i,
24             'utc-millisec' => qr/^[+-]\d+(\.\d+)?$/,
25             'email' => qr/\@/,
26             'ip-address' => sub
27             {
28 0 0       0 if (my @nums = ($_[0] =~ /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/))
29             {
30 0 0       0 return 1 unless grep {$_ > 255} @nums;
  0         0  
31             }
32 0         0 return;
33             },
34             'color' => sub
35             {
36 0 0       0 return 1 if $_[0] =~ /^\#[0-9A-F]{6}$/i;
37 0 0       0 return 1 if $_[0] =~ /^\#[0-9A-F]{3}$/i;
38 0 0       0 return 1 if $_[0] =~ /^(aqua|black|blue|fuchsia|gray|grey|green|lime|maroon|navy|olive|orange|purple|red|silver|teal|white|yellow)$/i;
39 0         0 return;
40             },
41 9     9   7520 );
42             }
43              
44             sub new
45             {
46 15     15 1 881 my ($class, $schema, %options) = @_;
47            
48 15 50       55 $schema = from_json($schema) unless ref $schema;
49 15   100     91 $options{format} //= {};
50            
51 15         80 return bless { %options, schema => $schema }, $class;
52             }
53              
54             sub detect
55             {
56 0     0 1 0 my ($class, $source) = @_;
57            
58 0         0 my $hyper = JSON::Hyper->new;
59 0         0 my ($object, $url);
60            
61 0 0       0 if ($source->isa('HTTP::Response'))
62             {
63 0         0 $url = $source->request->uri;
64 0         0 $object = from_json($source->decoded_content);
65             }
66             else
67             {
68 0         0 $url = "$source";
69 0         0 ($source, my $frag) = split /\#/, $source, 2;
70 0         0 ($object, $source) = $hyper->_get($source);
71 0         0 $object = fragment_resolve($object, $frag);
72             }
73            
74             # Link: <>; rel="describedby"
75 0         0 my $links = parse_links_to_rdfjson($source);
76             my @schema =
77 0         0 map { $class->new( $hyper->get($_->{value}) ) }
78 0         0 grep { lc $_->{type} eq 'uri' }
79 0         0 $links->{$url}{relationship_uri('describedby')};
80            
81             # ;profile=
82             push @schema,
83 0         0 map { $class->new( $hyper->get($_) ) }
84 0 0       0 map { if (/^\'/) { s/(^\')|(\'$)//g } elsif (/^\"/) { s/(^\")|(\"$)//g } else { $_ } }
  0 0       0  
  0         0  
  0         0  
85 0         0 map { s/^profile=// }
  0         0  
86             grep /^profile=/, $source->content_type;
87            
88             # $schema links
89 0 0       0 if ($object)
90             {
91             push @schema,
92 0         0 map { $class->new( $hyper->get($_->{href}) ) }
93 0 0       0 grep { lc $_->{rel} eq 'describedby' or lc $_->{rel} eq relationship_uri('describedby') }
  0         0  
94             $hyper->find_links($object);
95             }
96 0         0 return @schema;
97             }
98              
99             sub schema
100             {
101 55     55 1 66 my ($self) = @_;
102 55         209 return $self->{schema};
103             }
104              
105             sub format
106             {
107 55     55 1 79 my ($self) = @_;
108 55         281 return $self->{format};
109             }
110              
111             sub validate
112             {
113 55     55 1 5024 my ($self, $object) = @_;
114 55 50       154 $object = from_json($object) unless ref $object;
115            
116 55         141 my $helper = JSON::Schema::Helper->new(format => $self->format);
117 55         138 my $result = $helper->validate($object, $self->schema);
118 55         210 return JSON::Schema::Result->new($result);
119             }
120              
121             sub ua
122             {
123 0     0 1   my $self = shift;
124 0 0         $self = {} unless blessed($self);
125            
126 0 0         if (@_)
127             {
128 0           my $rv = $self->{ua};
129 0           $self->{ua} = shift;
130             croak "Set UA to something that is not an LWP::UserAgent!"
131 0 0 0       unless blessed $self->{ua} && $self->{ua}->isa('LWP::UserAgent');
132 0           return $rv;
133             }
134 0 0 0       unless (blessed $self->{ua} && $self->{ua}->isa('LWP::UserAgent'))
135             {
136 0           $self->{ua} = LWP::UserAgent->new(agent => sprintf('%s/%s ', __PACKAGE__, __PACKAGE__->VERSION));
137 0           $self->{ua}->default_header(Accept => 'application/json, application/schema+json');
138             }
139 0           return $self->{ua};
140             }
141              
142             1;
143              
144             __END__
145              
146             =head1 NAME
147              
148             JSON::Schema - validate JSON against a schema
149              
150             =head1 SYNOPSIS
151              
152             my $validator = JSON::Schema->new($schema, %options);
153             my $json = from_json( ... );
154             my $result = $validator->validate($json);
155            
156             if ($result)
157             {
158             print "Valid!\n";
159             }
160             else
161             {
162             print "Errors\n";
163             print " - $_\n" foreach $result->errors;
164             }
165              
166             =head1 STATUS
167              
168             This module offers good support for JSON Schema as described by draft
169             specifications circa 2012.
170              
171             However, since then the JSON Schema draft specifications have changed
172             significantly. It is planned for this module to be updated to support
173             the changes, however this work will not be undertaken until the JSON
174             Schema specifications become more stable. (Being published as an IETF
175             RFC will be seen as sufficient stability.)
176              
177             =head1 DESCRIPTION
178              
179             =head2 Constructors
180              
181             =over 4
182              
183             =item C<< JSON::Schema->new($schema, %options) >>
184              
185             Given a JSON (or equivalent Perl nested hashref/arrayref structure)
186             Schema, returns a Perl object capable of checking objects against
187             that schema.
188              
189             Note that some schemas contain '$ref' properties which act as
190             inclusions; this module does not expand those, but the L<JSON::Hyper>
191             module can.
192              
193             The only option currently supported is 'format' which takes a
194             hashref of formats (section 5.23 of the current JSON Schema draft)
195             such that the keys are the names of the formats, and the values are
196             regular expressions or callback functions. %JSON::Schema::FORMATS
197             provides a library of useful format checkers, but by default no
198             format checkers are used.
199              
200             my $s = JSON::Schema->new($schema,
201             format => \%JSON::Schema::FORMATS);
202              
203             =item C<< JSON::Schema->detect($url) >>
204              
205             Given the URL for a JSON instance (or an HTTP::Response object)
206             returns a list of schemas (as JSON::Schema objects) that the
207             JSON instance claims to conform to, detected from the HTTP
208             response headers.
209              
210             =back
211              
212             =head2 Methods
213              
214             =over 4
215              
216             =item C<< validate($object) >>
217              
218             Validates the object against the schema and returns a
219             L<JSON::Schema::Result>.
220              
221             =item C<< schema >>
222              
223             Returns the original schema as a hashref/arrayref structure.
224              
225             =item C<< format >>
226              
227             Returns the hashref of format checkers.
228              
229             =item C<< ua >>
230              
231             Returns the LWP::UserAgent that this schema object has used or would
232             use to retrieve content from the web.
233              
234             =back
235              
236             =head2 Perl Specifics
237              
238             Perl uses weak typing. This module largely gives JSON instances
239             the benefit of the doubt. For example, if something looks like a
240             number (e.g. a string which only includes the digits 0 to 9)
241             then it will validate against schemas that require a number.
242              
243             The module extends JSON Schema's native set of types ('string',
244             'number', 'integer', 'boolean', 'array', 'object', 'null', 'any')
245             with any Perl package name. i.e. the following is valid:
246              
247             my $validator = JSON::Schema->new({
248             properties => {
249             'time' => { type => ['DateTime','string'] },
250             },
251             });
252             my $object = {
253             'time' => DateTime->now;
254             };
255             my $result = $schema->validate($object);
256              
257             This extension makes JSON::Schema useful not just for validating
258             JSON structures, but acyclic Perl structures generally.
259              
260             Acyclic. Yes, acyclic. You don't want an infinite loop.
261              
262             =head1 BUGS
263              
264             Please report any bugs to L<http://rt.cpan.org/>.
265              
266             =head1 SEE ALSO
267              
268             L<JSON::Schema::Result>,
269             L<JSON::Schema::Error>,
270             L<JSON::Schema::Helper>,
271             L<JSON::Schema::Null>,
272             L<JSON::Schema::Examples>.
273              
274             Related modules:
275             L<JSON::T>,
276             L<JSON::Path>,
277             L<JSON::GRDDL>,
278             L<JSON::Hyper>.
279              
280             L<http://tools.ietf.org/html/draft-zyp-json-schema>.
281              
282             =head1 AUTHOR
283              
284             Toby Inkster E<lt>tobyink@cpan.orgE<gt>.
285              
286             This is largely a port of Kris Zyp's Javascript JSON Schema validator
287             L<http://code.google.com/p/jsonschema/>.
288              
289             =head1 COPYRIGHT AND LICENCE
290              
291             Copyright 2007-2009 Kris Zyp.
292              
293             Copyright 2010-2012 Toby Inkster.
294              
295             This module is tri-licensed. It is available under the X11 (a.k.a. MIT)
296             licence; you can also redistribute it and/or modify it under the same
297             terms as Perl itself.
298              
299             =head2 a.k.a. "The MIT Licence"
300              
301             Permission is hereby granted, free of charge, to any person obtaining a copy
302             of this software and associated documentation files (the "Software"), to deal
303             in the Software without restriction, including without limitation the rights
304             to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
305             copies of the Software, and to permit persons to whom the Software is
306             furnished to do so, subject to the following conditions:
307              
308             The above copyright notice and this permission notice shall be included in
309             all copies or substantial portions of the Software.
310              
311             THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
312             IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
313             FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
314             AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
315             LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
316             OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
317             THE SOFTWARE.
318              
319             =cut