| 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; |