File Coverage

blib/lib/Data/JSONSchema/Ajv.pm
Criterion Covered Total %
statement 89 91 97.8
branch 27 30 90.0
condition 10 13 76.9
subroutine 15 16 93.7
pod 5 5 100.0
total 146 155 94.1


line stmt bran cond sub pod time code
1 3     3   152520 use strict;
  3         13  
  3         75  
2 3     3   15 use warnings;
  3         4  
  3         129  
3              
4 3     3   1254 use JavaScript::Duktape::XS;
  3         31708  
  3         124  
5              
6 3     3   2360 use Data::JSONSchema::Ajv::src;
  3         15  
  3         92  
7 3     3   1143 use Data::JSONSchema::Ajv::src::04;
  3         13  
  3         92  
8 3     3   1166 use Data::JSONSchema::Ajv::src::06;
  3         13  
  3         214  
9              
10             =head1 NAME
11              
12             Data::JSONSchema::Ajv - JSON Schema Validator wrapping Ajv
13              
14             =head1 VERSION
15              
16             version 0.09
17              
18             =head1 DESCRIPTION
19              
20             JSON Schema Validator wrapping Ajv
21              
22             =head1 SYNOPSIS
23              
24             use Test::More;
25             use Data::JSONSchema::Ajv;
26              
27             my $ajv_options = {};
28             my $my_options = {
29             draft => '04', # Defaults to '07'
30             };
31              
32             my $ajv = Data::JSONSchema::Ajv->new($ajv_options, $my_options);
33              
34             my $validator = $ajv->make_validator(
35             { # http://json-schema.org/examples.html
36             title => "Example Schema",
37             type => "object",
38             properties => {
39             firstName => { type => "string" },
40             lastName => { type => "string" },
41             age => {
42             description => "Age in years",
43             type => "integer",
44             minimum => 0
45             }
46             },
47             required => [ "firstName", "lastName" ],
48             }
49             );
50              
51             my $payload = { firstName => 'Valentina', familyName => 'Tereshkova' };
52              
53             my $result = $validator->validate($payload);
54              
55             if ($result) {
56             is_deeply(
57             $result,
58             [ { dataPath => "",
59             keyword => "required",
60             message => "should have required property 'lastName'",
61             params => { missingProperty => "lastName" },
62             schemaPath => "#/required"
63             }
64             ],
65             "Expected errors thrown"
66             );
67             } else {
68             fail(
69             "validate() returned a false value, which means the example " .
70             "unexpectedly validated"
71             );
72             }
73              
74             =head1 WHAT WHY
75              
76             This module is an offensively light-weight wrapper
77             L.
78              
79             Light-weight in this context just means it's not very many lines of actual Perl
80              
81             =head1 METHODS
82              
83             =head2 new
84              
85             my $ajv = Data::JSONSchema::Ajv->new(
86             { v5 => $JSON::PP::true }, # Ajv options. Try: {},
87             {}, # Module options. See below
88             );
89              
90             Instantiates a new L environment and loads C into it.
91             Accepts two hashrefs (or undefs). The first is passed straight through to
92             C, whose options are documented L.
93              
94             The second one allows you to specify options of this module:
95              
96             =over
97              
98             =item ajv_src
99              
100             String that contains source code of standalone version of Ajv library, which can be
101             found L. This module already has some version
102             (possibly not last) of Ajv hardcoded inside it and will use it if this option doesn't
103             specified.
104              
105             =item draft
106              
107             A JSON Schema draft version as a string. Allowable values are C<04>, C<06>, and C<07>.
108             No support for multiple schemas at this time. Default is C<07>.
109              
110             =back
111              
112             =head2 make_validator
113              
114             my $validator = $ajv->make_validator( $hashref_schema OR $json_string );
115              
116             Compiles your schema using C and return a
117             C object, documented immediately below.
118              
119             =head2 compile
120              
121             $ajv->compile( $hashref_schema OR $json_string );
122              
123             Same as C, but doesn't return validator object. You can get validator later
124             using C (see below).
125              
126             =head2 get_validator
127              
128             $ajv->get_validator($schema_path);
129              
130             You can get validator from schema previously compiled with C or C. You
131             can even get validator from the part of compiled schema:
132              
133             # You can compile many schemas in one ajv instance
134             $ajv->compile({
135             '$id' => "Schema_one",
136             token => {
137             type => "string",
138             minLength => 5
139             },
140             login => {
141             params => [
142             {type => "boolean"},
143             {type => "string"}
144             ],
145             returns => { type => "number" }
146             }
147             });
148              
149             $ajv->compile({
150             '$id' => "Schema_two",
151             type => "string",
152             minLength => 5
153             });
154              
155             # and then get validators using schema id and path to needed subschema
156             $validator_one = $ajv->get_validator('Schema_one#/login/returns');
157             $validator_two = $ajv->get_validator('Schema_two');
158              
159             =head2 duktape
160              
161             Need to do something else, and something magic? This is a read-only accessor
162             to the Duktape env.
163              
164             =head1 Data::JSONSchema::Ajv::Validator
165              
166             Single method object:
167              
168             =head2 validate
169              
170             my $errors = $validator->validate( $data_structure );
171             my $errors = $validator->validate( \$data_structure );
172              
173             Validate a data-structure against the schema. Whith some options specified
174             (like C, C) Ajv may modify input data. If you
175             want to receive this modifications you need to pass your data structure by
176             reference: for example if you normally pass hashref or arrayref in this case
177             you need to pass reference to hashref or reference to arrayref. If you are
178             validating just a simple scalar like string or number you can also pass it
179             by reference, but you'll not get any data modifications because on Ajv's side
180             they are passed by value and can't be modified. You can try to wrap such
181             scalars in array with one element and modify schema a little to pass this
182             limitation. Returns C on success, and a data-structure complaining
183             on failure. The data-structure is whatever C produces - you can either
184             read its documentation, or be lazy and simply L it.
185              
186             =head1 BOOLEANS AND UNDEFINED/NULL
187              
188             Perl has no special Boolean types. JSON (and indeed JavaScript) does. If you're
189             really brave, you can pass in a C option to replace the underlying
190             L at instantiation.
191              
192             =head1 SEE ALSO
193              
194             This module was written because I couldn't get any of the other JSON Schema
195             validators to work.
196              
197             Toby Inkster wrote L, which I had high hopes for, because he's a
198             smart cookie, but it seems like it's very out of date compared to modern
199             schemas.
200              
201             I was unable to get L to fail validation for any schema I gave
202             it. That's probably due to having miswritten my schemas, but it didn't give me
203             any hints, and I did get some errors along the lines of
204             L and so I gave up. I also find it
205             mildly offensive that (the most excellent) L is a dependency for
206             a JSON tool. Additionally it doesn't validate the schemas themselves, and I'm
207             too stupid to use a tool like that.
208              
209             L provides some schema tests. This passes all
210             of thems except the ones that require going and downloading external schemas.
211              
212             =head1 AUTHOR
213              
214             All the hard work was done by the guy who wrote Ajv,
215             L.
216              
217             This Perl wrapper written by Peter Sergeant.
218              
219             =cut
220              
221             package Data::JSONSchema::Ajv {
222 3     3   17 use Carp qw/croak/;
  3         5  
  3         126  
223 3     3   1588 use Cpanel::JSON::XS;
  3         5956  
  3         1739  
224              
225             sub new {
226 17     17 1 536178 my ( $class, $ajv_options, $my_options ) = @_;
227              
228             $ajv_options = {
229             logger => $Cpanel::JSON::XS::false,
230 17 100       1039 %{ $ajv_options || {} }
  17         4059  
231             };
232              
233 17   100     2740 $my_options ||= {};
234 17   100     321 my $draft_version = delete $my_options->{'draft'} // '07';
235 17         60 my $ajv_src = delete $my_options->{ajv_src};
236 17   33     226 my $json_obj = delete $my_options->{'json'}
237             // Cpanel::JSON::XS->new->ascii->allow_nonref;
238 17 100       69 if ( keys %$my_options ) {
239 1         237 croak( "Unknown options: " . ( join ', ', keys %$my_options ) );
240             }
241              
242 16         18327 my $js = JavaScript::Duktape::XS->new();
243 16   66     2411010 $js->eval($ajv_src || $Data::JSONSchema::Ajv::src::src);
244              
245             # Setup appropriately for different version of the schema
246 16 100       287 if ( $draft_version eq '04' ) {
    100          
    100          
247             warn "Over-riding 'schemaId' as you specified draft-04"
248 2 50       19 if exists $ajv_options->{'schemaId'};
249 2         14 $ajv_options->{'schemaId'} = 'id';
250             warn "Over-riding 'meta' as you specified draft-04"
251 2 50       10 if exists $ajv_options->{'meta'};
252 2         223 $ajv_options->{'meta'}
253             = $json_obj->decode($Data::JSONSchema::Ajv::src::04::src);
254             }
255             elsif ( $draft_version eq '06' ) {
256             warn "Over-riding 'meta' as you specified draft-06"
257 2 50       18 if exists $ajv_options->{'meta'};
258 2         250 $ajv_options->{'meta'}
259             = $json_obj->decode($Data::JSONSchema::Ajv::src::06::src);
260             }
261             elsif ( $draft_version ne '07' ) {
262 1         1083 die "Can only accept draft versions: '04', '06', '07'";
263             }
264              
265 15         141 my $self = bless {
266             '_context' => $js,
267             '_counter' => 0,
268             '_json' => $json_obj,
269             }, $class;
270              
271 15         413 $js->set( ajvOptions => $ajv_options );
272              
273 15         299147 $js->eval('var ajv = new Ajv(ajvOptions);');
274 15 100       1404 $js->typeof('ajv') ne 'undefined'
275             or die "Can't create ajv object. Bad `ajv_src' specified?";
276              
277 14         2954 return $self;
278             }
279            
280             sub compile {
281 302     302 1 1814 my ( $self, $schema ) = @_;
282              
283 302         875 my $counter = $self->{'_counter'}++;
284 302         751 my $schema_name = "schemaDef_$counter";
285 302         732 my $validator_name = "validator_$counter";
286              
287 302 100       1049 if ( ref $schema ) {
288 299         6770 $self->{'_context'}->set( $schema_name, $schema );
289 299         913106 $self->{'_context'}
290             ->eval("var $validator_name = ajv.compile($schema_name);");
291             }
292             else {
293 3         178 $self->{'_context'}
294             ->eval("var $validator_name = ajv.compile($schema);");
295             }
296            
297 302 100       3955 $self->{'_context'}->typeof($validator_name) ne 'undefined'
298             or die "Can't compile schema passed";
299              
300 300         1277 return $validator_name;
301             }
302              
303             sub make_validator {
304 298     298 1 1351524 my ( $self, $schema ) = @_;
305              
306 298         1029 my $validator_name = $self->compile($schema);
307              
308 297         3316 return bless [ $self => $validator_name ],
309             'Data::JSONSchema::Ajv::Validator';
310             }
311            
312             sub get_validator {
313 4     4 1 1530 my ( $self, $pointer ) = @_;
314            
315 4         11 my $counter = $self->{'_counter'}++;
316 4         11 my $validator_name = "validator_$counter";
317            
318 4         1238 $self->{'_context'}
319             ->eval("var $validator_name = ajv.getSchema('$pointer');");
320            
321 4 100       47 $self->{'_context'}->typeof($validator_name) ne 'undefined'
322             or die "Can't find such validator";
323            
324 3         1313 return bless [ $self => $validator_name ],
325             'Data::JSONSchema::Ajv::Validator';
326             }
327              
328 0     0 1 0 sub duktape { my $self = shift; return $self->{'_context'}; }
  0         0  
329             }
330             $Data::JSONSchema::Ajv::VERSION = '0.09';;
331              
332             package Data::JSONSchema::Ajv::Validator {
333             $Data::JSONSchema::Ajv::Validator::VERSION = '0.09';
334 3     3   22 use strict;
  3         5  
  3         119  
335 3     3   23 use warnings;
  3         12  
  3         572  
336              
337             sub validate {
338 1091     1091   1638861 my ( $self, $input ) = @_;
339 1091         2426 my ( $parent, $name ) = @$self;
340 1091         2170 my $js = $parent->{'_context'};
341            
342 1091         1882 my $input_reftype = ref $input;
343              
344 1091         2189 my $data_name = "data_$name";
345 1091 100 100     226521 $js->set( $data_name, $input_reftype eq 'REF' || $input_reftype eq 'SCALAR' ? $$input : $input );
346              
347 1091         66654 $js->eval("var result = $name($data_name)");
348              
349 1091         8042 my $result = $js->get('result');
350            
351 1091 100       3094 if ( $input_reftype eq 'REF' ) {
352 2         285694 $$input = $js->get($data_name);
353             }
354              
355 1091         34590 $js->set( $data_name, undef );
356              
357 1091 100       4946 if ($result) {
358 591         6347 return;
359             }
360             else {
361 500         48145 $js->eval("var errors = $name.errors");
362 500         14654 my $errors = $js->get('errors');
363 500         2677 return $errors;
364             }
365              
366             }
367              
368             };
369              
370             1;