File Coverage

blib/lib/Data/JSONSchema/Ajv.pm
Criterion Covered Total %
statement 80 82 97.5
branch 25 28 89.2
condition 10 13 76.9
subroutine 13 14 92.8
pod 3 3 100.0
total 131 140 93.5


line stmt bran cond sub pod time code
1 3     3   135961 use strict;
  3         14  
  3         74  
2 3     3   13 use warnings;
  3         4  
  3         65  
3              
4 3     3   1099 use JavaScript::Duktape::XS;
  3         28973  
  3         121  
5              
6 3     3   2158 use Data::JSONSchema::Ajv::src;
  3         14  
  3         91  
7 3     3   1081 use Data::JSONSchema::Ajv::src::04;
  3         13  
  3         85  
8 3     3   994 use Data::JSONSchema::Ajv::src::06;
  3         13  
  3         170  
9              
10             =head1 NAME
11              
12             Data::JSONSchema::Ajv - JSON Schema Validator wrapping Ajv
13              
14             =head1 VERSION
15              
16             version 0.08
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 duktape
120              
121             Need to do something else, and something magic? This is a read-only accessor
122             to the Duktape env.
123              
124             =head1 Data::JSONSchema::Ajv::Validator
125              
126             Single method object:
127              
128             =head2 validate
129              
130             my $errors = $validator->validate( $data_structure );
131             my $errors = $validator->validate( \$data_structure );
132              
133             Validate a data-structure against the schema. Whith some options specified
134             (like C, C) Ajv may modify input data. If you
135             want to receive this modifications you need to pass your data structure by
136             reference: for example if you normally pass hashref or arrayref in this case
137             you need to pass reference to hashref or reference to arrayref. If you are
138             validating just a simple scalar like string or number you can also pass it
139             by reference, but you'll not get any data modifications because on Ajv's side
140             they are passed by value and can't be modified. You can try to wrap such
141             scalars in array with one element and modify schema a little to pass this
142             limitation. Returns C on success, and a data-structure complaining
143             on failure. The data-structure is whatever C produces - you can either
144             read its documentation, or be lazy and simply L it.
145              
146             =head1 BOOLEANS AND UNDEFINED/NULL
147              
148             Perl has no special Boolean types. JSON (and indeed JavaScript) does. If you're
149             really brave, you can pass in a C option to replace the underlying
150             L at instantiation.
151              
152             =head1 SEE ALSO
153              
154             This module was written because I couldn't get any of the other JSON Schema
155             validators to work.
156              
157             Toby Inkster wrote L, which I had high hopes for, because he's a
158             smart cookie, but it seems like it's very out of date compared to modern
159             schemas.
160              
161             I was unable to get L to fail validation for any schema I gave
162             it. That's probably due to having miswritten my schemas, but it didn't give me
163             any hints, and I did get some errors along the lines of
164             L and so I gave up. I also find it
165             mildly offensive that (the most excellent) L is a dependency for
166             a JSON tool. Additionally it doesn't validate the schemas themselves, and I'm
167             too stupid to use a tool like that.
168              
169             L provides some schema tests. This passes all
170             of thems except the ones that require going and downloading external schemas.
171              
172             =head1 AUTHOR
173              
174             All the hard work was done by the guy who wrote Ajv,
175             L.
176              
177             This Perl wrapper written by Peter Sergeant.
178              
179             =cut
180              
181             package Data::JSONSchema::Ajv {
182 3     3   18 use Carp qw/croak/;
  3         5  
  3         122  
183 3     3   1370 use Cpanel::JSON::XS;
  3         5317  
  3         1331  
184              
185             sub new {
186 15     15 1 514984 my ( $class, $ajv_options, $my_options ) = @_;
187              
188             $ajv_options = {
189             logger => $Cpanel::JSON::XS::false,
190 15 100       1051 %{ $ajv_options || {} }
  15         4086  
191             };
192              
193 15   100     2945 $my_options ||= {};
194 15   100     340 my $draft_version = delete $my_options->{'draft'} // '07';
195 15         43 my $ajv_src = delete $my_options->{ajv_src};
196 15   33     206 my $json_obj = delete $my_options->{'json'}
197             // Cpanel::JSON::XS->new->ascii->allow_nonref;
198 15 100       73 if ( keys %$my_options ) {
199 1         197 croak( "Unknown options: " . ( join ', ', keys %$my_options ) );
200             }
201              
202 14         15958 my $js = JavaScript::Duktape::XS->new();
203 14   66     2015221 $js->eval($ajv_src || $Data::JSONSchema::Ajv::src::src);
204              
205             # Setup appropriately for different version of the schema
206 14 100       253 if ( $draft_version eq '04' ) {
    100          
    100          
207             warn "Over-riding 'schemaId' as you specified draft-04"
208 2 50       17 if exists $ajv_options->{'schemaId'};
209 2         9 $ajv_options->{'schemaId'} = 'id';
210             warn "Over-riding 'meta' as you specified draft-04"
211 2 50       11 if exists $ajv_options->{'meta'};
212 2         202 $ajv_options->{'meta'}
213             = $json_obj->decode($Data::JSONSchema::Ajv::src::04::src);
214             }
215             elsif ( $draft_version eq '06' ) {
216             warn "Over-riding 'meta' as you specified draft-06"
217 2 50       20 if exists $ajv_options->{'meta'};
218 2         255 $ajv_options->{'meta'}
219             = $json_obj->decode($Data::JSONSchema::Ajv::src::06::src);
220             }
221             elsif ( $draft_version ne '07' ) {
222 1         1062 die "Can only accept draft versions: '04', '06', '07'";
223             }
224              
225 13         110 my $self = bless {
226             '_context' => $js,
227             '_counter' => 0,
228             '_json' => $json_obj,
229             }, $class;
230              
231 13         434 $js->set( ajvOptions => $ajv_options );
232              
233 13         268264 $js->eval('var ajv = new Ajv(ajvOptions);');
234 13 100       1452 $js->typeof('ajv') ne 'undefined'
235             or die "Can't create ajv object. Bad `ajv_src' specified?";
236              
237 12         2827 return $self;
238             }
239              
240             sub make_validator {
241 298     298 1 1164439 my ( $self, $schema ) = @_;
242              
243 298         701 my $counter = $self->{'_counter'}++;
244 298         711 my $schema_name = "schemaDef_$counter";
245 298         597 my $validator_name = "validator_$counter";
246              
247 298 100       766 if ( ref $schema ) {
248 296         4980 $self->{'_context'}->set( $schema_name, $schema );
249 296         742569 $self->{'_context'}
250             ->eval("var $validator_name = ajv.compile($schema_name);");
251             }
252             else {
253 2         132 $self->{'_context'}
254             ->eval("var $validator_name = ajv.compile($schema);");
255             }
256            
257 298 100       2982 $self->{'_context'}->typeof($validator_name) ne 'undefined'
258             or die "Can't compile schema passed";
259              
260 297         4231 return bless [ $self => $validator_name ],
261             'Data::JSONSchema::Ajv::Validator';
262             }
263              
264 0     0 1 0 sub duktape { my $self = shift; return $self->{'_context'}; }
  0         0  
265             }
266             $Data::JSONSchema::Ajv::VERSION = '0.08';;
267              
268             package Data::JSONSchema::Ajv::Validator {
269             $Data::JSONSchema::Ajv::Validator::VERSION = '0.08';
270 3     3   26 use strict;
  3         6  
  3         52  
271 3     3   13 use warnings;
  3         20  
  3         508  
272              
273             sub validate {
274 1091     1091   1433749 my ( $self, $input ) = @_;
275 1091         2128 my ( $parent, $name ) = @$self;
276 1091         2020 my $js = $parent->{'_context'};
277            
278 1091         1712 my $input_reftype = ref $input;
279              
280 1091         1799 my $data_name = "data_$name";
281 1091 100 100     236530 $js->set( $data_name, $input_reftype eq 'REF' || $input_reftype eq 'SCALAR' ? $$input : $input );
282              
283 1091         61524 $js->eval("var result = $name($data_name)");
284              
285 1091         7278 my $result = $js->get('result');
286            
287 1091 100       2633 if ( $input_reftype eq 'REF' ) {
288 2         295089 $$input = $js->get($data_name);
289             }
290              
291 1091         44989 $js->set( $data_name, undef );
292              
293 1091 100       4266 if ($result) {
294 591         5931 return;
295             }
296             else {
297 500         45026 $js->eval("var errors = $name.errors");
298 500         15178 my $errors = $js->get('errors');
299 500         2519 return $errors;
300             }
301              
302             }
303              
304             };
305              
306             1;