| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
##---------------------------------------------------------------------------- |
|
2
|
|
|
|
|
|
|
## JSON Schema Validator - ~/lib/JSON/Schema/Validate.pm |
|
3
|
|
|
|
|
|
|
## Version v0.9.0 |
|
4
|
|
|
|
|
|
|
## Copyright(c) 2026 DEGUEST Pte. Ltd. |
|
5
|
|
|
|
|
|
|
## Author: Jacques Deguest <jack@deguest.jp> |
|
6
|
|
|
|
|
|
|
## Created 2025/11/07 |
|
7
|
|
|
|
|
|
|
## Modified 2026/03/01 |
|
8
|
|
|
|
|
|
|
## All rights reserved |
|
9
|
|
|
|
|
|
|
## |
|
10
|
|
|
|
|
|
|
## |
|
11
|
|
|
|
|
|
|
## This program is free software; you can redistribute it and/or modify it |
|
12
|
|
|
|
|
|
|
## under the same terms as Perl itself. |
|
13
|
|
|
|
|
|
|
##---------------------------------------------------------------------------- |
|
14
|
|
|
|
|
|
|
package JSON::Schema::Validate; |
|
15
|
|
|
|
|
|
|
BEGIN |
|
16
|
|
|
|
|
|
|
{ |
|
17
|
44
|
|
|
44
|
|
5994602
|
use strict; |
|
|
44
|
|
|
|
|
91
|
|
|
|
44
|
|
|
|
|
1806
|
|
|
18
|
44
|
|
|
44
|
|
202
|
use warnings; |
|
|
44
|
|
|
|
|
81
|
|
|
|
44
|
|
|
|
|
2883
|
|
|
19
|
44
|
|
|
44
|
|
249
|
use warnings::register; |
|
|
44
|
|
|
|
|
80
|
|
|
|
44
|
|
|
|
|
2795
|
|
|
20
|
44
|
|
|
44
|
|
246
|
use vars qw( $VERSION $DEBUG ); |
|
|
44
|
|
|
|
|
105
|
|
|
|
44
|
|
|
|
|
2912
|
|
|
21
|
44
|
|
|
44
|
|
275
|
use B (); |
|
|
44
|
|
|
|
|
101
|
|
|
|
44
|
|
|
|
|
619
|
|
|
22
|
44
|
|
|
44
|
|
1198
|
use JSON (); |
|
|
44
|
|
|
|
|
17080
|
|
|
|
44
|
|
|
|
|
991
|
|
|
23
|
44
|
|
|
44
|
|
192
|
use Scalar::Util qw( blessed looks_like_number reftype refaddr ); |
|
|
44
|
|
|
|
|
64
|
|
|
|
44
|
|
|
|
|
3787
|
|
|
24
|
44
|
|
|
44
|
|
270
|
use List::Util qw( first any all ); |
|
|
44
|
|
|
|
|
111
|
|
|
|
44
|
|
|
|
|
3301
|
|
|
25
|
44
|
|
|
44
|
|
22759
|
use Encode (); |
|
|
44
|
|
|
|
|
873141
|
|
|
|
44
|
|
|
|
|
1909
|
|
|
26
|
44
|
|
|
44
|
|
1091
|
our $VERSION = 'v0.9.0'; |
|
27
|
|
|
|
|
|
|
}; |
|
28
|
|
|
|
|
|
|
|
|
29
|
44
|
|
|
44
|
|
650
|
use v5.16.0; |
|
|
44
|
|
|
|
|
149
|
|
|
30
|
44
|
|
|
44
|
|
204
|
use strict; |
|
|
44
|
|
|
|
|
59
|
|
|
|
44
|
|
|
|
|
903
|
|
|
31
|
44
|
|
|
44
|
|
150
|
use warnings; |
|
|
44
|
|
|
|
|
63
|
|
|
|
44
|
|
|
|
|
49985
|
|
|
32
|
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
sub new |
|
34
|
|
|
|
|
|
|
{ |
|
35
|
121
|
|
|
121
|
1
|
7256051
|
my $class = shift( @_ ); |
|
36
|
121
|
|
|
|
|
263
|
my $schema = shift( @_ ); |
|
37
|
121
|
50
|
33
|
|
|
1179
|
if( defined( $schema ) && ( !ref( $schema ) || ref( $schema ) ne 'HASH' ) ) |
|
|
|
|
33
|
|
|
|
|
|
38
|
|
|
|
|
|
|
{ |
|
39
|
0
|
|
|
|
|
0
|
die( "You provided a value (", overload::StrVal( $schema ), "), but it is not an hash reference. You must create a new $class object like this: $class->new( \$schema, \%opts );" ); |
|
40
|
|
|
|
|
|
|
} |
|
41
|
|
|
|
|
|
|
|
|
42
|
121
|
|
|
|
|
750
|
my $self = |
|
43
|
|
|
|
|
|
|
{ |
|
44
|
|
|
|
|
|
|
comment_handler => undef, |
|
45
|
|
|
|
|
|
|
# boolean |
|
46
|
|
|
|
|
|
|
compile_on => 0, |
|
47
|
|
|
|
|
|
|
# { schema, anchors, id_index, base } |
|
48
|
|
|
|
|
|
|
compiled => undef, |
|
49
|
|
|
|
|
|
|
# boolean; when 0, failures don’t invalidate; when 1, they do |
|
50
|
|
|
|
|
|
|
content_assert => 0, |
|
51
|
|
|
|
|
|
|
content_decoders => {}, |
|
52
|
|
|
|
|
|
|
errors => [], |
|
53
|
|
|
|
|
|
|
# boolean; when true, then non-standard extensions are enabled. |
|
54
|
|
|
|
|
|
|
extensions => 0, |
|
55
|
|
|
|
|
|
|
formats => {}, |
|
56
|
|
|
|
|
|
|
# boolean |
|
57
|
|
|
|
|
|
|
ignore_req_vocab => 0, |
|
58
|
|
|
|
|
|
|
last_error => '', |
|
59
|
|
|
|
|
|
|
last_trace => [], |
|
60
|
|
|
|
|
|
|
max_errors => 200, |
|
61
|
|
|
|
|
|
|
media_validators => {}, |
|
62
|
|
|
|
|
|
|
# boolean |
|
63
|
|
|
|
|
|
|
normalize_instance => 1, |
|
64
|
|
|
|
|
|
|
# boolean: when true, prune unknown properties before validate() |
|
65
|
|
|
|
|
|
|
prune_unknown => 0, |
|
66
|
|
|
|
|
|
|
# ($abs_uri) -> $schema_hashref |
|
67
|
|
|
|
|
|
|
resolver => undef, |
|
68
|
|
|
|
|
|
|
schema => _clone( $schema ), |
|
69
|
|
|
|
|
|
|
# 0 = unlimited |
|
70
|
|
|
|
|
|
|
trace_limit => 0, |
|
71
|
|
|
|
|
|
|
# boolean |
|
72
|
|
|
|
|
|
|
trace_on => 0, |
|
73
|
|
|
|
|
|
|
# 0 = record all |
|
74
|
|
|
|
|
|
|
trace_sample => 0, |
|
75
|
|
|
|
|
|
|
# boolean; when true, 'uniqueKeys' extension is enabled. |
|
76
|
|
|
|
|
|
|
unique_keys => 0, |
|
77
|
|
|
|
|
|
|
# internal boolean; not an option |
|
78
|
|
|
|
|
|
|
vocab_checked => 0, |
|
79
|
|
|
|
|
|
|
vocab_support => {}, |
|
80
|
|
|
|
|
|
|
}; |
|
81
|
|
|
|
|
|
|
|
|
82
|
121
|
|
|
|
|
422
|
bless( $self, $class ); |
|
83
|
121
|
|
|
|
|
572
|
my $opts = $self->_get_args_as_hash( @_ ); |
|
84
|
121
|
|
|
|
|
535
|
my @bool_options = qw( |
|
85
|
|
|
|
|
|
|
content_assert |
|
86
|
|
|
|
|
|
|
extensions |
|
87
|
|
|
|
|
|
|
ignore_req_vocab |
|
88
|
|
|
|
|
|
|
normalize_instance |
|
89
|
|
|
|
|
|
|
prune_unknown |
|
90
|
|
|
|
|
|
|
unique_keys |
|
91
|
|
|
|
|
|
|
); |
|
92
|
121
|
|
|
|
|
302
|
foreach my $opt ( @bool_options ) |
|
93
|
|
|
|
|
|
|
{ |
|
94
|
726
|
100
|
|
|
|
1577
|
next unless( exists( $opts->{ $opt } ) ); |
|
95
|
8
|
50
|
|
|
|
35
|
$self->{ $opt } = $opts->{ $opt } ? 1 : 0 |
|
96
|
|
|
|
|
|
|
} |
|
97
|
|
|
|
|
|
|
# Make sure the boolean value for 'extensions' is propagated to 'unique_keys' unless the option 'unique_keys' has been explicitly specified, and then we do not want to overwrite it. |
|
98
|
121
|
50
|
|
|
|
696
|
$self->{unique_keys} = $self->{extensions} unless( exists( $opts->{unique_keys} ) ); |
|
99
|
121
|
100
|
|
|
|
594
|
if( exists( $opts->{ignore_unknown_required_vocab} ) ) |
|
100
|
|
|
|
|
|
|
{ |
|
101
|
1
|
50
|
|
|
|
6
|
$self->{ignore_req_vocab} = $opts->{ignore_unknown_required_vocab} ? 1 : 0; |
|
102
|
|
|
|
|
|
|
} |
|
103
|
121
|
100
|
|
|
|
516
|
if( exists( $opts->{compile} ) ) |
|
104
|
|
|
|
|
|
|
{ |
|
105
|
11
|
100
|
|
|
|
49
|
$self->{compile_on} = $opts->{compile} ? 1 : 0; |
|
106
|
|
|
|
|
|
|
} |
|
107
|
121
|
100
|
|
|
|
489
|
if( exists( $opts->{trace} ) ) |
|
108
|
|
|
|
|
|
|
{ |
|
109
|
6
|
50
|
|
|
|
16
|
$self->{trace_on} = $opts->{trace} ? 1 : 0; |
|
110
|
|
|
|
|
|
|
} |
|
111
|
|
|
|
|
|
|
|
|
112
|
121
|
|
|
|
|
419
|
my @other_options = qw( max_errors trace_limit ); |
|
113
|
121
|
|
|
|
|
260
|
foreach my $opt ( @other_options ) |
|
114
|
|
|
|
|
|
|
{ |
|
115
|
242
|
100
|
|
|
|
605
|
next unless( exists( $opts->{ $opt } ) ); |
|
116
|
7
|
|
|
|
|
19
|
$self->{ $opt } = $opts->{ $opt }; |
|
117
|
|
|
|
|
|
|
} |
|
118
|
|
|
|
|
|
|
|
|
119
|
121
|
100
|
|
|
|
337
|
if( exists( $opts->{trace_sample} ) ) |
|
120
|
|
|
|
|
|
|
{ |
|
121
|
|
|
|
|
|
|
# Check for percentage integer (0 to 100) |
|
122
|
1
|
50
|
|
|
|
8
|
if( $opts->{trace_sample} =~ /^([0-9]{1,2}|100)$/ ) |
|
123
|
|
|
|
|
|
|
{ |
|
124
|
1
|
|
|
|
|
3
|
$self->{trace_sample} = $opts->{trace_sample}; |
|
125
|
|
|
|
|
|
|
} |
|
126
|
|
|
|
|
|
|
else |
|
127
|
|
|
|
|
|
|
{ |
|
128
|
0
|
0
|
|
|
|
0
|
warn( "Warning only: invalid value for option 'trace_sample'." ) if( warnings::enabled() ); |
|
129
|
|
|
|
|
|
|
} |
|
130
|
|
|
|
|
|
|
} |
|
131
|
|
|
|
|
|
|
|
|
132
|
|
|
|
|
|
|
# User-supplied format callbacks (override precedence left to caller order) |
|
133
|
121
|
100
|
66
|
|
|
505
|
if( $opts->{format} && ref( $opts->{format} ) eq 'HASH' ) |
|
134
|
|
|
|
|
|
|
{ |
|
135
|
3
|
|
|
|
|
21
|
$self->{formats}->{ $_ } = $opts->{format}->{ $_ } for( keys( %{$opts->{format}} ) ); |
|
|
3
|
|
|
|
|
23
|
|
|
136
|
|
|
|
|
|
|
} |
|
137
|
121
|
100
|
|
|
|
541
|
$self->{vocab_support} = $opts->{vocab_support} ? { %{$opts->{vocab_support}} } : {}; |
|
|
1
|
|
|
|
|
4
|
|
|
138
|
|
|
|
|
|
|
|
|
139
|
121
|
100
|
|
|
|
694
|
$self->_check_vocabulary_required unless( $self->{ignore_req_vocab} ); |
|
140
|
120
|
100
|
|
|
|
391
|
$self->_register_builtin_media_validators() if( $self->{content_assert} ); |
|
141
|
120
|
100
|
|
|
|
352
|
$self->{compiled} = _compile_root( $self->{schema} ) if( $self->{compile_on} ); |
|
142
|
120
|
|
|
|
|
606
|
return( $self ); |
|
143
|
|
|
|
|
|
|
} |
|
144
|
|
|
|
|
|
|
|
|
145
|
|
|
|
|
|
|
# $js->compile -> enables it |
|
146
|
|
|
|
|
|
|
# $js->compile(1) -> enables it |
|
147
|
|
|
|
|
|
|
# $js->compile(0) -> disables it |
|
148
|
|
|
|
|
|
|
sub compile |
|
149
|
|
|
|
|
|
|
{ |
|
150
|
1
|
|
|
1
|
1
|
626
|
my( $self, $bool ) = @_; |
|
151
|
1
|
50
|
|
|
|
5
|
my $on = defined( $bool ) ? $bool : 1; |
|
152
|
1
|
|
|
|
|
2
|
$self->{compile_on} = $on; |
|
153
|
|
|
|
|
|
|
|
|
154
|
1
|
50
|
33
|
|
|
8
|
if( $self->{compile_on} && !$self->{compiled} ) |
|
155
|
|
|
|
|
|
|
{ |
|
156
|
1
|
|
|
|
|
5
|
$self->{compiled} = _compile_root( $self->{schema} ); |
|
157
|
|
|
|
|
|
|
} |
|
158
|
1
|
|
|
|
|
3
|
return( $self ); |
|
159
|
|
|
|
|
|
|
} |
|
160
|
|
|
|
|
|
|
|
|
161
|
|
|
|
|
|
|
sub compile_js |
|
162
|
|
|
|
|
|
|
{ |
|
163
|
0
|
|
|
0
|
1
|
0
|
my $self = shift( @_ ); |
|
164
|
0
|
|
|
|
|
0
|
my $opts = $self->_get_args_as_hash( @_ ); |
|
165
|
|
|
|
|
|
|
|
|
166
|
|
|
|
|
|
|
my $schema = $self->{schema} |
|
167
|
0
|
0
|
|
|
|
0
|
or die( "No schema loaded; cannot compile to JavaScript" ); |
|
168
|
|
|
|
|
|
|
|
|
169
|
|
|
|
|
|
|
# Public JS API name, e.g. "validateIncorporation" |
|
170
|
|
|
|
|
|
|
my $name = exists( $opts->{name} ) && defined( $opts->{name} ) && length( $opts->{name} ) |
|
171
|
|
|
|
|
|
|
? $opts->{name} |
|
172
|
0
|
0
|
0
|
|
|
0
|
: 'validate'; |
|
173
|
|
|
|
|
|
|
|
|
174
|
|
|
|
|
|
|
# Max errors to collect on the client side |
|
175
|
0
|
0
|
|
|
|
0
|
my $max_errors = exists( $opts->{max_errors} ) ? int( $opts->{max_errors} ) : 200; |
|
176
|
0
|
0
|
|
|
|
0
|
$max_errors = 0 if( $max_errors < 0 ); |
|
177
|
|
|
|
|
|
|
|
|
178
|
0
|
|
|
|
|
0
|
my %seen; # schema_pointer -> function name |
|
179
|
0
|
|
|
|
|
0
|
my $counter = 0; # for generating unique function names |
|
180
|
0
|
|
|
|
|
0
|
my @funcs; # accumulated JS validator functions |
|
181
|
|
|
|
|
|
|
|
|
182
|
0
|
|
|
|
|
0
|
my $root_ptr = '#'; |
|
183
|
|
|
|
|
|
|
|
|
184
|
|
|
|
|
|
|
# Pass $opts down so we can see ecma => ... inside the compiler. |
|
185
|
0
|
|
|
|
|
0
|
my $root_fn = $self->_compile_js_node( $schema, $root_ptr, \%seen, \@funcs, \$counter, $schema, $opts ); |
|
186
|
|
|
|
|
|
|
|
|
187
|
0
|
|
|
|
|
0
|
my $js = ''; |
|
188
|
|
|
|
|
|
|
|
|
189
|
0
|
|
|
|
|
0
|
$js .= <<'JS_RUNTIME'; |
|
190
|
|
|
|
|
|
|
(function(global) |
|
191
|
|
|
|
|
|
|
{ |
|
192
|
|
|
|
|
|
|
"use strict"; |
|
193
|
|
|
|
|
|
|
|
|
194
|
|
|
|
|
|
|
function _jsv_err(ctx, path, keyword, message, schemaPtr) |
|
195
|
|
|
|
|
|
|
{ |
|
196
|
|
|
|
|
|
|
if(ctx.maxErrors && ctx.errors.length >= ctx.maxErrors) |
|
197
|
|
|
|
|
|
|
{ |
|
198
|
|
|
|
|
|
|
return; |
|
199
|
|
|
|
|
|
|
} |
|
200
|
|
|
|
|
|
|
ctx.errors.push({ |
|
201
|
|
|
|
|
|
|
path: path, |
|
202
|
|
|
|
|
|
|
keyword: keyword, |
|
203
|
|
|
|
|
|
|
message: message, |
|
204
|
|
|
|
|
|
|
schema_pointer: schemaPtr |
|
205
|
|
|
|
|
|
|
}); |
|
206
|
|
|
|
|
|
|
} |
|
207
|
|
|
|
|
|
|
|
|
208
|
|
|
|
|
|
|
function _jsv_typeOf(x) |
|
209
|
|
|
|
|
|
|
{ |
|
210
|
|
|
|
|
|
|
if(x === null) |
|
211
|
|
|
|
|
|
|
{ |
|
212
|
|
|
|
|
|
|
return "null"; |
|
213
|
|
|
|
|
|
|
} |
|
214
|
|
|
|
|
|
|
if(Array.isArray ? Array.isArray(x) : Object.prototype.toString.call(x) === "[object Array]") |
|
215
|
|
|
|
|
|
|
{ |
|
216
|
|
|
|
|
|
|
return "array"; |
|
217
|
|
|
|
|
|
|
} |
|
218
|
|
|
|
|
|
|
var t = typeof x; |
|
219
|
|
|
|
|
|
|
if(t === "number" && isFinite(x) && Math.floor(x) === x) |
|
220
|
|
|
|
|
|
|
{ |
|
221
|
|
|
|
|
|
|
// distinguish "integer" for convenience |
|
222
|
|
|
|
|
|
|
return "integer"; |
|
223
|
|
|
|
|
|
|
} |
|
224
|
|
|
|
|
|
|
return t; |
|
225
|
|
|
|
|
|
|
} |
|
226
|
|
|
|
|
|
|
|
|
227
|
|
|
|
|
|
|
function _jsv_hasOwn(obj, prop) |
|
228
|
|
|
|
|
|
|
{ |
|
229
|
|
|
|
|
|
|
return Object.prototype.hasOwnProperty.call(obj, prop); |
|
230
|
|
|
|
|
|
|
} |
|
231
|
|
|
|
|
|
|
|
|
232
|
|
|
|
|
|
|
JS_RUNTIME |
|
233
|
|
|
|
|
|
|
|
|
234
|
|
|
|
|
|
|
# Public entry point |
|
235
|
0
|
|
|
|
|
0
|
$js .= <<"JS_RUNTIME"; |
|
236
|
|
|
|
|
|
|
function $name(instance) |
|
237
|
|
|
|
|
|
|
{ |
|
238
|
|
|
|
|
|
|
var ctx = { errors: [], maxErrors: $max_errors }; |
|
239
|
|
|
|
|
|
|
$root_fn(instance, "#", ctx); |
|
240
|
|
|
|
|
|
|
return ctx.errors; |
|
241
|
|
|
|
|
|
|
} |
|
242
|
|
|
|
|
|
|
|
|
243
|
|
|
|
|
|
|
JS_RUNTIME |
|
244
|
|
|
|
|
|
|
|
|
245
|
|
|
|
|
|
|
# Attach to global (browser: window) in a conservative way |
|
246
|
0
|
|
|
|
|
0
|
$js .= <<"JS_RUNTIME"; |
|
247
|
|
|
|
|
|
|
if(typeof global === 'object' && global) |
|
248
|
|
|
|
|
|
|
{ |
|
249
|
|
|
|
|
|
|
global.$name = $name; |
|
250
|
|
|
|
|
|
|
} |
|
251
|
|
|
|
|
|
|
|
|
252
|
|
|
|
|
|
|
JS_RUNTIME |
|
253
|
|
|
|
|
|
|
|
|
254
|
|
|
|
|
|
|
# Emit all compiled validator functions, indented one level |
|
255
|
0
|
|
|
|
|
0
|
$js .= join( "\n\n", map{ ' ' . join( "\n ", split( /\n/, $_ ) ) } @funcs ); |
|
|
0
|
|
|
|
|
0
|
|
|
256
|
|
|
|
|
|
|
|
|
257
|
0
|
|
|
|
|
0
|
$js .= <<'JS_RUNTIME'; |
|
258
|
|
|
|
|
|
|
|
|
259
|
|
|
|
|
|
|
})(this); |
|
260
|
|
|
|
|
|
|
JS_RUNTIME |
|
261
|
|
|
|
|
|
|
|
|
262
|
0
|
|
|
|
|
0
|
return( $js ); |
|
263
|
|
|
|
|
|
|
} |
|
264
|
|
|
|
|
|
|
|
|
265
|
|
|
|
|
|
|
# $js->content_checks -> enables it |
|
266
|
|
|
|
|
|
|
# $js->content_checks(1) -> enables it |
|
267
|
|
|
|
|
|
|
# $js->content_checks(0) -> disables it |
|
268
|
|
|
|
|
|
|
sub content_checks |
|
269
|
|
|
|
|
|
|
{ |
|
270
|
3
|
|
|
3
|
1
|
24
|
my( $self, $bool ) = @_; |
|
271
|
3
|
50
|
|
|
|
10
|
my $on = defined( $bool ) ? $bool : 1; |
|
272
|
3
|
50
|
|
|
|
11
|
$self->{content_assert} = $on ? 1 : 0; |
|
273
|
3
|
50
|
|
|
|
23
|
$self->_register_builtin_media_validators() if( $self->{content_assert} ); |
|
274
|
3
|
|
|
|
|
4
|
return( $self ); |
|
275
|
|
|
|
|
|
|
} |
|
276
|
|
|
|
|
|
|
|
|
277
|
|
|
|
|
|
|
# TODO: Backward compatibility, but need to remove it |
|
278
|
|
|
|
|
|
|
{ |
|
279
|
44
|
|
|
44
|
|
351
|
no warnings 'once'; |
|
|
44
|
|
|
|
|
74
|
|
|
|
44
|
|
|
|
|
1271313
|
|
|
280
|
|
|
|
|
|
|
*enable_content_checks = \&content_checks; |
|
281
|
|
|
|
|
|
|
} |
|
282
|
|
|
|
|
|
|
|
|
283
|
21
|
|
|
21
|
1
|
953
|
sub error { $_[0]->{last_error} } |
|
284
|
|
|
|
|
|
|
|
|
285
|
|
|
|
|
|
|
# We return a copy of the array reference containing the error objects |
|
286
|
3
|
|
|
3
|
1
|
403
|
sub errors { return( [@{$_[0]->{errors}}] ); } |
|
|
3
|
|
|
|
|
12
|
|
|
287
|
|
|
|
|
|
|
|
|
288
|
|
|
|
|
|
|
sub extensions |
|
289
|
|
|
|
|
|
|
{ |
|
290
|
1
|
|
|
1
|
1
|
3
|
my( $self, $bool ) = @_; |
|
291
|
1
|
50
|
|
|
|
4
|
my $on = defined( $bool ) ? $bool : 1; |
|
292
|
1
|
|
|
|
|
3
|
$self->{extensions} = $on; |
|
293
|
1
|
|
|
|
|
4
|
$self->unique_keys( $on ); |
|
294
|
1
|
|
|
|
|
23
|
return( $self ); |
|
295
|
|
|
|
|
|
|
} |
|
296
|
|
|
|
|
|
|
|
|
297
|
|
|
|
|
|
|
sub get_trace |
|
298
|
|
|
|
|
|
|
{ |
|
299
|
3
|
|
|
3
|
1
|
508
|
my( $self ) = @_; |
|
300
|
3
|
50
|
|
|
|
7
|
return( [@{ $self->{last_trace} || [] }] ); |
|
|
3
|
|
|
|
|
20
|
|
|
301
|
|
|
|
|
|
|
} |
|
302
|
|
|
|
|
|
|
|
|
303
|
|
|
|
|
|
|
# Accessor-only method. See trace_limit for its mutator alter ego. |
|
304
|
0
|
|
0
|
0
|
1
|
0
|
sub get_trace_limit { 0 + ( $_[0]->{trace_limit} // 0 ) } |
|
305
|
|
|
|
|
|
|
|
|
306
|
|
|
|
|
|
|
# $js->ignore_unknown_required_vocab -> enables it |
|
307
|
|
|
|
|
|
|
# $js->ignore_unknown_required_vocab(1) -> enables it |
|
308
|
|
|
|
|
|
|
# $js->ignore_unknown_required_vocab(0) -> disables it |
|
309
|
|
|
|
|
|
|
sub ignore_unknown_required_vocab |
|
310
|
|
|
|
|
|
|
{ |
|
311
|
0
|
|
|
0
|
1
|
0
|
my( $self, $bool ) = @_; |
|
312
|
0
|
0
|
|
|
|
0
|
my $on = defined( $bool ) ? $bool : 1; |
|
313
|
0
|
|
|
|
|
0
|
$self->{ignore_req_vocab} = $on; |
|
314
|
0
|
|
|
|
|
0
|
return( $self ); |
|
315
|
|
|
|
|
|
|
} |
|
316
|
|
|
|
|
|
|
|
|
317
|
0
|
0
|
|
0
|
1
|
0
|
sub is_compile_enabled { $_[0]->{compile_on} ? 1 : 0 } |
|
318
|
|
|
|
|
|
|
|
|
319
|
0
|
0
|
|
0
|
1
|
0
|
sub is_content_checks_enabled { $_[0]->{content_assert} ? 1 : 0 } |
|
320
|
|
|
|
|
|
|
|
|
321
|
|
|
|
|
|
|
# Accessor only method. See trace or the mutator vession. |
|
322
|
0
|
0
|
|
0
|
1
|
0
|
sub is_trace_on { $_[0]->{trace_on} ? 1 : 0 } |
|
323
|
|
|
|
|
|
|
|
|
324
|
0
|
0
|
|
0
|
1
|
0
|
sub is_unique_keys_enabled { $_[0]->{unique_keys} ? 1 : 0 } |
|
325
|
|
|
|
|
|
|
|
|
326
|
0
|
0
|
|
0
|
1
|
0
|
sub is_unknown_required_vocab_ignored { $_[0]->{ignore_req_vocab} ? 1 : 0 } |
|
327
|
|
|
|
|
|
|
|
|
328
|
|
|
|
|
|
|
sub is_valid |
|
329
|
|
|
|
|
|
|
{ |
|
330
|
2
|
|
|
2
|
1
|
6628
|
my $self = shift( @_ ); |
|
331
|
2
|
|
|
|
|
4
|
my $data = shift( @_ ); |
|
332
|
|
|
|
|
|
|
|
|
333
|
2
|
|
|
|
|
7
|
my $opts = $self->_get_args_as_hash( @_ ); |
|
334
|
|
|
|
|
|
|
# Optional: allow overriding max_errors for this call only |
|
335
|
|
|
|
|
|
|
# (e.g. $v->is_valid( $data, max_errors => 1 );) |
|
336
|
2
|
|
50
|
|
|
29
|
$opts->{max_errors} //= 1; |
|
337
|
|
|
|
|
|
|
|
|
338
|
|
|
|
|
|
|
# validate already populates $self->{errors}; is_valid just returns boolean |
|
339
|
2
|
100
|
|
|
|
8
|
return( $self->validate( $data, $opts ) ? 1 : 0 ); |
|
340
|
|
|
|
|
|
|
} |
|
341
|
|
|
|
|
|
|
|
|
342
|
|
|
|
|
|
|
# Example: |
|
343
|
|
|
|
|
|
|
# my $pruned = $js->prune_instance( $incoming_data ); |
|
344
|
|
|
|
|
|
|
sub prune_instance |
|
345
|
|
|
|
|
|
|
{ |
|
346
|
5
|
|
|
5
|
1
|
56
|
my( $self, $data ) = @_; |
|
347
|
|
|
|
|
|
|
|
|
348
|
|
|
|
|
|
|
# Work on a cloned copy if normalize_instance is on, |
|
349
|
|
|
|
|
|
|
# to remain consistent with validate(). |
|
350
|
5
|
50
|
|
|
|
12
|
if( $self->{normalize_instance} ) |
|
351
|
|
|
|
|
|
|
{ |
|
352
|
5
|
|
|
|
|
14
|
$data = _clone( $data ); |
|
353
|
|
|
|
|
|
|
} |
|
354
|
|
|
|
|
|
|
|
|
355
|
5
|
|
|
|
|
21
|
return( $self->_prune_with_schema( $self->{schema}, $data ) ); |
|
356
|
|
|
|
|
|
|
} |
|
357
|
|
|
|
|
|
|
|
|
358
|
|
|
|
|
|
|
sub prune_unknown |
|
359
|
|
|
|
|
|
|
{ |
|
360
|
0
|
|
|
0
|
1
|
0
|
my( $self, $bool ) = @_; |
|
361
|
0
|
0
|
|
|
|
0
|
my $on = defined( $bool ) ? $bool : 1; |
|
362
|
0
|
0
|
|
|
|
0
|
$self->{prune_unknown} = $on ? 1 : 0; |
|
363
|
0
|
|
|
|
|
0
|
return( $self ); |
|
364
|
|
|
|
|
|
|
} |
|
365
|
|
|
|
|
|
|
|
|
366
|
|
|
|
|
|
|
sub register_builtin_formats |
|
367
|
|
|
|
|
|
|
{ |
|
368
|
47
|
|
|
47
|
1
|
87
|
my( $self ) = @_; |
|
369
|
|
|
|
|
|
|
|
|
370
|
47
|
|
|
|
|
71
|
local $@; |
|
371
|
47
|
50
|
|
|
|
94
|
my $has_iso = eval{ require DateTime::Format::ISO8601; 1 } ? 1 : 0; |
|
|
47
|
|
|
|
|
6539
|
|
|
|
0
|
|
|
|
|
0
|
|
|
372
|
47
|
50
|
|
|
|
135
|
my $has_tp = eval{ require Time::Piece; 1 } ? 1 : 0; |
|
|
47
|
|
|
|
|
4716
|
|
|
|
47
|
|
|
|
|
95311
|
|
|
373
|
47
|
50
|
|
|
|
76
|
my $has_dt = eval{ require DateTime; 1 } ? 1 : 0; |
|
|
47
|
|
|
|
|
7684
|
|
|
|
47
|
|
|
|
|
4325591
|
|
|
374
|
47
|
50
|
|
|
|
122
|
my $has_idn = eval{ require Net::IDN::Encode; 1 } ? 1 : 0; |
|
|
47
|
|
|
|
|
7018
|
|
|
|
0
|
|
|
|
|
0
|
|
|
375
|
|
|
|
|
|
|
# perl -MRegexp::Common=Email::Address -lE 'say $Regexp::Common::RE{Email}{Address}' |
|
376
|
47
|
|
|
|
|
190
|
state $email_re = qr/\A(?:(?^u:(?:(?^u:(?>(?^u:(?^u:(?>(?^u:(?>(?^u:(?>(?^u:(?^u:(?>\s*\((?:\s*(?^u:(?^u:(?>[^()\\]+))|(?^u:\\(?^u:[^\x0A\x0D]))|))*\s*\)\s*))|(?>\s+))*[^\x00-\x1F\x7F()<>\[\]:;@\\,."\s]+(?^u:(?^u:(?>\s*\((?:\s*(?^u:(?^u:(?>[^()\\]+))|(?^u:\\(?^u:[^\x0A\x0D]))|))*\s*\)\s*))|(?>\s+))*))|\.|\s*"(?^u:(?^u:[^\\"])|(?^u:\\(?^u:[^\x0A\x0D])))+"\s*))+))|(?>(?^u:(?^u:(?>(?^u:(?^u:(?>\s*\((?:\s*(?^u:(?^u:(?>[^()\\]+))|(?^u:\\(?^u:[^\x0A\x0D]))|))*\s*\)\s*))|(?>\s+))*[^\x00-\x1F\x7F()<>\[\]:;@\\,."\s]+(?^u:(?^u:(?>\s*\((?:\s*(?^u:(?^u:(?>[^()\\]+))|(?^u:\\(?^u:[^\x0A\x0D]))|))*\s*\)\s*))|(?>\s+))*))|(?^u:(?>(?^u:(?^u:(?>\s*\((?:\s*(?^u:(?^u:(?>[^()\\]+))|(?^u:\\(?^u:[^\x0A\x0D]))|))*\s*\)\s*))|(?>\s+))*"(?^u:(?^u:[^\\"])|(?^u:\\(?^u:[^\x0A\x0D])))*"(?^u:(?^u:(?>\s*\((?:\s*(?^u:(?^u:(?>[^()\\]+))|(?^u:\\(?^u:[^\x0A\x0D]))|))*\s*\)\s*))|(?>\s+))*)))+))?)(?^u:(?>(?^u:(?^u:(?>\s*\((?:\s*(?^u:(?^u:(?>[^()\\]+))|(?^u:\\(?^u:[^\x0A\x0D]))|))*\s*\)\s*))|(?>\s+))*<(?^u:(?^u:(?^u:(?>(?^u:(?^u:(?>\s*\((?:\s*(?^u:(?^u:(?>[^()\\]+))|(?^u:\\(?^u:[^\x0A\x0D]))|))*\s*\)\s*))|(?>\s+))*(?^u:(?>[^\x00-\x1F\x7F()<>\[\]:;@\\,."\s]+(?:\.[^\x00-\x1F\x7F()<>\[\]:;@\\,."\s]+)*))(?^u:(?^u:(?>\s*\((?:\s*(?^u:(?^u:(?>[^()\\]+))|(?^u:\\(?^u:[^\x0A\x0D]))|))*\s*\)\s*))|(?>\s+))*))|(?^u:(?>(?^u:(?^u:(?>\s*\((?:\s*(?^u:(?^u:(?>[^()\\]+))|(?^u:\\(?^u:[^\x0A\x0D]))|))*\s*\)\s*))|(?>\s+))*"(?^u:(?^u:[^\\"])|(?^u:\\(?^u:[^\x0A\x0D])))*"(?^u:(?^u:(?>\s*\((?:\s*(?^u:(?^u:(?>[^()\\]+))|(?^u:\\(?^u:[^\x0A\x0D]))|))*\s*\)\s*))|(?>\s+))*)))\@(?^u:(?^u:(?>(?^u:(?^u:(?>\s*\((?:\s*(?^u:(?^u:(?>[^()\\]+))|(?^u:\\(?^u:[^\x0A\x0D]))|))*\s*\)\s*))|(?>\s+))*(?^u:(?>[^\x00-\x1F\x7F()<>\[\]:;@\\,."\s]+(?:\.[^\x00-\x1F\x7F()<>\[\]:;@\\,."\s]+)*))(?^u:(?^u:(?>\s*\((?:\s*(?^u:(?^u:(?>[^()\\]+))|(?^u:\\(?^u:[^\x0A\x0D]))|))*\s*\)\s*))|(?>\s+))*))|(?^u:(?>(?^u:(?^u:(?>\s*\((?:\s*(?^u:(?^u:(?>[^()\\]+))|(?^u:\\(?^u:[^\x0A\x0D]))|))*\s*\)\s*))|(?>\s+))*\[(?:\s*(?^u:(?^u:[^\[\]\\])|(?^u:\\(?^u:[^\x0A\x0D]))))*\s*\](?^u:(?^u:(?>\s*\((?:\s*(?^u:(?^u:(?>[^()\\]+))|(?^u:\\(?^u:[^\x0A\x0D]))|))*\s*\)\s*))|(?>\s+))*))))>(?^u:(?^u:(?>\s*\((?:\s*(?^u:(?^u:(?>[^()\\]+))|(?^u:\\(?^u:[^\x0A\x0D]))|))*\s*\)\s*))|(?>\s+))*)))|(?^u:(?^u:(?^u:(?>(?^u:(?^u:(?>\s*\((?:\s*(?^u:(?^u:(?>[^()\\]+))|(?^u:\\(?^u:[^\x0A\x0D]))|))*\s*\)\s*))|(?>\s+))*(?^u:(?>[^\x00-\x1F\x7F()<>\[\]:;@\\,."\s]+(?:\.[^\x00-\x1F\x7F()<>\[\]:;@\\,."\s]+)*))(?^u:(?^u:(?>\s*\((?:\s*(?^u:(?^u:(?>[^()\\]+))|(?^u:\\(?^u:[^\x0A\x0D]))|))*\s*\)\s*))|(?>\s+))*))|(?^u:(?>(?^u:(?^u:(?>\s*\((?:\s*(?^u:(?^u:(?>[^()\\]+))|(?^u:\\(?^u:[^\x0A\x0D]))|))*\s*\)\s*))|(?>\s+))*"(?^u:(?^u:[^\\"])|(?^u:\\(?^u:[^\x0A\x0D])))*"(?^u:(?^u:(?>\s*\((?:\s*(?^u:(?^u:(?>[^()\\]+))|(?^u:\\(?^u:[^\x0A\x0D]))|))*\s*\)\s*))|(?>\s+))*)))\@(?^u:(?^u:(?>(?^u:(?^u:(?>\s*\((?:\s*(?^u:(?^u:(?>[^()\\]+))|(?^u:\\(?^u:[^\x0A\x0D]))|))*\s*\)\s*))|(?>\s+))*(?^u:(?>[^\x00-\x1F\x7F()<>\[\]:;@\\,."\s]+(?:\.[^\x00-\x1F\x7F()<>\[\]:;@\\,."\s]+)*))(?^u:(?^u:(?>\s*\((?:\s*(?^u:(?^u:(?>[^()\\]+))|(?^u:\\(?^u:[^\x0A\x0D]))|))*\s*\)\s*))|(?>\s+))*))|(?^u:(?>(?^u:(?^u:(?>\s*\((?:\s*(?^u:(?^u:(?>[^()\\]+))|(?^u:\\(?^u:[^\x0A\x0D]))|))*\s*\)\s*))|(?>\s+))*\[(?:\s*(?^u:(?^u:[^\[\]\\])|(?^u:\\(?^u:[^\x0A\x0D]))))*\s*\](?^u:(?^u:(?>\s*\((?:\s*(?^u:(?^u:(?>[^()\\]+))|(?^u:\\(?^u:[^\x0A\x0D]))|))*\s*\)\s*))|(?>\s+))*)))))(?>(?^u:(?>\s*\((?:\s*(?^u:(?^u:(?>[^()\\]+))|(?^u:\\(?^u:[^\x0A\x0D]))|))*\s*\)\s*))*)))\z/; |
|
377
|
|
|
|
|
|
|
|
|
378
|
47
|
|
|
|
|
88
|
my %F; |
|
379
|
|
|
|
|
|
|
|
|
380
|
|
|
|
|
|
|
# RFC3339 date-time / date / time |
|
381
|
|
|
|
|
|
|
$F{'date-time'} = sub |
|
382
|
|
|
|
|
|
|
{ |
|
383
|
2
|
|
|
2
|
|
4
|
my( $s ) = @_; |
|
384
|
2
|
50
|
33
|
|
|
47
|
return(0) unless( defined( $s ) && !ref( $s ) ); |
|
385
|
|
|
|
|
|
|
|
|
386
|
|
|
|
|
|
|
# Preferred path when DateTime::Format::ISO8601 is available |
|
387
|
2
|
50
|
|
|
|
6
|
if( $has_iso ) |
|
388
|
|
|
|
|
|
|
{ |
|
389
|
0
|
0
|
|
|
|
0
|
return( eval{ DateTime::Format::ISO8601->parse_datetime( $s ) ? 1 : 0 } ? 1 : 0 ); |
|
|
0
|
0
|
|
|
|
0
|
|
|
390
|
|
|
|
|
|
|
} |
|
391
|
|
|
|
|
|
|
|
|
392
|
|
|
|
|
|
|
# YYYY-MM-DDThh:mm:ss[.fraction](Z|±hh:mm) |
|
393
|
2
|
50
|
|
|
|
24
|
return(0) unless( $s =~ /\A |
|
394
|
|
|
|
|
|
|
(\d{4})-(\d{2})-(\d{2}) # date |
|
395
|
|
|
|
|
|
|
T |
|
396
|
|
|
|
|
|
|
(\d{2}):(\d{2}):(\d{2}) # time |
|
397
|
|
|
|
|
|
|
(?:\.\d+)? # optional fraction |
|
398
|
|
|
|
|
|
|
(?:Z|[+\-]\d{2}:\d{2}) # offset |
|
399
|
|
|
|
|
|
|
\z/x ); |
|
400
|
|
|
|
|
|
|
|
|
401
|
2
|
|
|
|
|
28
|
my( $y, $m, $d, $H, $M, $S ) = ( $1, $2, $3, $4, $5, $6 ); |
|
402
|
|
|
|
|
|
|
|
|
403
|
|
|
|
|
|
|
# Time::Piece is a core module (available since perl 5.10): use it first |
|
404
|
2
|
50
|
|
|
|
5
|
if( $has_tp ) |
|
405
|
|
|
|
|
|
|
{ |
|
406
|
|
|
|
|
|
|
# strptime validates calendar correctness (e.g. rejects Feb 30) |
|
407
|
|
|
|
|
|
|
# We feed it the bare datetime without offset/fraction (already captured above) |
|
408
|
2
|
|
|
|
|
15
|
my $bare = sprintf( '%04d-%02d-%02dT%02d:%02d:%02d', $y, $m, $d, $H, $M, $S ); |
|
409
|
2
|
100
|
|
|
|
5
|
return( eval{ Time::Piece->strptime( $bare, '%Y-%m-%dT%H:%M:%S' ); 1 } ? 1 : 0 ); |
|
|
2
|
|
|
|
|
20
|
|
|
|
1
|
|
|
|
|
152
|
|
|
410
|
|
|
|
|
|
|
} |
|
411
|
|
|
|
|
|
|
|
|
412
|
|
|
|
|
|
|
# Fall back to DateTime if available |
|
413
|
0
|
0
|
|
|
|
0
|
if( $has_dt ) |
|
414
|
|
|
|
|
|
|
{ |
|
415
|
|
|
|
|
|
|
return( eval |
|
416
|
0
|
0
|
|
|
|
0
|
{ |
|
417
|
0
|
|
|
|
|
0
|
DateTime->new( |
|
418
|
|
|
|
|
|
|
year => $y, |
|
419
|
|
|
|
|
|
|
month => $m, |
|
420
|
|
|
|
|
|
|
day => $d, |
|
421
|
|
|
|
|
|
|
hour => $H, |
|
422
|
|
|
|
|
|
|
minute => $M, |
|
423
|
|
|
|
|
|
|
second => $S |
|
424
|
|
|
|
|
|
|
); |
|
425
|
0
|
|
|
|
|
0
|
1 |
|
426
|
|
|
|
|
|
|
} ? 1 : 0 ); |
|
427
|
|
|
|
|
|
|
} |
|
428
|
|
|
|
|
|
|
|
|
429
|
|
|
|
|
|
|
# Last resort: the regex above was already sufficient for structural checks |
|
430
|
0
|
|
|
|
|
0
|
return(1); |
|
431
|
47
|
|
|
|
|
422
|
}; |
|
432
|
|
|
|
|
|
|
|
|
433
|
|
|
|
|
|
|
$F{'date'} = sub |
|
434
|
|
|
|
|
|
|
{ |
|
435
|
2
|
|
|
2
|
|
5
|
my( $s ) = @_; |
|
436
|
2
|
50
|
33
|
|
|
12
|
return(0) unless( defined( $s ) && !ref( $s ) ); |
|
437
|
2
|
50
|
|
|
|
15
|
return(0) unless( $s =~ /\A(\d{4})-(\d{2})-(\d{2})\z/ ); |
|
438
|
2
|
|
|
|
|
13
|
my( $y, $m, $d ) = ( $1, $2, $3 ); |
|
439
|
|
|
|
|
|
|
|
|
440
|
|
|
|
|
|
|
# Time::Piece is core — prefer it |
|
441
|
2
|
50
|
|
|
|
7
|
if( $has_tp ) |
|
442
|
|
|
|
|
|
|
{ |
|
443
|
2
|
100
|
|
|
|
3
|
return( eval{ Time::Piece->strptime( $s, '%Y-%m-%d' ); 1 } ? 1 : 0 ); |
|
|
2
|
|
|
|
|
14
|
|
|
|
1
|
|
|
|
|
69
|
|
|
444
|
|
|
|
|
|
|
} |
|
445
|
|
|
|
|
|
|
|
|
446
|
|
|
|
|
|
|
# Fall back to DateTime if available |
|
447
|
0
|
0
|
|
|
|
0
|
if( $has_dt ) |
|
448
|
|
|
|
|
|
|
{ |
|
449
|
0
|
0
|
|
|
|
0
|
return( eval{ DateTime->new( year => $y, month => $m, day => $d ); 1 } ? 1 : 0 ); |
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
450
|
|
|
|
|
|
|
} |
|
451
|
|
|
|
|
|
|
|
|
452
|
|
|
|
|
|
|
# Last resort: structural regex already passed above |
|
453
|
0
|
|
|
|
|
0
|
return(1); |
|
454
|
47
|
|
|
|
|
263
|
}; |
|
455
|
|
|
|
|
|
|
|
|
456
|
|
|
|
|
|
|
$F{'time'} = sub |
|
457
|
|
|
|
|
|
|
{ |
|
458
|
2
|
|
|
2
|
|
5
|
my( $s ) = @_; |
|
459
|
2
|
50
|
33
|
|
|
11
|
return(0) unless( defined( $s ) && !ref( $s ) ); |
|
460
|
2
|
50
|
|
|
|
20
|
if( $has_iso ) |
|
461
|
|
|
|
|
|
|
{ |
|
462
|
0
|
0
|
|
|
|
0
|
return eval{ DateTime::Format::ISO8601->parse_datetime( "1970-01-01T$s" ) ? 1 : 0 } ? 1 : 0; |
|
|
0
|
0
|
|
|
|
0
|
|
|
463
|
|
|
|
|
|
|
} |
|
464
|
2
|
100
|
|
|
|
18
|
return $s =~ /\A |
|
465
|
|
|
|
|
|
|
(?:[01]\d|2[0-3]) # HH |
|
466
|
|
|
|
|
|
|
: |
|
467
|
|
|
|
|
|
|
(?:[0-5]\d) # MM |
|
468
|
|
|
|
|
|
|
: |
|
469
|
|
|
|
|
|
|
(?:[0-5]\d) # SS |
|
470
|
|
|
|
|
|
|
(?:\.\d+)? # .fraction |
|
471
|
|
|
|
|
|
|
(?:Z|[+\-](?:[01]\d|2[0-3]):[0-5]\d)? # offset |
|
472
|
|
|
|
|
|
|
\z/x ? 1 : 0; |
|
473
|
47
|
|
|
|
|
200
|
}; |
|
474
|
|
|
|
|
|
|
|
|
475
|
|
|
|
|
|
|
# Duration (RFC 3339 §5.6 / ISO 8601) |
|
476
|
|
|
|
|
|
|
# Valid: P1Y, P3M, P1Y2M3DT4H5M6S, PT10S, P1W |
|
477
|
|
|
|
|
|
|
# Invalid: P, PT, P1Y2M3DT (T with nothing after it, or no designator at all) |
|
478
|
|
|
|
|
|
|
$F{'duration'} = sub |
|
479
|
|
|
|
|
|
|
{ |
|
480
|
2
|
|
|
2
|
|
5
|
my( $s ) = @_; |
|
481
|
2
|
50
|
33
|
|
|
27
|
return(0) unless( defined( $s ) && !ref( $s ) ); |
|
482
|
|
|
|
|
|
|
|
|
483
|
|
|
|
|
|
|
# Week form: PnW (stands alone; no mixing with other designators per ISO 8601) |
|
484
|
2
|
50
|
|
|
|
9
|
return(1) if( $s =~ /\AP\d+W\z/ ); |
|
485
|
|
|
|
|
|
|
|
|
486
|
|
|
|
|
|
|
# Full form: at least one date *or* time component must be present |
|
487
|
|
|
|
|
|
|
# The T separator must be followed by at least one time designator |
|
488
|
2
|
100
|
66
|
|
|
36
|
return( $s =~ /\A |
|
489
|
|
|
|
|
|
|
P |
|
490
|
|
|
|
|
|
|
(?:(\d+)Y)? |
|
491
|
|
|
|
|
|
|
(?:(\d+)M)? |
|
492
|
|
|
|
|
|
|
(?:(\d+)D)? |
|
493
|
|
|
|
|
|
|
(?:T |
|
494
|
|
|
|
|
|
|
(?=\d) # T must be followed by at least one digit |
|
495
|
|
|
|
|
|
|
(?:(\d+)H)? |
|
496
|
|
|
|
|
|
|
(?:(\d+)M)? |
|
497
|
|
|
|
|
|
|
(?:(\d+(?:\.\d+)?)S)? |
|
498
|
|
|
|
|
|
|
)? |
|
499
|
|
|
|
|
|
|
\z/x && ( defined($1) || defined($2) || defined($3) || defined($4) || defined($5) || defined($6) ) |
|
500
|
|
|
|
|
|
|
? 1 : 0 ); |
|
501
|
47
|
|
|
|
|
195
|
}; |
|
502
|
|
|
|
|
|
|
|
|
503
|
|
|
|
|
|
|
# Email / IDN email |
|
504
|
|
|
|
|
|
|
# Plain email (ASCII) — unchanged |
|
505
|
|
|
|
|
|
|
$F{'email'} = sub |
|
506
|
|
|
|
|
|
|
{ |
|
507
|
13
|
|
|
13
|
|
27
|
my( $s ) = @_; |
|
508
|
13
|
50
|
33
|
|
|
75
|
return(0) unless( defined( $s ) && !ref( $s ) ); |
|
509
|
13
|
100
|
|
|
|
454
|
return( $s =~ $email_re ? 1 : 0 ); |
|
510
|
47
|
|
|
|
|
205
|
}; |
|
511
|
|
|
|
|
|
|
|
|
512
|
|
|
|
|
|
|
# IDN email: punycode the domain, validate with same regex |
|
513
|
|
|
|
|
|
|
$F{'idn-email'} = sub |
|
514
|
|
|
|
|
|
|
{ |
|
515
|
1
|
|
|
1
|
|
3
|
my( $s ) = @_; |
|
516
|
1
|
50
|
33
|
|
|
20
|
return(0) unless( defined( $s ) && !ref( $s ) ); |
|
517
|
1
|
50
|
|
|
|
10
|
return(0) unless( $s =~ /\A(.+)\@(.+)\z/s ); # keep local-part as-is (EAI allows UTF-8) |
|
518
|
1
|
|
|
|
|
6
|
my( $local, $domain ) = ( $1, $2 ); |
|
519
|
|
|
|
|
|
|
|
|
520
|
1
|
50
|
|
|
|
4
|
if( $has_idn ) |
|
521
|
|
|
|
|
|
|
{ |
|
522
|
0
|
|
|
|
|
0
|
local $@; |
|
523
|
0
|
|
|
|
|
0
|
my $ascii = eval{ Net::IDN::Encode::domain_to_ascii( $domain ) }; |
|
|
0
|
|
|
|
|
0
|
|
|
524
|
0
|
0
|
0
|
|
|
0
|
return(0) unless( defined( $ascii ) && length( $ascii ) ); |
|
525
|
|
|
|
|
|
|
|
|
526
|
0
|
|
|
|
|
0
|
my $candidate = "$local\@$ascii"; |
|
527
|
0
|
0
|
|
|
|
0
|
return( $candidate =~ $email_re ? 1 : 0 ); |
|
528
|
|
|
|
|
|
|
} |
|
529
|
|
|
|
|
|
|
|
|
530
|
|
|
|
|
|
|
# Fallback: if the domain already *looks* ASCII, validate directly |
|
531
|
1
|
50
|
|
|
|
7
|
if( $domain =~ /\A[[:ascii:]]+\z/ ) |
|
532
|
|
|
|
|
|
|
{ |
|
533
|
1
|
|
|
|
|
3
|
my $candidate = "$local\@$domain"; |
|
534
|
1
|
50
|
|
|
|
24
|
return( $candidate =~ $email_re ? 1 : 0 ); |
|
535
|
|
|
|
|
|
|
} |
|
536
|
|
|
|
|
|
|
|
|
537
|
|
|
|
|
|
|
# Without IDN module, fall back to permissive Unicode domain check + ASCII regex |
|
538
|
0
|
|
|
|
|
0
|
return(0); |
|
539
|
47
|
|
|
|
|
232
|
}; |
|
540
|
|
|
|
|
|
|
|
|
541
|
|
|
|
|
|
|
# Hostnames |
|
542
|
|
|
|
|
|
|
$F{'hostname'} = sub |
|
543
|
|
|
|
|
|
|
{ |
|
544
|
2
|
|
|
2
|
|
6
|
my( $s ) = @_; |
|
545
|
2
|
50
|
33
|
|
|
10
|
return(0) unless( defined( $s ) && !ref( $s ) ); |
|
546
|
2
|
50
|
|
|
|
22
|
return(0) if( length( $s ) > 253 ); |
|
547
|
2
|
|
|
|
|
9
|
for my $label ( split( /\./, $s ) ) |
|
548
|
|
|
|
|
|
|
{ |
|
549
|
3
|
50
|
33
|
|
|
19
|
return(0) unless( length( $label ) >= 1 && length( $label ) <= 63 ); |
|
550
|
3
|
100
|
|
|
|
21
|
return(0) unless( $label =~ /\A[a-zA-Z0-9](?:[a-zA-Z0-9\-]*[a-zA-Z0-9])?\z/ ); |
|
551
|
|
|
|
|
|
|
} |
|
552
|
1
|
|
|
|
|
4
|
return(1); |
|
553
|
47
|
|
|
|
|
247
|
}; |
|
554
|
|
|
|
|
|
|
|
|
555
|
|
|
|
|
|
|
$F{'idn-hostname'} = sub |
|
556
|
|
|
|
|
|
|
{ |
|
557
|
1
|
|
|
1
|
|
3
|
my( $s ) = @_; |
|
558
|
1
|
50
|
33
|
|
|
34
|
return(0) unless( defined( $s ) && !ref( $s ) ); |
|
559
|
1
|
50
|
|
|
|
5
|
if( $has_idn ) |
|
560
|
|
|
|
|
|
|
{ |
|
561
|
0
|
|
|
|
|
0
|
local $@; |
|
562
|
0
|
|
|
|
|
0
|
my $ascii = eval{ Net::IDN::Encode::domain_to_ascii( $s ) }; |
|
|
0
|
|
|
|
|
0
|
|
|
563
|
0
|
0
|
0
|
|
|
0
|
return(0) unless( defined( $ascii ) && length( $ascii ) ); |
|
564
|
0
|
0
|
|
|
|
0
|
return( $F{'hostname'}->( $ascii ) ? 1 : 0 ); |
|
565
|
|
|
|
|
|
|
} |
|
566
|
|
|
|
|
|
|
|
|
567
|
|
|
|
|
|
|
# Fallback: permissive Unicode label check (as you had), then ASCII hostname rule |
|
568
|
1
|
50
|
|
|
|
14
|
return(0) if( length( $s ) > 253 ); |
|
569
|
1
|
|
|
|
|
6
|
for my $label ( split( /\./, $s ) ) |
|
570
|
|
|
|
|
|
|
{ |
|
571
|
2
|
50
|
33
|
|
|
11
|
return(0) unless( length( $label ) >= 1 && length( $label ) <= 63 ); |
|
572
|
2
|
50
|
|
|
|
12
|
return(0) unless( $label =~ /\A[[:alnum:]\pL\pN](?:[[:alnum:]\pL\pN\-]*[[:alnum:]\pL\pN])?\z/u ); |
|
573
|
|
|
|
|
|
|
} |
|
574
|
1
|
|
|
|
|
5
|
return(1); |
|
575
|
47
|
|
|
|
|
353
|
}; |
|
576
|
|
|
|
|
|
|
|
|
577
|
|
|
|
|
|
|
# IP addresses |
|
578
|
|
|
|
|
|
|
$F{'ipv4'} = sub |
|
579
|
|
|
|
|
|
|
{ |
|
580
|
2
|
|
|
2
|
|
5
|
my( $s ) = @_; |
|
581
|
2
|
50
|
33
|
|
|
11
|
return(0) unless( defined( $s ) && !ref( $s ) ); |
|
582
|
2
|
100
|
|
|
|
44
|
return $s =~ /\A |
|
583
|
|
|
|
|
|
|
(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\. |
|
584
|
|
|
|
|
|
|
(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\. |
|
585
|
|
|
|
|
|
|
(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\. |
|
586
|
|
|
|
|
|
|
(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d) |
|
587
|
|
|
|
|
|
|
\z/x ? 1 : 0; |
|
588
|
47
|
|
|
|
|
216
|
}; |
|
589
|
|
|
|
|
|
|
|
|
590
|
|
|
|
|
|
|
$F{'ipv6'} = sub |
|
591
|
|
|
|
|
|
|
{ |
|
592
|
2
|
|
|
2
|
|
4
|
my( $s ) = @_; |
|
593
|
2
|
50
|
33
|
|
|
26
|
return(0) unless( defined( $s ) && !ref( $s ) ); |
|
594
|
2
|
100
|
|
|
|
33
|
return $s =~ /\A |
|
595
|
|
|
|
|
|
|
(?: (?:[0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4} |
|
596
|
|
|
|
|
|
|
| (?:[0-9A-Fa-f]{1,4}:){1,7}: |
|
597
|
|
|
|
|
|
|
| (?:[0-9A-Fa-f]{1,4}:){1,6}:[0-9A-Fa-f]{1,4} |
|
598
|
|
|
|
|
|
|
| (?:[0-9A-Fa-f]{1,4}:){1,5}(?::[0-9A-Fa-f]{1,4}){1,2} |
|
599
|
|
|
|
|
|
|
| (?:[0-9A-Fa-f]{1,4}:){1,4}(?::[0-9A-Fa-f]{1,4}){1,3} |
|
600
|
|
|
|
|
|
|
| (?:[0-9A-Fa-f]{1,4}:){1,3}(?::[0-9A-Fa-f]{1,4}){1,4} |
|
601
|
|
|
|
|
|
|
| (?:[0-9A-Fa-f]{1,4}:){1,2}(?::[0-9A-Fa-f]{1,4}){1,5} |
|
602
|
|
|
|
|
|
|
| [0-9A-Fa-f]{1,4}:(?:(?::[0-9A-Fa-f]{1,4}){1,6}) |
|
603
|
|
|
|
|
|
|
| :(?:(?::[0-9A-Fa-f]{1,4}){1,7}|:) |
|
604
|
|
|
|
|
|
|
| (?:[0-9A-Fa-f]{1,4}:){6} |
|
605
|
|
|
|
|
|
|
(?:\d{1,3}\.){3}\d{1,3} |
|
606
|
|
|
|
|
|
|
| ::(?:[0-9A-Fa-f]{1,4}:){0,5} |
|
607
|
|
|
|
|
|
|
(?:\d{1,3}\.){3}\d{1,3} |
|
608
|
|
|
|
|
|
|
| (?:[0-9A-Fa-f]{1,4}:){1,5}: |
|
609
|
|
|
|
|
|
|
(?:\d{1,3}\.){3}\d{1,3} |
|
610
|
|
|
|
|
|
|
) |
|
611
|
|
|
|
|
|
|
\z/x ? 1 : 0; |
|
612
|
47
|
|
|
|
|
314
|
}; |
|
613
|
|
|
|
|
|
|
|
|
614
|
|
|
|
|
|
|
# URI/IRI |
|
615
|
|
|
|
|
|
|
$F{'uri'} = sub |
|
616
|
|
|
|
|
|
|
{ |
|
617
|
2
|
|
|
2
|
|
6
|
my( $s ) = @_; |
|
618
|
2
|
50
|
33
|
|
|
48
|
return(0) unless( defined( $s ) && !ref( $s ) ); |
|
619
|
2
|
100
|
|
|
|
16
|
return( $s =~ /\A[A-Za-z][A-Za-z0-9+\-.]*:[^\s]+\z/ ? 1 : 0 ); |
|
620
|
47
|
|
|
|
|
355
|
}; |
|
621
|
|
|
|
|
|
|
|
|
622
|
|
|
|
|
|
|
$F{'uri-reference'} = sub |
|
623
|
|
|
|
|
|
|
{ |
|
624
|
1
|
|
|
1
|
|
3
|
my( $s ) = @_; |
|
625
|
1
|
50
|
33
|
|
|
21
|
return(0) unless( defined( $s ) && !ref( $s ) ); |
|
626
|
1
|
50
|
|
|
|
10
|
return( $s =~ /\A(?:[A-Za-z][A-Za-z0-9+\-.]*:)?[^\s]+\z/ ? 1 : 0 ); |
|
627
|
47
|
|
|
|
|
265
|
}; |
|
628
|
|
|
|
|
|
|
|
|
629
|
|
|
|
|
|
|
$F{'iri'} = sub |
|
630
|
|
|
|
|
|
|
{ |
|
631
|
1
|
|
|
1
|
|
3
|
my( $s ) = @_; |
|
632
|
1
|
50
|
33
|
|
|
8
|
return(0) unless( defined( $s ) && !ref( $s ) ); |
|
633
|
|
|
|
|
|
|
# Must have a scheme (letters followed by colon) per RFC 3987 §2.2 |
|
634
|
|
|
|
|
|
|
# Scheme: ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) |
|
635
|
|
|
|
|
|
|
# After scheme: at least one non-whitespace character |
|
636
|
1
|
50
|
|
|
|
13
|
return( $s =~ /\A[A-Za-z][A-Za-z0-9+\-.]*:[^\s]+\z/u ? 1 : 0 ); |
|
637
|
47
|
|
|
|
|
251
|
}; |
|
638
|
|
|
|
|
|
|
|
|
639
|
|
|
|
|
|
|
# UUID |
|
640
|
|
|
|
|
|
|
$F{'uuid'} = sub |
|
641
|
|
|
|
|
|
|
{ |
|
642
|
3
|
|
|
3
|
|
7
|
my( $s ) = @_; |
|
643
|
3
|
50
|
33
|
|
|
18
|
return(0) unless( defined( $s ) && !ref( $s ) ); |
|
644
|
3
|
100
|
|
|
|
19
|
return( $s =~ /\A[a-fA-F0-9]{8}\-[a-fA-F0-9]{4}\-[a-fA-F0-9]{4}\-[a-fA-F0-9]{4}\-[a-fA-F0-9]{12}\z/ ? 1 : 0 ); |
|
645
|
47
|
|
|
|
|
193
|
}; |
|
646
|
|
|
|
|
|
|
|
|
647
|
|
|
|
|
|
|
# JSON Pointer / Relative JSON Pointer |
|
648
|
|
|
|
|
|
|
$F{'json-pointer'} = sub |
|
649
|
|
|
|
|
|
|
{ |
|
650
|
3
|
|
|
3
|
|
8
|
my( $s ) = @_; |
|
651
|
3
|
50
|
33
|
|
|
14
|
return(0) unless( defined( $s ) && !ref( $s ) ); |
|
652
|
3
|
100
|
|
|
|
11
|
return(1) if( $s eq '' ); |
|
653
|
2
|
100
|
|
|
|
21
|
return( $s =~ m{\A/(?:[^~/]|~[01])*(?:/(?:[^~/]|~[01])*)*\z} ? 1 : 0 ); |
|
654
|
47
|
|
|
|
|
222
|
}; |
|
655
|
|
|
|
|
|
|
|
|
656
|
|
|
|
|
|
|
$F{'relative-json-pointer'} = sub |
|
657
|
|
|
|
|
|
|
{ |
|
658
|
3
|
|
|
3
|
|
9
|
my( $s ) = @_; |
|
659
|
3
|
50
|
33
|
|
|
18
|
return(0) unless( defined( $s ) && !ref( $s ) ); |
|
660
|
3
|
100
|
|
|
|
13
|
return(1) if( $s =~ /\A0\z/ ); |
|
661
|
2
|
100
|
|
|
|
35
|
return( $s =~ m,\A[1-9]\d*(?:#|(?:/(?:[^~/]|~[01])*)*)\z, ? 1 : 0 ); |
|
662
|
47
|
|
|
|
|
247
|
}; |
|
663
|
|
|
|
|
|
|
|
|
664
|
|
|
|
|
|
|
# Regex |
|
665
|
|
|
|
|
|
|
$F{'regex'} = sub |
|
666
|
|
|
|
|
|
|
{ |
|
667
|
2
|
|
|
2
|
|
6
|
my( $s ) = @_; |
|
668
|
2
|
50
|
33
|
|
|
11
|
return(0) unless( defined( $s ) && !ref( $s ) ); |
|
669
|
2
|
|
|
|
|
4
|
local $@; |
|
670
|
2
|
100
|
|
|
|
3
|
return( eval{ "" =~ /$s/; 1 } ? 1 : 0 ); |
|
|
2
|
|
|
|
|
101
|
|
|
|
1
|
|
|
|
|
8
|
|
|
671
|
47
|
|
|
|
|
241
|
}; |
|
672
|
|
|
|
|
|
|
|
|
673
|
47
|
|
|
|
|
255
|
while( my( $k, $v ) = each( %F ) ) |
|
674
|
|
|
|
|
|
|
{ |
|
675
|
799
|
50
|
|
|
|
3157
|
$self->{formats}->{ $k } = $v unless( exists( $self->{formats}->{ $k } ) ); |
|
676
|
|
|
|
|
|
|
} |
|
677
|
|
|
|
|
|
|
|
|
678
|
47
|
|
|
|
|
262
|
return( $self ); |
|
679
|
|
|
|
|
|
|
} |
|
680
|
|
|
|
|
|
|
|
|
681
|
|
|
|
|
|
|
sub register_content_decoder |
|
682
|
|
|
|
|
|
|
{ |
|
683
|
1
|
|
|
1
|
1
|
11
|
my( $self, $name, $cb ) = @_; |
|
684
|
1
|
50
|
|
|
|
3
|
if( ref( $cb ) eq 'CODE' ) |
|
685
|
|
|
|
|
|
|
{ |
|
686
|
1
|
|
|
|
|
3
|
$self->{content_decoders}->{ lc( "$name" ) } = $cb; |
|
687
|
|
|
|
|
|
|
} |
|
688
|
|
|
|
|
|
|
else |
|
689
|
|
|
|
|
|
|
{ |
|
690
|
0
|
|
|
|
|
0
|
die( "content decoder must be a code reference" ); |
|
691
|
|
|
|
|
|
|
} |
|
692
|
1
|
|
|
|
|
2
|
return( $self ); |
|
693
|
|
|
|
|
|
|
} |
|
694
|
|
|
|
|
|
|
|
|
695
|
|
|
|
|
|
|
sub register_format |
|
696
|
|
|
|
|
|
|
{ |
|
697
|
0
|
|
|
0
|
1
|
0
|
my( $self, $name, $code ) = @_; |
|
698
|
0
|
0
|
0
|
|
|
0
|
die( "format name required" ) unless( defined( $name ) && length( $name ) ); |
|
699
|
0
|
0
|
|
|
|
0
|
die( "format validator must be a coderef" ) unless( ref( $code ) eq 'CODE' ); |
|
700
|
0
|
|
|
|
|
0
|
$self->{formats}->{ $name } = $code; |
|
701
|
0
|
|
|
|
|
0
|
return( $self ); |
|
702
|
|
|
|
|
|
|
} |
|
703
|
|
|
|
|
|
|
|
|
704
|
|
|
|
|
|
|
sub register_media_validator |
|
705
|
|
|
|
|
|
|
{ |
|
706
|
5
|
|
|
5
|
1
|
20
|
my( $self, $type, $cb ) = @_; |
|
707
|
5
|
50
|
|
|
|
35
|
if( ref( $cb ) eq 'CODE' ) |
|
708
|
|
|
|
|
|
|
{ |
|
709
|
5
|
|
|
|
|
27
|
$self->{media_validators}->{ lc( "$type" ) } = $cb; |
|
710
|
|
|
|
|
|
|
} |
|
711
|
|
|
|
|
|
|
else |
|
712
|
|
|
|
|
|
|
{ |
|
713
|
0
|
|
|
|
|
0
|
die( "media validator must be a code reference" ); |
|
714
|
|
|
|
|
|
|
} |
|
715
|
5
|
|
|
|
|
10
|
return( $self ); |
|
716
|
|
|
|
|
|
|
} |
|
717
|
|
|
|
|
|
|
|
|
718
|
|
|
|
|
|
|
sub set_comment_handler |
|
719
|
|
|
|
|
|
|
{ |
|
720
|
4
|
|
|
4
|
1
|
37
|
my( $self, $code ) = @_; |
|
721
|
4
|
50
|
|
|
|
16
|
if( @_ > 1 ) |
|
722
|
|
|
|
|
|
|
{ |
|
723
|
4
|
100
|
100
|
|
|
27
|
if( defined( $code ) && ref( $code ) ne 'CODE' ) |
|
724
|
|
|
|
|
|
|
{ |
|
725
|
1
|
50
|
|
|
|
150
|
warn( "Warning only: the handler provided is not a code reference." ) if( warnings::enabled() ); |
|
726
|
1
|
|
|
|
|
10
|
return( $self ); |
|
727
|
|
|
|
|
|
|
} |
|
728
|
3
|
|
|
|
|
9
|
$self->{comment_handler} = $code; |
|
729
|
|
|
|
|
|
|
} |
|
730
|
3
|
|
|
|
|
13
|
return( $self ); |
|
731
|
|
|
|
|
|
|
} |
|
732
|
|
|
|
|
|
|
|
|
733
|
|
|
|
|
|
|
sub set_resolver |
|
734
|
|
|
|
|
|
|
{ |
|
735
|
2
|
|
|
2
|
1
|
12
|
my( $self, $code ) = @_; |
|
736
|
2
|
50
|
|
|
|
9
|
if( @_ > 1 ) |
|
737
|
|
|
|
|
|
|
{ |
|
738
|
2
|
50
|
33
|
|
|
12
|
if( defined( $code ) && ref( $code ) ne 'CODE' ) |
|
739
|
|
|
|
|
|
|
{ |
|
740
|
0
|
0
|
|
|
|
0
|
warn( "Warning only: the handler provided is not a code reference." ) if( warnings::enabled() ); |
|
741
|
0
|
|
|
|
|
0
|
return( $self ); |
|
742
|
|
|
|
|
|
|
} |
|
743
|
2
|
|
|
|
|
4
|
$self->{resolver} = $code; |
|
744
|
|
|
|
|
|
|
} |
|
745
|
2
|
|
|
|
|
5
|
return( $self ); |
|
746
|
|
|
|
|
|
|
} |
|
747
|
|
|
|
|
|
|
|
|
748
|
|
|
|
|
|
|
sub set_vocabulary_support |
|
749
|
|
|
|
|
|
|
{ |
|
750
|
0
|
|
|
0
|
1
|
0
|
my( $self, $h ) = @_; |
|
751
|
0
|
0
|
|
|
|
0
|
$self->{vocab_support} = { %{ $h || {} } }; |
|
|
0
|
|
|
|
|
0
|
|
|
752
|
0
|
|
|
|
|
0
|
$self->{vocab_checked} = 0; |
|
753
|
0
|
|
|
|
|
0
|
return( $self ); |
|
754
|
|
|
|
|
|
|
} |
|
755
|
|
|
|
|
|
|
|
|
756
|
|
|
|
|
|
|
# This is a mutator only method |
|
757
|
|
|
|
|
|
|
# $js->trace -> enables it |
|
758
|
|
|
|
|
|
|
# $js->trace(1) -> enables it |
|
759
|
|
|
|
|
|
|
# $js->trace(0) -> disables it |
|
760
|
|
|
|
|
|
|
# Always returns the object |
|
761
|
|
|
|
|
|
|
# See is_trace_on for the accessor method |
|
762
|
|
|
|
|
|
|
sub trace |
|
763
|
|
|
|
|
|
|
{ |
|
764
|
1
|
|
|
1
|
1
|
1200
|
my( $self, $bool ) = @_; |
|
765
|
1
|
50
|
|
|
|
5
|
my $on = defined( $bool ) ? $bool : 1; |
|
766
|
1
|
|
|
|
|
3
|
$self->{trace_on} = $on; |
|
767
|
1
|
|
|
|
|
3
|
return( $self ); |
|
768
|
|
|
|
|
|
|
} |
|
769
|
|
|
|
|
|
|
|
|
770
|
|
|
|
|
|
|
sub trace_limit |
|
771
|
|
|
|
|
|
|
{ |
|
772
|
0
|
|
|
0
|
1
|
0
|
my( $self, $n ) = @_; |
|
773
|
0
|
|
0
|
|
|
0
|
$self->{trace_limit} = 0 + ( $n || 0 ); |
|
774
|
0
|
|
|
|
|
0
|
return( $self ); |
|
775
|
|
|
|
|
|
|
} |
|
776
|
|
|
|
|
|
|
|
|
777
|
|
|
|
|
|
|
sub trace_sample |
|
778
|
|
|
|
|
|
|
{ |
|
779
|
0
|
|
|
0
|
1
|
0
|
my( $self, $pct ) = @_; |
|
780
|
0
|
|
0
|
|
|
0
|
$self->{trace_sample} = 0 + ( $pct || 0 ); |
|
781
|
0
|
|
|
|
|
0
|
return( $self ); |
|
782
|
|
|
|
|
|
|
} |
|
783
|
|
|
|
|
|
|
|
|
784
|
|
|
|
|
|
|
sub unique_keys |
|
785
|
|
|
|
|
|
|
{ |
|
786
|
9
|
|
|
9
|
1
|
32
|
my( $self, $bool ) = @_; |
|
787
|
9
|
50
|
|
|
|
18
|
my $on = defined( $bool ) ? $bool : 1; |
|
788
|
9
|
|
|
|
|
13
|
$self->{unique_keys} = $on; |
|
789
|
9
|
|
|
|
|
24
|
return( $self ); |
|
790
|
|
|
|
|
|
|
} |
|
791
|
|
|
|
|
|
|
|
|
792
|
|
|
|
|
|
|
sub validate |
|
793
|
|
|
|
|
|
|
{ |
|
794
|
218
|
|
|
218
|
1
|
44169
|
my $self = shift( @_ ); |
|
795
|
218
|
|
|
|
|
409
|
my $data = shift( @_ ); |
|
796
|
218
|
|
|
|
|
743
|
my $opts = $self->_get_args_as_hash( @_ ); |
|
797
|
|
|
|
|
|
|
|
|
798
|
218
|
|
|
|
|
609
|
$self->{errors} = []; |
|
799
|
218
|
|
|
|
|
479
|
$self->{last_error} = ''; |
|
800
|
|
|
|
|
|
|
|
|
801
|
|
|
|
|
|
|
# Ensure we have the compiled root (indexing/anchors) even in lazy mode |
|
802
|
218
|
100
|
|
|
|
1106
|
$self->{compiled} = _compile_root( $self->{schema} ) unless( $self->{compiled} ); |
|
803
|
|
|
|
|
|
|
|
|
804
|
|
|
|
|
|
|
# One-time $vocabulary check (Draft 2020-12) |
|
805
|
218
|
50
|
|
|
|
685
|
if( !$self->{vocab_checked} ) |
|
806
|
|
|
|
|
|
|
{ |
|
807
|
0
|
|
|
|
|
0
|
my $root = $self->{schema}; |
|
808
|
0
|
0
|
0
|
|
|
0
|
if( ref( $root ) eq 'HASH' && |
|
809
|
|
|
|
|
|
|
ref( $root->{'$vocabulary'} ) eq 'HASH' ) |
|
810
|
|
|
|
|
|
|
{ |
|
811
|
0
|
|
|
|
|
0
|
my $decl = $root->{'$vocabulary'}; # { uri => JSON::true|false, ... } |
|
812
|
0
|
|
0
|
|
|
0
|
my $support = $self->{vocab_support} || {}; |
|
813
|
0
|
|
|
|
|
0
|
for my $uri ( keys( %$decl ) ) |
|
814
|
|
|
|
|
|
|
{ |
|
815
|
0
|
0
|
|
|
|
0
|
next unless( $decl->{ $uri } ); # only enforce for required=true |
|
816
|
0
|
0
|
|
|
|
0
|
next if( $support->{ $uri } ); # caller says it's supported |
|
817
|
0
|
0
|
|
|
|
0
|
unless( $self->{ignore_req_vocab} ) |
|
818
|
|
|
|
|
|
|
{ |
|
819
|
0
|
|
|
|
|
0
|
die( "Required vocabulary not supported: $uri" ); |
|
820
|
|
|
|
|
|
|
} |
|
821
|
|
|
|
|
|
|
} |
|
822
|
|
|
|
|
|
|
} |
|
823
|
0
|
|
|
|
|
0
|
$self->{vocab_checked} = 1; |
|
824
|
|
|
|
|
|
|
} |
|
825
|
|
|
|
|
|
|
|
|
826
|
|
|
|
|
|
|
# Because Perl scalar are not JSON scalar, we force the Perl structure into strict JSON types, eliminating all Perl duality and guaranteeing predictable validation semantics. |
|
827
|
218
|
50
|
|
|
|
548
|
if( $self->{normalize_instance} ) |
|
828
|
|
|
|
|
|
|
{ |
|
829
|
218
|
|
|
|
|
596
|
$data = _clone( $data ); |
|
830
|
|
|
|
|
|
|
} |
|
831
|
|
|
|
|
|
|
|
|
832
|
|
|
|
|
|
|
# Optional pre-validation pruning of unknown properties / nested objects. |
|
833
|
|
|
|
|
|
|
# This only happens if explicitly enabled via prune_unknown => 1. |
|
834
|
218
|
100
|
|
|
|
665
|
if( $self->{prune_unknown} ) |
|
835
|
|
|
|
|
|
|
{ |
|
836
|
1
|
|
|
|
|
5
|
$data = $self->_prune_with_schema( $self->{schema}, $data ); |
|
837
|
|
|
|
|
|
|
} |
|
838
|
|
|
|
|
|
|
|
|
839
|
|
|
|
|
|
|
my $ctx = |
|
840
|
|
|
|
|
|
|
{ |
|
841
|
|
|
|
|
|
|
root => $self->{compiled}, |
|
842
|
|
|
|
|
|
|
instance_root => $data, |
|
843
|
|
|
|
|
|
|
resolver => $self->{resolver}, |
|
844
|
|
|
|
|
|
|
formats => $self->{formats}, |
|
845
|
|
|
|
|
|
|
errors => $self->{errors}, |
|
846
|
|
|
|
|
|
|
max_errors => ( ( defined( $opts->{max_errors} ) && $opts->{max_errors} =~ /^\d+$/ ) ? $opts->{max_errors} : $self->{max_errors} ), |
|
847
|
|
|
|
|
|
|
error_count => 0, |
|
848
|
|
|
|
|
|
|
|
|
849
|
|
|
|
|
|
|
# paths / recursion |
|
850
|
|
|
|
|
|
|
ptr_stack => ['#'], |
|
851
|
|
|
|
|
|
|
id_stack => [ $self->{compiled}->{base} ], |
|
852
|
|
|
|
|
|
|
dyn_stack => [ {} ], # dynamicAnchor scope frames |
|
853
|
|
|
|
|
|
|
visited => {}, # "schema_ptr|inst_addr" => 1 |
|
854
|
|
|
|
|
|
|
|
|
855
|
|
|
|
|
|
|
# annotation (for unevaluated*) |
|
856
|
|
|
|
|
|
|
ann_mode => 1, |
|
857
|
|
|
|
|
|
|
compile_on => ( defined( $opts->{compile_on} ) ? ( $opts->{compile_on} ? 1 : 0 ) : ( $self->{compile_on} ? 1 : 0 ) ), |
|
858
|
|
|
|
|
|
|
|
|
859
|
|
|
|
|
|
|
# trace |
|
860
|
|
|
|
|
|
|
trace_on => ( defined( $opts->{trace_on} ) ? ( $opts->{trace_on} ? 1 : 0 ) : ( $self->{trace_on} ? 1 : 0 ) ), |
|
861
|
|
|
|
|
|
|
trace_sample => $self->{trace_sample} || 0, |
|
862
|
|
|
|
|
|
|
trace_limit => ( defined( $opts->{trace_limit} ) && $opts->{trace_limit} =~ /^\d+$/ ) ? $opts->{trace_limit} : ( $self->{trace_limit} || 0 ), |
|
863
|
|
|
|
|
|
|
trace => [], |
|
864
|
|
|
|
|
|
|
|
|
865
|
|
|
|
|
|
|
# content assertion & helpers |
|
866
|
|
|
|
|
|
|
content_assert => ( defined( $opts->{content_assert} ) ? ( $opts->{content_assert} ? 1 : 0 ) : ( $self->{content_assert} ? 1 : 0 ) ), |
|
867
|
|
|
|
|
|
|
media_validators => $self->{media_validators}, |
|
868
|
|
|
|
|
|
|
content_decoders => $self->{content_decoders}, |
|
869
|
|
|
|
|
|
|
|
|
870
|
|
|
|
|
|
|
comment_handler => $self->{comment_handler}, |
|
871
|
|
|
|
|
|
|
|
|
872
|
|
|
|
|
|
|
# extensions |
|
873
|
|
|
|
|
|
|
unique_keys => $self->{unique_keys}, |
|
874
|
|
|
|
|
|
|
extensions => $self->{extensions}, |
|
875
|
218
|
100
|
66
|
|
|
7325
|
}; |
|
|
|
0
|
100
|
|
|
|
|
|
|
|
100
|
33
|
|
|
|
|
|
|
|
50
|
100
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
876
|
|
|
|
|
|
|
|
|
877
|
|
|
|
|
|
|
# Guarantee at least one trace entry when trace is enabled |
|
878
|
218
|
100
|
|
|
|
688
|
if( $ctx->{trace_on} ) |
|
879
|
|
|
|
|
|
|
{ |
|
880
|
6
|
|
|
|
|
10
|
push( @{$ctx->{trace}}, |
|
|
6
|
|
|
|
|
43
|
|
|
881
|
|
|
|
|
|
|
{ |
|
882
|
|
|
|
|
|
|
schema_ptr => '#', |
|
883
|
|
|
|
|
|
|
keyword => 'validate', |
|
884
|
|
|
|
|
|
|
inst_path => '#', |
|
885
|
|
|
|
|
|
|
outcome => 'start', |
|
886
|
|
|
|
|
|
|
note => 'start', |
|
887
|
|
|
|
|
|
|
}); |
|
888
|
|
|
|
|
|
|
} |
|
889
|
|
|
|
|
|
|
|
|
890
|
218
|
|
|
|
|
723
|
my $res = _v( $ctx, '#', $self->{compiled}->{schema}, $data ); |
|
891
|
|
|
|
|
|
|
|
|
892
|
218
|
|
|
|
|
599
|
$self->{last_trace} = $ctx->{trace}; |
|
893
|
|
|
|
|
|
|
|
|
894
|
218
|
100
|
|
|
|
541
|
if( !$res->{ok} ) |
|
895
|
|
|
|
|
|
|
{ |
|
896
|
|
|
|
|
|
|
# $self->{last_error} = _first_error_text( $self->{errors} ); |
|
897
|
106
|
50
|
|
|
|
1621
|
$self->{last_error} = scalar( @{$self->{errors}} ) ? $self->{errors}->[0] : ''; |
|
|
106
|
|
|
|
|
452
|
|
|
898
|
106
|
|
|
|
|
1287
|
return(0); |
|
899
|
|
|
|
|
|
|
} |
|
900
|
112
|
|
|
|
|
1447
|
return(1); |
|
901
|
|
|
|
|
|
|
} |
|
902
|
|
|
|
|
|
|
|
|
903
|
|
|
|
|
|
|
sub _apply_dynamic_ref |
|
904
|
|
|
|
|
|
|
{ |
|
905
|
5
|
|
|
5
|
|
23
|
my( $ctx, $schema_ptr, $ref, $inst ) = @_; |
|
906
|
|
|
|
|
|
|
|
|
907
|
5
|
50
|
|
|
|
59
|
if( $ref =~ /\#(.+)\z/ ) |
|
908
|
|
|
|
|
|
|
{ |
|
909
|
5
|
|
|
|
|
17
|
my $name = $1; |
|
910
|
5
|
|
|
|
|
8
|
for( my $i = $#{ $ctx->{dyn_stack} }; $i >= 0; $i-- ) |
|
|
5
|
|
|
|
|
24
|
|
|
911
|
|
|
|
|
|
|
{ |
|
912
|
5
|
|
|
|
|
9
|
my $frame = $ctx->{dyn_stack}->[$i]; |
|
913
|
5
|
50
|
|
|
|
15
|
if( my $node = $frame->{ $name } ) |
|
914
|
|
|
|
|
|
|
{ |
|
915
|
5
|
|
|
|
|
18
|
my $sp = _ptr_of_node( $ctx->{root}, $node ); |
|
916
|
5
|
|
|
|
|
22
|
return( _v( $ctx, $sp, $node, $inst ) ); |
|
917
|
|
|
|
|
|
|
} |
|
918
|
|
|
|
|
|
|
} |
|
919
|
|
|
|
|
|
|
} |
|
920
|
|
|
|
|
|
|
|
|
921
|
0
|
|
|
|
|
0
|
return( _apply_ref( $ctx, $schema_ptr, $ref, $inst ) ); |
|
922
|
|
|
|
|
|
|
} |
|
923
|
|
|
|
|
|
|
|
|
924
|
|
|
|
|
|
|
# $ref and $dynamicRef |
|
925
|
|
|
|
|
|
|
sub _apply_ref |
|
926
|
|
|
|
|
|
|
{ |
|
927
|
22
|
|
|
22
|
|
83
|
my( $ctx, $schema_ptr, $ref, $inst ) = @_; |
|
928
|
|
|
|
|
|
|
|
|
929
|
22
|
|
|
|
|
42
|
my $base = $ctx->{id_stack}->[-1]; |
|
930
|
22
|
|
|
|
|
59
|
my $abs = _resolve_uri( $base, $ref ); |
|
931
|
|
|
|
|
|
|
|
|
932
|
|
|
|
|
|
|
# Direct absolute ID/anchor hit |
|
933
|
22
|
100
|
|
|
|
83
|
if( my $node = $ctx->{root}->{id_index}->{ $abs } ) |
|
934
|
|
|
|
|
|
|
{ |
|
935
|
3
|
|
|
|
|
10
|
my $sp = _ptr_of_node( $ctx->{root}, $node ); |
|
936
|
3
|
|
|
|
|
10
|
return( _v( $ctx, $sp, $node, $inst ) ); |
|
937
|
|
|
|
|
|
|
} |
|
938
|
|
|
|
|
|
|
|
|
939
|
|
|
|
|
|
|
# Local fragment |
|
940
|
19
|
100
|
|
|
|
62
|
if( $ref =~ /^\#/ ) |
|
941
|
|
|
|
|
|
|
{ |
|
942
|
|
|
|
|
|
|
# 1) Try anchors (e.g. "#foo" / "#MyAnchor") |
|
943
|
14
|
100
|
|
|
|
53
|
if( my $n = $ctx->{root}->{anchors}->{ $ref } ) |
|
944
|
|
|
|
|
|
|
{ |
|
945
|
5
|
|
|
|
|
27
|
return( _v( $ctx, $ref, $n, $inst ) ); |
|
946
|
|
|
|
|
|
|
} |
|
947
|
9
|
50
|
|
|
|
35
|
if( $ref =~ /^\#([A-Za-z0-9._-]+)\z/ ) |
|
948
|
|
|
|
|
|
|
{ |
|
949
|
0
|
|
|
|
|
0
|
my $cand = $base . '#' . $1; |
|
950
|
0
|
0
|
|
|
|
0
|
if( my $node = $ctx->{root}->{id_index}->{ $cand } ) |
|
951
|
|
|
|
|
|
|
{ |
|
952
|
0
|
|
|
|
|
0
|
my $sp = _ptr_of_node( $ctx->{root}, $node ); |
|
953
|
0
|
|
|
|
|
0
|
return( _v( $ctx, $sp, $node, $inst ) ); |
|
954
|
|
|
|
|
|
|
} |
|
955
|
|
|
|
|
|
|
} |
|
956
|
|
|
|
|
|
|
# 2) If it’s a JSON Pointer ( "#/..." ), use _jsv_resolve_internal_ref |
|
957
|
9
|
50
|
|
|
|
29
|
if( $ref =~ m{\A\#/(?:[^~/]|~[01])} ) |
|
958
|
|
|
|
|
|
|
{ |
|
959
|
|
|
|
|
|
|
# or pulled from id_index; same one used in compile_js |
|
960
|
9
|
50
|
|
|
|
17
|
my $root_schema = $ctx->{root}->{schema} or return( _err_res( $ctx, $schema_ptr, "missing schema", '$ref' ) ); |
|
961
|
|
|
|
|
|
|
|
|
962
|
9
|
|
|
|
|
18
|
my $node = _jsv_resolve_internal_ref( $root_schema, $ref ); |
|
963
|
9
|
100
|
|
|
|
14
|
if( $node ) |
|
964
|
|
|
|
|
|
|
{ |
|
965
|
8
|
|
|
|
|
14
|
return( _v( $ctx, $ref, $node, $inst ) ); |
|
966
|
|
|
|
|
|
|
} |
|
967
|
|
|
|
|
|
|
|
|
968
|
1
|
|
|
|
|
6
|
return _err_res( |
|
969
|
|
|
|
|
|
|
$ctx, |
|
970
|
|
|
|
|
|
|
$schema_ptr, |
|
971
|
|
|
|
|
|
|
"unresolved JSON Pointer fragment in \$ref: $abs", |
|
972
|
|
|
|
|
|
|
'$ref' |
|
973
|
|
|
|
|
|
|
); |
|
974
|
|
|
|
|
|
|
} |
|
975
|
|
|
|
|
|
|
# 3) If not a JSON Pointer and not an anchor, fall through to external resolver / error |
|
976
|
|
|
|
|
|
|
} |
|
977
|
|
|
|
|
|
|
|
|
978
|
|
|
|
|
|
|
# External resolver hook |
|
979
|
5
|
50
|
|
|
|
12
|
if( $ctx->{resolver} ) |
|
980
|
|
|
|
|
|
|
{ |
|
981
|
5
|
|
|
|
|
9
|
local $@; |
|
982
|
5
|
|
|
|
|
8
|
my $ext = eval{ $ctx->{resolver}->( $abs ) }; |
|
|
5
|
|
|
|
|
18
|
|
|
983
|
5
|
50
|
33
|
|
|
71
|
return( _err_res( $ctx, $schema_ptr, "resolver failed for \$ref: $abs", '$ref' ) ) unless( $ext && ref( $ext ) ); |
|
984
|
|
|
|
|
|
|
|
|
985
|
5
|
50
|
33
|
|
|
38
|
my $ext_base = _normalize_uri( ( ref( $ext ) eq 'HASH' && $ext->{'$id'} ) ? $ext->{'$id'} : $abs ); |
|
986
|
|
|
|
|
|
|
|
|
987
|
5
|
|
|
|
|
26
|
my( $frag ) = ( $abs =~ /(\#.*)\z/ ); |
|
988
|
5
|
|
|
|
|
11
|
my ( $anchors, $ids ) = ( {}, {} ); |
|
989
|
5
|
|
|
|
|
15
|
_index_schema_202012( $ext, $ext_base, '#', $anchors, $ids ); |
|
990
|
|
|
|
|
|
|
|
|
991
|
5
|
|
|
|
|
8
|
push( @{$ctx->{id_stack}}, $ext_base ); |
|
|
5
|
|
|
|
|
14
|
|
|
992
|
|
|
|
|
|
|
|
|
993
|
|
|
|
|
|
|
# If the abs URI included a JSON Pointer fragment, honor it here |
|
994
|
5
|
|
|
|
|
8
|
my $target_ptr = '#'; |
|
995
|
5
|
50
|
33
|
|
|
32
|
if( defined( $frag ) && length( $frag ) ) |
|
996
|
|
|
|
|
|
|
{ |
|
997
|
5
|
50
|
|
|
|
20
|
if( $frag =~ m{\A\#/(?:[^~/]|~[01])} ) |
|
|
|
0
|
|
|
|
|
|
|
998
|
|
|
|
|
|
|
{ |
|
999
|
|
|
|
|
|
|
# JSON Pointer fragment |
|
1000
|
5
|
50
|
|
|
|
40
|
if( my $node = $anchors->{ $frag } ) |
|
1001
|
|
|
|
|
|
|
{ |
|
1002
|
5
|
|
|
|
|
20
|
my $r = _v( $ctx, $frag, $node, $inst ); |
|
1003
|
5
|
|
|
|
|
8
|
pop( @{$ctx->{id_stack}} ); |
|
|
5
|
|
|
|
|
9
|
|
|
1004
|
5
|
|
|
|
|
40
|
return( $r ); |
|
1005
|
|
|
|
|
|
|
} |
|
1006
|
|
|
|
|
|
|
else |
|
1007
|
|
|
|
|
|
|
{ |
|
1008
|
0
|
|
|
|
|
0
|
pop( @{$ctx->{id_stack}} ); |
|
|
0
|
|
|
|
|
0
|
|
|
1009
|
0
|
|
|
|
|
0
|
return( _err_res( $ctx, $schema_ptr, "unresolved JSON Pointer fragment in \$ref: $abs", '$ref' ) ); |
|
1010
|
|
|
|
|
|
|
} |
|
1011
|
|
|
|
|
|
|
} |
|
1012
|
|
|
|
|
|
|
elsif( $frag =~ /\A\#([A-Za-z0-9._-]+)\z/ ) |
|
1013
|
|
|
|
|
|
|
{ |
|
1014
|
0
|
|
|
|
|
0
|
my $cand = $ext_base . '#' . $1; |
|
1015
|
0
|
0
|
|
|
|
0
|
if( my $node = $ids->{ $cand } ) |
|
1016
|
|
|
|
|
|
|
{ |
|
1017
|
0
|
|
0
|
|
|
0
|
my $sp = _ptr_of_node( { anchors => $anchors }, $node ) || '#'; |
|
1018
|
0
|
|
|
|
|
0
|
my $r = _v( $ctx, $sp, $node, $inst ); |
|
1019
|
0
|
|
|
|
|
0
|
pop( @{$ctx->{id_stack}} ); |
|
|
0
|
|
|
|
|
0
|
|
|
1020
|
0
|
|
|
|
|
0
|
return( $r ); |
|
1021
|
|
|
|
|
|
|
} |
|
1022
|
|
|
|
|
|
|
} |
|
1023
|
|
|
|
|
|
|
} |
|
1024
|
|
|
|
|
|
|
|
|
1025
|
0
|
|
|
|
|
0
|
my $r = _v( $ctx, '#', $ext, $inst ); |
|
1026
|
0
|
|
|
|
|
0
|
pop( @{$ctx->{id_stack}} ); |
|
|
0
|
|
|
|
|
0
|
|
|
1027
|
0
|
|
|
|
|
0
|
return( $r ); |
|
1028
|
|
|
|
|
|
|
} |
|
1029
|
|
|
|
|
|
|
|
|
1030
|
0
|
|
|
|
|
0
|
return( _err_res( $ctx, $schema_ptr, "unresolved \$ref: $ref (abs: $abs)", '$ref' ) ); |
|
1031
|
|
|
|
|
|
|
} |
|
1032
|
|
|
|
|
|
|
|
|
1033
|
|
|
|
|
|
|
sub _canon |
|
1034
|
|
|
|
|
|
|
{ |
|
1035
|
86
|
|
|
86
|
|
112
|
my( $v ) = @_; |
|
1036
|
86
|
|
|
|
|
181
|
state $json = JSON->new->allow_nonref(1)->canonical(1)->convert_blessed(1); |
|
1037
|
86
|
|
|
|
|
311
|
return( $json->encode( $v ) ); |
|
1038
|
|
|
|
|
|
|
} |
|
1039
|
|
|
|
|
|
|
|
|
1040
|
|
|
|
|
|
|
sub _check_vocabulary_required |
|
1041
|
|
|
|
|
|
|
{ |
|
1042
|
120
|
|
|
120
|
|
293
|
my( $self ) = @_; |
|
1043
|
120
|
50
|
|
|
|
423
|
return(1) if( $self->{vocab_checked} ); |
|
1044
|
|
|
|
|
|
|
|
|
1045
|
120
|
|
|
|
|
270
|
my $root = $self->{schema}; |
|
1046
|
120
|
100
|
66
|
|
|
987
|
if( ref( $root ) eq 'HASH' && ref( $root->{'$vocabulary'} ) eq 'HASH' ) |
|
1047
|
|
|
|
|
|
|
{ |
|
1048
|
2
|
|
|
|
|
4
|
my $decl = $root->{'$vocabulary'}; |
|
1049
|
2
|
|
50
|
|
|
6
|
my $support = $self->{vocab_support} || {}; |
|
1050
|
2
|
|
|
|
|
19
|
for my $uri ( keys( %$decl ) ) |
|
1051
|
|
|
|
|
|
|
{ |
|
1052
|
|
|
|
|
|
|
# required only |
|
1053
|
3
|
100
|
|
|
|
50
|
next unless( $decl->{ $uri } ); |
|
1054
|
1
|
50
|
|
|
|
6
|
next if( $support->{ $uri } ); |
|
1055
|
|
|
|
|
|
|
# TODO: Maybe we should return an exception rather than dying; it would be more user-friendly |
|
1056
|
1
|
|
|
|
|
18
|
die( "Required vocabulary not supported: $uri" ); |
|
1057
|
|
|
|
|
|
|
} |
|
1058
|
|
|
|
|
|
|
} |
|
1059
|
119
|
|
|
|
|
285
|
return( $self->{vocab_checked} = 1 ); |
|
1060
|
|
|
|
|
|
|
} |
|
1061
|
|
|
|
|
|
|
|
|
1062
|
|
|
|
|
|
|
sub _clone |
|
1063
|
|
|
|
|
|
|
{ |
|
1064
|
344
|
|
|
344
|
|
690
|
my( $v ) = @_; |
|
1065
|
344
|
|
|
|
|
1813
|
state $json = JSON->new->allow_nonref(1)->canonical(1); |
|
1066
|
344
|
|
|
|
|
8086
|
return( $json->decode( $json->encode( $v ) ) ); |
|
1067
|
|
|
|
|
|
|
} |
|
1068
|
|
|
|
|
|
|
|
|
1069
|
|
|
|
|
|
|
sub _compile_js_node |
|
1070
|
|
|
|
|
|
|
{ |
|
1071
|
0
|
|
|
0
|
|
0
|
my $self = shift( @_ ); |
|
1072
|
0
|
|
|
|
|
0
|
my( $S, $sp, $seen, $funcs, $counter_ref, $root, $opts ) = @_; |
|
1073
|
|
|
|
|
|
|
|
|
1074
|
0
|
|
0
|
|
|
0
|
$opts ||= {}; |
|
1075
|
0
|
0
|
|
|
|
0
|
my $ecma = exists( $opts->{ecma} ) ? $opts->{ecma} : 'auto'; |
|
1076
|
0
|
|
0
|
|
|
0
|
my $force_unicode = |
|
1077
|
|
|
|
|
|
|
defined( $ecma ) && |
|
1078
|
|
|
|
|
|
|
$ecma =~ /^\d+$/ && |
|
1079
|
|
|
|
|
|
|
$ecma >= 2018; |
|
1080
|
|
|
|
|
|
|
|
|
1081
|
|
|
|
|
|
|
# Re-use same JS function for the same schema pointer |
|
1082
|
0
|
0
|
|
|
|
0
|
if( exists( $seen->{ $sp } ) ) |
|
1083
|
|
|
|
|
|
|
{ |
|
1084
|
0
|
|
|
|
|
0
|
return( $seen->{ $sp } ); |
|
1085
|
|
|
|
|
|
|
} |
|
1086
|
|
|
|
|
|
|
|
|
1087
|
|
|
|
|
|
|
# Support pointer-alias schemas such as: |
|
1088
|
|
|
|
|
|
|
# definitions => { address => "#/definitions/jp_address" } |
|
1089
|
0
|
0
|
0
|
|
|
0
|
if( defined( $S ) && !ref( $S ) && $S =~ /^#\// ) |
|
|
|
|
0
|
|
|
|
|
|
1090
|
|
|
|
|
|
|
{ |
|
1091
|
0
|
|
|
|
|
0
|
my $target_ptr = $S; |
|
1092
|
|
|
|
|
|
|
|
|
1093
|
|
|
|
|
|
|
# If we already compiled that pointer, just alias it |
|
1094
|
0
|
0
|
|
|
|
0
|
if( exists( $seen->{ $target_ptr } ) ) |
|
1095
|
|
|
|
|
|
|
{ |
|
1096
|
0
|
|
|
|
|
0
|
$seen->{ $sp } = $seen->{ $target_ptr }; |
|
1097
|
0
|
|
|
|
|
0
|
return( $seen->{ $target_ptr } ); |
|
1098
|
|
|
|
|
|
|
} |
|
1099
|
|
|
|
|
|
|
|
|
1100
|
0
|
|
|
|
|
0
|
my $target_schema = _jsv_resolve_internal_ref( $root, $target_ptr ); |
|
1101
|
|
|
|
|
|
|
|
|
1102
|
|
|
|
|
|
|
# Follow chains of pointer-aliases |
|
1103
|
0
|
0
|
0
|
|
|
0
|
if( defined( $target_schema ) && !ref( $target_schema ) ) |
|
1104
|
|
|
|
|
|
|
{ |
|
1105
|
0
|
|
|
|
|
0
|
my %followed; |
|
1106
|
0
|
|
0
|
|
|
0
|
while( |
|
|
|
|
0
|
|
|
|
|
|
1107
|
|
|
|
|
|
|
defined( $target_schema ) && |
|
1108
|
|
|
|
|
|
|
!ref( $target_schema ) && |
|
1109
|
|
|
|
|
|
|
$target_schema =~ /^#\// |
|
1110
|
|
|
|
|
|
|
) |
|
1111
|
|
|
|
|
|
|
{ |
|
1112
|
0
|
0
|
|
|
|
0
|
last if( $followed{ $target_schema }++ ); |
|
1113
|
0
|
|
|
|
|
0
|
$target_ptr = $target_schema; |
|
1114
|
0
|
|
|
|
|
0
|
$target_schema = _jsv_resolve_internal_ref( $root, $target_ptr ); |
|
1115
|
|
|
|
|
|
|
} |
|
1116
|
|
|
|
|
|
|
} |
|
1117
|
|
|
|
|
|
|
|
|
1118
|
0
|
0
|
|
|
|
0
|
unless( defined( $target_schema ) ) |
|
1119
|
|
|
|
|
|
|
{ |
|
1120
|
|
|
|
|
|
|
# Could not resolve: emit a no-op validator (server still enforces) |
|
1121
|
0
|
|
|
|
|
0
|
my $id = $$counter_ref++; |
|
1122
|
0
|
|
|
|
|
0
|
my $fn = "jsv_node_${id}"; |
|
1123
|
0
|
|
|
|
|
0
|
$seen->{ $sp } = $fn; |
|
1124
|
0
|
|
|
|
|
0
|
push( @$funcs, <<JS_RUNTIME ); |
|
1125
|
|
|
|
|
|
|
// $sp (unresolved pointer-alias $S) |
|
1126
|
|
|
|
|
|
|
function $fn(inst, path, ctx) |
|
1127
|
|
|
|
|
|
|
{ |
|
1128
|
|
|
|
|
|
|
} |
|
1129
|
|
|
|
|
|
|
JS_RUNTIME |
|
1130
|
0
|
|
|
|
|
0
|
return( $fn ); |
|
1131
|
|
|
|
|
|
|
} |
|
1132
|
|
|
|
|
|
|
|
|
1133
|
0
|
0
|
|
|
|
0
|
unless( ref( $target_schema ) eq 'HASH' ) |
|
1134
|
|
|
|
|
|
|
{ |
|
1135
|
0
|
|
0
|
|
|
0
|
die( |
|
1136
|
|
|
|
|
|
|
"Internal error: pointer-alias '$S' at '$sp' did not resolve to an object schema " . |
|
1137
|
|
|
|
|
|
|
"(got " . ( ref( $target_schema ) || 'scalar' ) . ")." |
|
1138
|
|
|
|
|
|
|
); |
|
1139
|
|
|
|
|
|
|
} |
|
1140
|
|
|
|
|
|
|
|
|
1141
|
0
|
|
|
|
|
0
|
my $base_fn = $self->_compile_js_node( $target_schema, $target_ptr, $seen, $funcs, $counter_ref, $root, $opts ); |
|
1142
|
|
|
|
|
|
|
|
|
1143
|
|
|
|
|
|
|
# Alias this pointer to the target validator |
|
1144
|
0
|
|
|
|
|
0
|
$seen->{ $sp } = $base_fn; |
|
1145
|
0
|
|
|
|
|
0
|
return( $base_fn ); |
|
1146
|
|
|
|
|
|
|
} |
|
1147
|
|
|
|
|
|
|
|
|
1148
|
|
|
|
|
|
|
# NOTE: $ref |
|
1149
|
|
|
|
|
|
|
# 0) $ref handling (internal refs only) |
|
1150
|
0
|
0
|
0
|
|
|
0
|
if( ref( $S ) eq 'HASH' && |
|
1151
|
|
|
|
|
|
|
exists( $S->{'$ref'} ) ) |
|
1152
|
|
|
|
|
|
|
{ |
|
1153
|
0
|
|
|
|
|
0
|
my $ref = $S->{'$ref'}; |
|
1154
|
|
|
|
|
|
|
|
|
1155
|
|
|
|
|
|
|
# Only support internal refs ("#/...") in JS |
|
1156
|
0
|
0
|
0
|
|
|
0
|
if( defined( $ref ) && $ref =~ /^#/ ) |
|
1157
|
|
|
|
|
|
|
{ |
|
1158
|
0
|
|
|
|
|
0
|
my $target_ptr = $ref; |
|
1159
|
|
|
|
|
|
|
|
|
1160
|
|
|
|
|
|
|
# If we already compiled that pointer, just alias and return it |
|
1161
|
0
|
0
|
|
|
|
0
|
if( exists( $seen->{ $target_ptr } ) ) |
|
1162
|
|
|
|
|
|
|
{ |
|
1163
|
0
|
|
|
|
|
0
|
$seen->{ $sp } = $seen->{ $target_ptr }; |
|
1164
|
0
|
|
|
|
|
0
|
return( $seen->{ $target_ptr } ); |
|
1165
|
|
|
|
|
|
|
} |
|
1166
|
|
|
|
|
|
|
|
|
1167
|
|
|
|
|
|
|
# Otherwise, resolve pointer against the root schema |
|
1168
|
0
|
|
|
|
|
0
|
my $target_schema = _jsv_resolve_internal_ref( $root, $target_ptr ); |
|
1169
|
|
|
|
|
|
|
|
|
1170
|
|
|
|
|
|
|
|
|
1171
|
|
|
|
|
|
|
# Support "pointer alias" schemas such as: |
|
1172
|
|
|
|
|
|
|
# definitions => { address => "#/definitions/jp_address" } |
|
1173
|
|
|
|
|
|
|
# Keep resolving until we reach a real schema hash. |
|
1174
|
0
|
0
|
0
|
|
|
0
|
if( defined( $target_schema ) && !ref( $target_schema ) ) |
|
1175
|
|
|
|
|
|
|
{ |
|
1176
|
0
|
|
|
|
|
0
|
my %followed; |
|
1177
|
0
|
|
0
|
|
|
0
|
while( |
|
|
|
|
0
|
|
|
|
|
|
1178
|
|
|
|
|
|
|
defined( $target_schema ) && |
|
1179
|
|
|
|
|
|
|
!ref( $target_schema ) && |
|
1180
|
|
|
|
|
|
|
$target_schema =~ /^#\// |
|
1181
|
|
|
|
|
|
|
) |
|
1182
|
|
|
|
|
|
|
{ |
|
1183
|
0
|
0
|
|
|
|
0
|
last if( $followed{ $target_schema }++ ); |
|
1184
|
0
|
|
|
|
|
0
|
$target_ptr = $target_schema; |
|
1185
|
0
|
|
|
|
|
0
|
$target_schema = _jsv_resolve_internal_ref( $root, $target_ptr ); |
|
1186
|
|
|
|
|
|
|
} |
|
1187
|
|
|
|
|
|
|
} |
|
1188
|
|
|
|
|
|
|
|
|
1189
|
0
|
0
|
|
|
|
0
|
unless( defined( $target_schema ) ) |
|
1190
|
|
|
|
|
|
|
{ |
|
1191
|
|
|
|
|
|
|
# Pointer could not be resolved. As a safety fallback, |
|
1192
|
|
|
|
|
|
|
# make this node a no-op on the client (server will still enforce). |
|
1193
|
0
|
|
|
|
|
0
|
my $id = $$counter_ref++; |
|
1194
|
0
|
|
|
|
|
0
|
my $fn = "jsv_node_${id}"; |
|
1195
|
0
|
|
|
|
|
0
|
$seen->{ $sp } = $fn; |
|
1196
|
0
|
|
|
|
|
0
|
push( @$funcs, <<JS_RUNTIME ); |
|
1197
|
|
|
|
|
|
|
// $sp |
|
1198
|
|
|
|
|
|
|
function $fn(inst, path, ctx) |
|
1199
|
|
|
|
|
|
|
{ |
|
1200
|
|
|
|
|
|
|
} |
|
1201
|
|
|
|
|
|
|
|
|
1202
|
|
|
|
|
|
|
JS_RUNTIME |
|
1203
|
0
|
|
|
|
|
0
|
return( $fn ); |
|
1204
|
|
|
|
|
|
|
} |
|
1205
|
|
|
|
|
|
|
|
|
1206
|
0
|
0
|
|
|
|
0
|
unless( ref( $target_schema ) eq 'HASH' ) |
|
1207
|
|
|
|
|
|
|
{ |
|
1208
|
0
|
0
|
0
|
|
|
0
|
die( "Internal error: \$ref target '$target_ptr' did not resolve to a schema object (got " . |
|
1209
|
|
|
|
|
|
|
( defined( $target_schema ) ? ref( $target_schema ) || 'scalar' : 'undef' ) . |
|
1210
|
|
|
|
|
|
|
"). If you are using pointer-alias definitions, they must ultimately resolve to an object schema." ); |
|
1211
|
|
|
|
|
|
|
} |
|
1212
|
|
|
|
|
|
|
|
|
1213
|
|
|
|
|
|
|
# Compile the target, then merge sibling keywords |
|
1214
|
0
|
|
|
|
|
0
|
my $base_fn = $self->_compile_js_node( $target_schema, $target_ptr, $seen, $funcs, $counter_ref, $root, $opts ); |
|
1215
|
|
|
|
|
|
|
|
|
1216
|
|
|
|
|
|
|
# Create a wrapper that runs both the referenced schema AND local keywords |
|
1217
|
0
|
|
|
|
|
0
|
my $id = $$counter_ref++; |
|
1218
|
0
|
|
|
|
|
0
|
my $wrapper_fn = "jsv_node_$id"; |
|
1219
|
0
|
|
|
|
|
0
|
$seen->{ $sp } = $wrapper_fn; |
|
1220
|
|
|
|
|
|
|
|
|
1221
|
0
|
|
|
|
|
0
|
my @wrapper_body; |
|
1222
|
0
|
|
|
|
|
0
|
push( @wrapper_body, <<JS_RUNTIME ); |
|
1223
|
|
|
|
|
|
|
// $sp (\$ref + siblings) |
|
1224
|
|
|
|
|
|
|
function $wrapper_fn(inst, path, ctx) |
|
1225
|
|
|
|
|
|
|
{ |
|
1226
|
|
|
|
|
|
|
$base_fn(inst, path, ctx); |
|
1227
|
|
|
|
|
|
|
if(ctx.errors.length >= ctx.maxErrors) return; |
|
1228
|
|
|
|
|
|
|
JS_RUNTIME |
|
1229
|
|
|
|
|
|
|
|
|
1230
|
|
|
|
|
|
|
# Now compile the current node again, but skip the $ref |
|
1231
|
0
|
|
|
|
|
0
|
my $local_S = { %$S }; # shallow copy |
|
1232
|
0
|
|
|
|
|
0
|
delete( $local_S->{'$ref'} ); |
|
1233
|
0
|
|
|
|
|
0
|
my $local_sp = _join_ptr( $sp, '__local__' ); |
|
1234
|
0
|
|
|
|
|
0
|
my $local_fn = $self->_compile_js_node( $local_S, $local_sp, $seen, $funcs, $counter_ref, $root, $opts ); |
|
1235
|
|
|
|
|
|
|
|
|
1236
|
0
|
0
|
|
|
|
0
|
if( $local_fn ne $wrapper_fn ) |
|
1237
|
|
|
|
|
|
|
{ |
|
1238
|
0
|
|
|
|
|
0
|
push( @wrapper_body, <<JS_RUNTIME ); |
|
1239
|
|
|
|
|
|
|
$local_fn(inst, path, ctx); |
|
1240
|
|
|
|
|
|
|
JS_RUNTIME |
|
1241
|
|
|
|
|
|
|
} |
|
1242
|
|
|
|
|
|
|
|
|
1243
|
0
|
|
|
|
|
0
|
push( @wrapper_body, <<JS_RUNTIME ); |
|
1244
|
|
|
|
|
|
|
} |
|
1245
|
|
|
|
|
|
|
JS_RUNTIME |
|
1246
|
0
|
|
|
|
|
0
|
push( @$funcs, join( '', @wrapper_body ) ); |
|
1247
|
0
|
|
|
|
|
0
|
return( $wrapper_fn ); |
|
1248
|
|
|
|
|
|
|
} |
|
1249
|
|
|
|
|
|
|
|
|
1250
|
|
|
|
|
|
|
# External refs (URLs, etc.) are not resolved on the client. |
|
1251
|
|
|
|
|
|
|
# Full resolution is done server-side. |
|
1252
|
|
|
|
|
|
|
} |
|
1253
|
|
|
|
|
|
|
|
|
1254
|
0
|
|
|
|
|
0
|
my $id = $$counter_ref++; |
|
1255
|
0
|
|
|
|
|
0
|
my $fn = "jsv_node_$id"; |
|
1256
|
0
|
|
|
|
|
0
|
$seen->{ $sp } = $fn; |
|
1257
|
|
|
|
|
|
|
|
|
1258
|
0
|
|
|
|
|
0
|
my @body; |
|
1259
|
|
|
|
|
|
|
|
|
1260
|
0
|
|
|
|
|
0
|
push( @body, <<JS_RUNTIME ); |
|
1261
|
|
|
|
|
|
|
// $sp |
|
1262
|
|
|
|
|
|
|
function $fn(inst, path, ctx) |
|
1263
|
|
|
|
|
|
|
{ |
|
1264
|
|
|
|
|
|
|
JS_RUNTIME |
|
1265
|
|
|
|
|
|
|
|
|
1266
|
|
|
|
|
|
|
# NOTE: combinator (allOf / anyOf / oneOf / not / if-then-else) |
|
1267
|
0
|
0
|
|
|
|
0
|
if( ref( $S ) eq 'HASH' ) |
|
1268
|
|
|
|
|
|
|
{ |
|
1269
|
|
|
|
|
|
|
# allOf – AND of subschemas |
|
1270
|
0
|
0
|
0
|
|
|
0
|
if( exists( $S->{allOf} ) && |
|
|
|
|
0
|
|
|
|
|
|
1271
|
|
|
|
|
|
|
ref( $S->{allOf} ) eq 'ARRAY' && |
|
1272
|
0
|
|
|
|
|
0
|
@{$S->{allOf}} ) |
|
1273
|
|
|
|
|
|
|
{ |
|
1274
|
0
|
|
|
|
|
0
|
for my $i ( 0 .. $#{$S->{allOf}} ) |
|
|
0
|
|
|
|
|
0
|
|
|
1275
|
|
|
|
|
|
|
{ |
|
1276
|
0
|
|
|
|
|
0
|
my $sub_sp = _join_ptr( $sp, 'allOf', $i ); |
|
1277
|
0
|
|
|
|
|
0
|
my $sub_fn = $self->_compile_js_node( $S->{allOf}->[ $i ], $sub_sp, $seen, $funcs, $counter_ref, $root, $opts ); |
|
1278
|
|
|
|
|
|
|
|
|
1279
|
0
|
|
|
|
|
0
|
push( @body, <<JS_RUNTIME ); |
|
1280
|
|
|
|
|
|
|
// $sub_sp |
|
1281
|
|
|
|
|
|
|
$sub_fn(inst, path + '/allOf/$i', ctx); |
|
1282
|
|
|
|
|
|
|
if(ctx.maxErrors && ctx.errors.length >= ctx.maxErrors) return; |
|
1283
|
|
|
|
|
|
|
JS_RUNTIME |
|
1284
|
|
|
|
|
|
|
} |
|
1285
|
|
|
|
|
|
|
|
|
1286
|
|
|
|
|
|
|
# IMPORTANT: we do *not* return here. |
|
1287
|
|
|
|
|
|
|
# Other local keywords (type, properties, contains, ...) must still run. |
|
1288
|
|
|
|
|
|
|
} |
|
1289
|
|
|
|
|
|
|
|
|
1290
|
|
|
|
|
|
|
# anyOf – at least one must validate (we only emit a single anyOf error) |
|
1291
|
0
|
0
|
0
|
|
|
0
|
if( exists( $S->{anyOf} ) && |
|
|
|
|
0
|
|
|
|
|
|
1292
|
|
|
|
|
|
|
ref( $S->{anyOf} ) eq 'ARRAY' && |
|
1293
|
0
|
|
|
|
|
0
|
@{$S->{anyOf}} ) |
|
1294
|
|
|
|
|
|
|
{ |
|
1295
|
0
|
|
|
|
|
0
|
push( @body, <<JS_RUNTIME ); |
|
1296
|
|
|
|
|
|
|
(function() |
|
1297
|
|
|
|
|
|
|
{ |
|
1298
|
|
|
|
|
|
|
var baseErrors = ctx.errors; |
|
1299
|
|
|
|
|
|
|
var matched = false; |
|
1300
|
|
|
|
|
|
|
|
|
1301
|
|
|
|
|
|
|
JS_RUNTIME |
|
1302
|
|
|
|
|
|
|
|
|
1303
|
0
|
|
|
|
|
0
|
for my $i ( 0 .. $#{$S->{anyOf}} ) |
|
|
0
|
|
|
|
|
0
|
|
|
1304
|
|
|
|
|
|
|
{ |
|
1305
|
0
|
|
|
|
|
0
|
my $sub_sp = _join_ptr( $sp, 'anyOf', $i ); |
|
1306
|
0
|
|
|
|
|
0
|
my $sub_fn = $self->_compile_js_node( $S->{anyOf}->[ $i ], $sub_sp, $seen, $funcs, $counter_ref, $root, $opts ); |
|
1307
|
|
|
|
|
|
|
|
|
1308
|
0
|
|
|
|
|
0
|
push( @body, <<JS_RUNTIME ); |
|
1309
|
|
|
|
|
|
|
if(matched) return; |
|
1310
|
|
|
|
|
|
|
ctx.errors = []; |
|
1311
|
|
|
|
|
|
|
// $sub_sp |
|
1312
|
|
|
|
|
|
|
$sub_fn(inst, path + '/anyOf/$i', ctx); |
|
1313
|
|
|
|
|
|
|
if(ctx.errors.length === 0) |
|
1314
|
|
|
|
|
|
|
{ |
|
1315
|
|
|
|
|
|
|
matched = true; |
|
1316
|
|
|
|
|
|
|
ctx.errors = baseErrors; |
|
1317
|
|
|
|
|
|
|
return; |
|
1318
|
|
|
|
|
|
|
} |
|
1319
|
|
|
|
|
|
|
JS_RUNTIME |
|
1320
|
|
|
|
|
|
|
} |
|
1321
|
|
|
|
|
|
|
|
|
1322
|
0
|
|
|
|
|
0
|
my $sp_qp = _js_quote( $sp ); |
|
1323
|
0
|
|
|
|
|
0
|
push( @body, <<JS_RUNTIME ); |
|
1324
|
|
|
|
|
|
|
ctx.errors = baseErrors; |
|
1325
|
|
|
|
|
|
|
if(!matched) |
|
1326
|
|
|
|
|
|
|
{ |
|
1327
|
|
|
|
|
|
|
_jsv_err(ctx, path, 'anyOf', 'no subschema matched', $sp_qp); |
|
1328
|
|
|
|
|
|
|
} |
|
1329
|
|
|
|
|
|
|
})(); |
|
1330
|
|
|
|
|
|
|
// return; |
|
1331
|
|
|
|
|
|
|
JS_RUNTIME |
|
1332
|
|
|
|
|
|
|
} |
|
1333
|
|
|
|
|
|
|
|
|
1334
|
|
|
|
|
|
|
# oneOf – exactly one must validate |
|
1335
|
0
|
0
|
0
|
|
|
0
|
if( exists( $S->{oneOf} ) && |
|
|
|
|
0
|
|
|
|
|
|
1336
|
|
|
|
|
|
|
ref( $S->{oneOf} ) eq 'ARRAY' && |
|
1337
|
0
|
|
|
|
|
0
|
@{$S->{oneOf}} ) |
|
1338
|
|
|
|
|
|
|
{ |
|
1339
|
0
|
|
|
|
|
0
|
push( @body, <<JS_RUNTIME ); |
|
1340
|
|
|
|
|
|
|
(function() |
|
1341
|
|
|
|
|
|
|
{ |
|
1342
|
|
|
|
|
|
|
var baseErrors = ctx.errors; |
|
1343
|
|
|
|
|
|
|
var hits = 0; |
|
1344
|
|
|
|
|
|
|
|
|
1345
|
|
|
|
|
|
|
JS_RUNTIME |
|
1346
|
|
|
|
|
|
|
|
|
1347
|
0
|
|
|
|
|
0
|
for my $i ( 0 .. $#{$S->{oneOf}} ) |
|
|
0
|
|
|
|
|
0
|
|
|
1348
|
|
|
|
|
|
|
{ |
|
1349
|
0
|
|
|
|
|
0
|
my $sub_sp = _join_ptr( $sp, 'oneOf', $i ); |
|
1350
|
0
|
|
|
|
|
0
|
my $sub_fn = $self->_compile_js_node( $S->{oneOf}->[ $i ], $sub_sp, $seen, $funcs, $counter_ref, $root, $opts ); |
|
1351
|
|
|
|
|
|
|
|
|
1352
|
0
|
|
|
|
|
0
|
push( @body, <<JS_RUNTIME ); |
|
1353
|
|
|
|
|
|
|
ctx.errors = []; |
|
1354
|
|
|
|
|
|
|
// $sub_sp |
|
1355
|
|
|
|
|
|
|
$sub_fn(inst, path + '/oneOf/$i', ctx); |
|
1356
|
|
|
|
|
|
|
if(ctx.errors.length === 0) |
|
1357
|
|
|
|
|
|
|
{ |
|
1358
|
|
|
|
|
|
|
hits++; |
|
1359
|
|
|
|
|
|
|
} |
|
1360
|
|
|
|
|
|
|
JS_RUNTIME |
|
1361
|
|
|
|
|
|
|
} |
|
1362
|
|
|
|
|
|
|
|
|
1363
|
0
|
|
|
|
|
0
|
my $sp_qp = _js_quote( $sp ); |
|
1364
|
0
|
|
|
|
|
0
|
push( @body, <<JS_RUNTIME ); |
|
1365
|
|
|
|
|
|
|
ctx.errors = baseErrors; |
|
1366
|
|
|
|
|
|
|
if(hits !== 1) |
|
1367
|
|
|
|
|
|
|
{ |
|
1368
|
|
|
|
|
|
|
_jsv_err(ctx, path, 'oneOf', 'exactly one subschema must match, but ' + hits + ' did', $sp_qp); |
|
1369
|
|
|
|
|
|
|
} |
|
1370
|
|
|
|
|
|
|
})(); |
|
1371
|
|
|
|
|
|
|
// return; |
|
1372
|
|
|
|
|
|
|
JS_RUNTIME |
|
1373
|
|
|
|
|
|
|
} |
|
1374
|
|
|
|
|
|
|
|
|
1375
|
|
|
|
|
|
|
# not – but we SKIP "negative required" patterns on the client |
|
1376
|
0
|
0
|
|
|
|
0
|
if( exists( $S->{not} ) ) |
|
1377
|
|
|
|
|
|
|
{ |
|
1378
|
0
|
|
|
|
|
0
|
my $skip_not = 0; |
|
1379
|
|
|
|
|
|
|
|
|
1380
|
0
|
0
|
|
|
|
0
|
if( ref( $S->{not} ) eq 'HASH' ) |
|
1381
|
|
|
|
|
|
|
{ |
|
1382
|
0
|
|
|
|
|
0
|
my $N = $S->{not}; |
|
1383
|
|
|
|
|
|
|
|
|
1384
|
|
|
|
|
|
|
# Direct: { "not": { "required": [...] } } |
|
1385
|
0
|
0
|
0
|
|
|
0
|
if( exists( $N->{required} ) && |
|
|
|
0
|
0
|
|
|
|
|
|
1386
|
|
|
|
|
|
|
ref( $N->{required} ) eq 'ARRAY' ) |
|
1387
|
|
|
|
|
|
|
{ |
|
1388
|
0
|
|
|
|
|
0
|
$skip_not = 1; |
|
1389
|
|
|
|
|
|
|
} |
|
1390
|
|
|
|
|
|
|
# Or: { "not": { "anyOf": [ {required:...}, ... ] } } |
|
1391
|
|
|
|
|
|
|
elsif( exists( $N->{anyOf} ) && |
|
1392
|
|
|
|
|
|
|
ref( $N->{anyOf} ) eq 'ARRAY' ) |
|
1393
|
|
|
|
|
|
|
{ |
|
1394
|
0
|
|
|
|
|
0
|
my $all_req = 1; |
|
1395
|
0
|
|
|
|
|
0
|
for my $elt ( @{$N->{anyOf}} ) |
|
|
0
|
|
|
|
|
0
|
|
|
1396
|
|
|
|
|
|
|
{ |
|
1397
|
0
|
0
|
0
|
|
|
0
|
if( !( ref( $elt ) eq 'HASH' && |
|
|
|
|
0
|
|
|
|
|
|
1398
|
|
|
|
|
|
|
exists( $elt->{required} ) && |
|
1399
|
|
|
|
|
|
|
ref( $elt->{required} ) eq 'ARRAY' ) ) |
|
1400
|
|
|
|
|
|
|
{ |
|
1401
|
0
|
|
|
|
|
0
|
$all_req = 0; |
|
1402
|
0
|
|
|
|
|
0
|
last; |
|
1403
|
|
|
|
|
|
|
} |
|
1404
|
|
|
|
|
|
|
} |
|
1405
|
0
|
0
|
|
|
|
0
|
$skip_not = 1 if( $all_req ); |
|
1406
|
|
|
|
|
|
|
} |
|
1407
|
|
|
|
|
|
|
} |
|
1408
|
|
|
|
|
|
|
|
|
1409
|
0
|
0
|
|
|
|
0
|
if( !$skip_not ) |
|
1410
|
|
|
|
|
|
|
{ |
|
1411
|
0
|
|
|
|
|
0
|
my $sub_sp = _join_ptr( $sp, 'not' ); |
|
1412
|
0
|
|
|
|
|
0
|
my $sub_fn = $self->_compile_js_node( $S->{not}, $sub_sp, $seen, $funcs, $counter_ref, $root, $opts ); |
|
1413
|
0
|
|
|
|
|
0
|
my $sp_qp = _js_quote( $sp ); |
|
1414
|
0
|
|
|
|
|
0
|
push( @body, <<JS_RUNTIME ); |
|
1415
|
|
|
|
|
|
|
(function() |
|
1416
|
|
|
|
|
|
|
{ |
|
1417
|
|
|
|
|
|
|
var baseErrors = ctx.errors; |
|
1418
|
|
|
|
|
|
|
ctx.errors = []; |
|
1419
|
|
|
|
|
|
|
// $sub_sp |
|
1420
|
|
|
|
|
|
|
$sub_fn(inst, path + '/not', ctx); |
|
1421
|
|
|
|
|
|
|
var failed = (ctx.errors.length > 0); |
|
1422
|
|
|
|
|
|
|
ctx.errors = baseErrors; |
|
1423
|
|
|
|
|
|
|
if(!failed) |
|
1424
|
|
|
|
|
|
|
{ |
|
1425
|
|
|
|
|
|
|
_jsv_err(ctx, path, 'not', 'instance matched forbidden schema', $sp_qp); |
|
1426
|
|
|
|
|
|
|
} |
|
1427
|
|
|
|
|
|
|
})(); |
|
1428
|
|
|
|
|
|
|
// return; |
|
1429
|
|
|
|
|
|
|
JS_RUNTIME |
|
1430
|
|
|
|
|
|
|
} |
|
1431
|
|
|
|
|
|
|
else |
|
1432
|
|
|
|
|
|
|
{ |
|
1433
|
|
|
|
|
|
|
# Skip "not + required" style rules on the client; |
|
1434
|
|
|
|
|
|
|
# they are enforced server-side only. |
|
1435
|
0
|
|
|
|
|
0
|
push( @body, <<JS_RUNTIME ); |
|
1436
|
|
|
|
|
|
|
// NOTE: 'not' at $sp is a negative-required pattern; skipped client-side. |
|
1437
|
|
|
|
|
|
|
JS_RUNTIME |
|
1438
|
|
|
|
|
|
|
} |
|
1439
|
|
|
|
|
|
|
} |
|
1440
|
|
|
|
|
|
|
|
|
1441
|
|
|
|
|
|
|
# if / then / else (branch errors are enforced, "if" errors are not) |
|
1442
|
0
|
0
|
|
|
|
0
|
if( exists( $S->{if} ) ) |
|
1443
|
|
|
|
|
|
|
{ |
|
1444
|
0
|
|
|
|
|
0
|
my $if_sp = _join_ptr( $sp, 'if' ); |
|
1445
|
0
|
|
|
|
|
0
|
my $if_fn = $self->_compile_js_node( $S->{if}, $if_sp, $seen, $funcs, $counter_ref, $root, $opts ); |
|
1446
|
|
|
|
|
|
|
|
|
1447
|
0
|
|
|
|
|
0
|
push( @body, <<JS_RUNTIME ); |
|
1448
|
|
|
|
|
|
|
(function() |
|
1449
|
|
|
|
|
|
|
{ |
|
1450
|
|
|
|
|
|
|
var baseErrors = ctx.errors; |
|
1451
|
|
|
|
|
|
|
var tmp = []; |
|
1452
|
|
|
|
|
|
|
ctx.errors = tmp; |
|
1453
|
|
|
|
|
|
|
// $if_sp |
|
1454
|
|
|
|
|
|
|
$if_fn(inst, path + '/if', ctx); |
|
1455
|
|
|
|
|
|
|
var failed = (tmp.length > 0); |
|
1456
|
|
|
|
|
|
|
ctx.errors = baseErrors; |
|
1457
|
|
|
|
|
|
|
JS_RUNTIME |
|
1458
|
|
|
|
|
|
|
|
|
1459
|
0
|
0
|
|
|
|
0
|
if( $S->{then} ) |
|
1460
|
|
|
|
|
|
|
{ |
|
1461
|
0
|
|
|
|
|
0
|
my $then_sp = _join_ptr( $sp, 'then' ); |
|
1462
|
0
|
|
|
|
|
0
|
my $then_fn = $self->_compile_js_node( $S->{then}, $then_sp, $seen, $funcs, $counter_ref, $root, $opts ); |
|
1463
|
0
|
|
|
|
|
0
|
push( @body, <<JS_RUNTIME ); |
|
1464
|
|
|
|
|
|
|
if(!failed) |
|
1465
|
|
|
|
|
|
|
{ |
|
1466
|
|
|
|
|
|
|
$then_fn(inst, path + '/then', ctx); |
|
1467
|
|
|
|
|
|
|
} |
|
1468
|
|
|
|
|
|
|
JS_RUNTIME |
|
1469
|
|
|
|
|
|
|
} |
|
1470
|
0
|
0
|
|
|
|
0
|
if( $S->{else} ) |
|
1471
|
|
|
|
|
|
|
{ |
|
1472
|
0
|
|
|
|
|
0
|
my $else_sp = _join_ptr( $sp, 'else' ); |
|
1473
|
0
|
|
|
|
|
0
|
my $else_fn = $self->_compile_js_node( $S->{else}, $else_sp, $seen, $funcs, $counter_ref, $root, $opts ); |
|
1474
|
0
|
|
|
|
|
0
|
push( @body, <<JS_RUNTIME ); |
|
1475
|
|
|
|
|
|
|
else |
|
1476
|
|
|
|
|
|
|
{ |
|
1477
|
|
|
|
|
|
|
$else_fn(inst, path + '/else', ctx); |
|
1478
|
|
|
|
|
|
|
} |
|
1479
|
|
|
|
|
|
|
JS_RUNTIME |
|
1480
|
|
|
|
|
|
|
} |
|
1481
|
|
|
|
|
|
|
|
|
1482
|
0
|
|
|
|
|
0
|
push( @body, <<JS_RUNTIME ); |
|
1483
|
|
|
|
|
|
|
})(); |
|
1484
|
|
|
|
|
|
|
JS_RUNTIME |
|
1485
|
|
|
|
|
|
|
# Do NOT return here; this node can also have local constraints |
|
1486
|
|
|
|
|
|
|
} |
|
1487
|
|
|
|
|
|
|
|
|
1488
|
|
|
|
|
|
|
# uniqueKeys extension when enabled |
|
1489
|
0
|
0
|
0
|
|
|
0
|
if( $self->{unique_keys} && |
|
|
|
|
0
|
|
|
|
|
|
1490
|
|
|
|
|
|
|
exists( $S->{uniqueKeys} ) && |
|
1491
|
|
|
|
|
|
|
ref( $S->{uniqueKeys} ) eq 'ARRAY' ) |
|
1492
|
|
|
|
|
|
|
{ |
|
1493
|
0
|
|
|
|
|
0
|
for my $keyset_ref ( @{$S->{uniqueKeys}} ) |
|
|
0
|
|
|
|
|
0
|
|
|
1494
|
|
|
|
|
|
|
{ |
|
1495
|
0
|
0
|
0
|
|
|
0
|
next unless( ref( $keyset_ref ) eq 'ARRAY' && @$keyset_ref ); |
|
1496
|
|
|
|
|
|
|
|
|
1497
|
0
|
|
|
|
|
0
|
my @keys = map{ _js_quote( $_ ) } @$keyset_ref; |
|
|
0
|
|
|
|
|
0
|
|
|
1498
|
0
|
|
|
|
|
0
|
my $qsp = _js_quote( $sp ); |
|
1499
|
|
|
|
|
|
|
|
|
1500
|
0
|
|
|
|
|
0
|
push( @body, <<JS_RUNTIME ); |
|
1501
|
|
|
|
|
|
|
if(Array.isArray(inst)) |
|
1502
|
|
|
|
|
|
|
{ |
|
1503
|
|
|
|
|
|
|
var seen = {}; |
|
1504
|
|
|
|
|
|
|
for(var i = 0; i < inst.length; i++) |
|
1505
|
|
|
|
|
|
|
{ |
|
1506
|
|
|
|
|
|
|
var item = inst[i]; |
|
1507
|
|
|
|
|
|
|
var key = ''; |
|
1508
|
|
|
|
|
|
|
try |
|
1509
|
|
|
|
|
|
|
{ |
|
1510
|
|
|
|
|
|
|
key = [ @keys ].map(function(k){ return item[k]; }).join('\x1E'); // RS separator |
|
1511
|
|
|
|
|
|
|
} |
|
1512
|
|
|
|
|
|
|
catch(e) {} |
|
1513
|
|
|
|
|
|
|
if(seen.hasOwnProperty(key)) |
|
1514
|
|
|
|
|
|
|
{ |
|
1515
|
|
|
|
|
|
|
_jsv_err(ctx, path, 'uniqueKeys', 'duplicate items with keys [@$keyset_ref]', $qsp); |
|
1516
|
|
|
|
|
|
|
break; |
|
1517
|
|
|
|
|
|
|
} |
|
1518
|
|
|
|
|
|
|
seen[key] = true; |
|
1519
|
|
|
|
|
|
|
} |
|
1520
|
|
|
|
|
|
|
} |
|
1521
|
|
|
|
|
|
|
JS_RUNTIME |
|
1522
|
|
|
|
|
|
|
} |
|
1523
|
|
|
|
|
|
|
} |
|
1524
|
|
|
|
|
|
|
} |
|
1525
|
|
|
|
|
|
|
|
|
1526
|
0
|
0
|
0
|
|
|
0
|
my $has_type_keyword = ( ref( $S ) eq 'HASH' && exists( $S->{type} ) ) ? 1 : 0; |
|
1527
|
|
|
|
|
|
|
|
|
1528
|
|
|
|
|
|
|
# NOTE: type |
|
1529
|
|
|
|
|
|
|
# 1) type |
|
1530
|
0
|
0
|
|
|
|
0
|
if( exists( $S->{type} ) ) |
|
1531
|
|
|
|
|
|
|
{ |
|
1532
|
0
|
0
|
|
|
|
0
|
my @types = ref( $S->{type} ) eq 'ARRAY' ? @{$S->{type}} : ( $S->{type} ); |
|
|
0
|
|
|
|
|
0
|
|
|
1533
|
|
|
|
|
|
|
|
|
1534
|
|
|
|
|
|
|
# Special-case "number" vs "integer": _jsv_typeOf returns "integer" for ints |
|
1535
|
0
|
|
|
|
|
0
|
my @checks; |
|
1536
|
0
|
|
|
|
|
0
|
for my $t ( @types ) |
|
1537
|
|
|
|
|
|
|
{ |
|
1538
|
0
|
0
|
|
|
|
0
|
if( $t eq 'number' ) |
|
1539
|
|
|
|
|
|
|
{ |
|
1540
|
|
|
|
|
|
|
# accept "number" or "integer" from _jsv_typeOf |
|
1541
|
0
|
|
|
|
|
0
|
push( @checks, q{tt === 'number' || tt === 'integer'} ); |
|
1542
|
|
|
|
|
|
|
} |
|
1543
|
|
|
|
|
|
|
else |
|
1544
|
|
|
|
|
|
|
{ |
|
1545
|
0
|
|
|
|
|
0
|
my $qt = _js_quote( $t ); |
|
1546
|
0
|
|
|
|
|
0
|
push( @checks, "tt === $qt" ); |
|
1547
|
|
|
|
|
|
|
} |
|
1548
|
|
|
|
|
|
|
} |
|
1549
|
|
|
|
|
|
|
|
|
1550
|
0
|
|
|
|
|
0
|
my $cond = join( ' || ', @checks ); |
|
1551
|
0
|
0
|
|
|
|
0
|
my $msg = 'expected type ' . |
|
1552
|
|
|
|
|
|
|
( @types == 1 ? $types[0] : '[' . join( ',', @types ) . ']' ) . |
|
1553
|
|
|
|
|
|
|
' but found '; |
|
1554
|
0
|
|
|
|
|
0
|
my $qmsg = _js_quote( $msg ); |
|
1555
|
0
|
|
|
|
|
0
|
my $qsp = _js_quote( $sp ); |
|
1556
|
|
|
|
|
|
|
|
|
1557
|
0
|
|
|
|
|
0
|
push( @body, <<JS_RUNTIME ); |
|
1558
|
|
|
|
|
|
|
var tt = _jsv_typeOf(inst); |
|
1559
|
|
|
|
|
|
|
if(!( $cond )) |
|
1560
|
|
|
|
|
|
|
{ |
|
1561
|
|
|
|
|
|
|
_jsv_err(ctx, path, 'type', $qmsg + tt, $qsp); |
|
1562
|
|
|
|
|
|
|
return; |
|
1563
|
|
|
|
|
|
|
} |
|
1564
|
|
|
|
|
|
|
JS_RUNTIME |
|
1565
|
|
|
|
|
|
|
} |
|
1566
|
|
|
|
|
|
|
|
|
1567
|
|
|
|
|
|
|
# NOTE: enum |
|
1568
|
|
|
|
|
|
|
# 2) enum |
|
1569
|
0
|
0
|
0
|
|
|
0
|
if( exists( $S->{enum} ) && |
|
|
|
|
0
|
|
|
|
|
|
1570
|
|
|
|
|
|
|
ref( $S->{enum} ) eq 'ARRAY' && |
|
1571
|
0
|
|
|
|
|
0
|
@{$S->{enum}} ) |
|
1572
|
|
|
|
|
|
|
{ |
|
1573
|
0
|
|
|
|
|
0
|
my @vals = @{$S->{enum}}; |
|
|
0
|
|
|
|
|
0
|
|
|
1574
|
0
|
|
|
|
|
0
|
my @vs_js = map{ _js_quote( $_ ) } @vals; |
|
|
0
|
|
|
|
|
0
|
|
|
1575
|
0
|
|
|
|
|
0
|
my $qsp = _js_quote( $sp ); |
|
1576
|
|
|
|
|
|
|
|
|
1577
|
0
|
|
|
|
|
0
|
local $" = ', '; |
|
1578
|
0
|
|
|
|
|
0
|
push( @body, <<JS_RUNTIME ); |
|
1579
|
|
|
|
|
|
|
(function() |
|
1580
|
|
|
|
|
|
|
{ |
|
1581
|
|
|
|
|
|
|
var ok = false; |
|
1582
|
|
|
|
|
|
|
var v = inst; |
|
1583
|
|
|
|
|
|
|
var list = [ @vs_js ]; |
|
1584
|
|
|
|
|
|
|
for(var i = 0; i < list.length; i++) |
|
1585
|
|
|
|
|
|
|
{ |
|
1586
|
|
|
|
|
|
|
if(v === list[i]) |
|
1587
|
|
|
|
|
|
|
{ |
|
1588
|
|
|
|
|
|
|
ok = true; |
|
1589
|
|
|
|
|
|
|
break; |
|
1590
|
|
|
|
|
|
|
} |
|
1591
|
|
|
|
|
|
|
} |
|
1592
|
|
|
|
|
|
|
if(!ok) |
|
1593
|
|
|
|
|
|
|
{ |
|
1594
|
|
|
|
|
|
|
_jsv_err(ctx, path, 'enum', 'value is not in enum list', $qsp); |
|
1595
|
|
|
|
|
|
|
} |
|
1596
|
|
|
|
|
|
|
})(); |
|
1597
|
|
|
|
|
|
|
JS_RUNTIME |
|
1598
|
|
|
|
|
|
|
} |
|
1599
|
|
|
|
|
|
|
|
|
1600
|
|
|
|
|
|
|
# NOTE: const |
|
1601
|
|
|
|
|
|
|
# 3) const (primitive only) |
|
1602
|
0
|
0
|
|
|
|
0
|
if( exists( $S->{const} ) ) |
|
1603
|
|
|
|
|
|
|
{ |
|
1604
|
|
|
|
|
|
|
# For simplicity on the JS side, only support primitive const reliably. |
|
1605
|
|
|
|
|
|
|
# (Object/array equality is non-trivial; we keep it minimal for now.) |
|
1606
|
0
|
|
|
|
|
0
|
my $c = $S->{const}; |
|
1607
|
0
|
|
|
|
|
0
|
my $qsp = _js_quote( $sp ); |
|
1608
|
|
|
|
|
|
|
|
|
1609
|
0
|
0
|
|
|
|
0
|
if( !ref( $c ) ) |
|
1610
|
|
|
|
|
|
|
{ |
|
1611
|
0
|
|
|
|
|
0
|
my $cv = _js_quote( $c ); |
|
1612
|
0
|
|
|
|
|
0
|
push( @body, <<JS_RUNTIME ); |
|
1613
|
|
|
|
|
|
|
if(inst !== $cv) |
|
1614
|
|
|
|
|
|
|
{ |
|
1615
|
|
|
|
|
|
|
_jsv_err(ctx, path, 'const', 'value does not match const', $qsp); |
|
1616
|
|
|
|
|
|
|
} |
|
1617
|
|
|
|
|
|
|
JS_RUNTIME |
|
1618
|
|
|
|
|
|
|
} |
|
1619
|
|
|
|
|
|
|
else |
|
1620
|
|
|
|
|
|
|
{ |
|
1621
|
|
|
|
|
|
|
# Complex const -> skip in JS (server will still enforce) |
|
1622
|
0
|
|
|
|
|
0
|
push( @body, <<JS_RUNTIME ); |
|
1623
|
|
|
|
|
|
|
// NOTE: const at $sp is non-primitive; not enforced client-side. |
|
1624
|
|
|
|
|
|
|
|
|
1625
|
|
|
|
|
|
|
JS_RUNTIME |
|
1626
|
|
|
|
|
|
|
} |
|
1627
|
|
|
|
|
|
|
} |
|
1628
|
|
|
|
|
|
|
|
|
1629
|
|
|
|
|
|
|
# NOTE: required |
|
1630
|
|
|
|
|
|
|
# 4) required (objects) |
|
1631
|
0
|
0
|
0
|
|
|
0
|
if( exists( $S->{required} ) && |
|
|
|
|
0
|
|
|
|
|
|
1632
|
|
|
|
|
|
|
ref( $S->{required} ) eq 'ARRAY' && |
|
1633
|
0
|
|
|
|
|
0
|
@{$S->{required}} ) |
|
1634
|
|
|
|
|
|
|
{ |
|
1635
|
0
|
|
|
|
|
0
|
push( @body, <<JS_RUNTIME ); |
|
1636
|
|
|
|
|
|
|
if(_jsv_typeOf(inst) === 'object') |
|
1637
|
|
|
|
|
|
|
{ |
|
1638
|
|
|
|
|
|
|
JS_RUNTIME |
|
1639
|
|
|
|
|
|
|
|
|
1640
|
0
|
|
|
|
|
0
|
for my $p ( @{$S->{required}} ) |
|
|
0
|
|
|
|
|
0
|
|
|
1641
|
|
|
|
|
|
|
{ |
|
1642
|
0
|
|
|
|
|
0
|
my $qp = _js_quote( $p ); |
|
1643
|
0
|
|
|
|
|
0
|
my $sp2 = _join_ptr( $sp, 'properties', $p ); |
|
1644
|
0
|
|
|
|
|
0
|
my $qsp2 = _js_quote( $sp2 ); |
|
1645
|
|
|
|
|
|
|
|
|
1646
|
0
|
|
|
|
|
0
|
my $msg = "required property '$p' is missing"; |
|
1647
|
0
|
|
|
|
|
0
|
my $qmsg = _js_quote( $msg ); |
|
1648
|
|
|
|
|
|
|
|
|
1649
|
0
|
|
|
|
|
0
|
push( @body, <<JS_RUNTIME ); |
|
1650
|
|
|
|
|
|
|
if(!_jsv_hasOwn(inst, $qp)) |
|
1651
|
|
|
|
|
|
|
{ |
|
1652
|
|
|
|
|
|
|
_jsv_err(ctx, path, 'required', $qmsg, $qsp2); |
|
1653
|
|
|
|
|
|
|
} |
|
1654
|
|
|
|
|
|
|
JS_RUNTIME |
|
1655
|
|
|
|
|
|
|
} |
|
1656
|
|
|
|
|
|
|
|
|
1657
|
0
|
|
|
|
|
0
|
push( @body, <<JS_RUNTIME ); |
|
1658
|
|
|
|
|
|
|
} |
|
1659
|
|
|
|
|
|
|
JS_RUNTIME |
|
1660
|
|
|
|
|
|
|
} |
|
1661
|
|
|
|
|
|
|
|
|
1662
|
|
|
|
|
|
|
# NOTE: string && pattern |
|
1663
|
|
|
|
|
|
|
# 5) string length & pattern |
|
1664
|
|
|
|
|
|
|
my $has_string_constraints = |
|
1665
|
|
|
|
|
|
|
exists( $S->{minLength} ) || |
|
1666
|
|
|
|
|
|
|
exists( $S->{maxLength} ) || |
|
1667
|
0
|
|
0
|
|
|
0
|
exists( $S->{pattern} ); |
|
1668
|
|
|
|
|
|
|
|
|
1669
|
0
|
0
|
|
|
|
0
|
if( $has_string_constraints ) |
|
1670
|
|
|
|
|
|
|
{ |
|
1671
|
0
|
|
|
|
|
0
|
my $qsp = _js_quote( $sp ); |
|
1672
|
|
|
|
|
|
|
|
|
1673
|
0
|
|
|
|
|
0
|
push( @body, <<JS_RUNTIME ); |
|
1674
|
|
|
|
|
|
|
if(_jsv_typeOf(inst) === 'string') |
|
1675
|
|
|
|
|
|
|
{ |
|
1676
|
|
|
|
|
|
|
JS_RUNTIME |
|
1677
|
|
|
|
|
|
|
|
|
1678
|
0
|
0
|
|
|
|
0
|
if( exists( $S->{minLength} ) ) |
|
1679
|
|
|
|
|
|
|
{ |
|
1680
|
0
|
|
|
|
|
0
|
my $min = int( $S->{minLength} ); |
|
1681
|
0
|
|
|
|
|
0
|
push( @body, <<JS_RUNTIME ); |
|
1682
|
|
|
|
|
|
|
if(inst.length < $min) |
|
1683
|
|
|
|
|
|
|
{ |
|
1684
|
|
|
|
|
|
|
_jsv_err(ctx, path, 'minLength', 'string shorter than minLength $min', $qsp); |
|
1685
|
|
|
|
|
|
|
} |
|
1686
|
|
|
|
|
|
|
JS_RUNTIME |
|
1687
|
|
|
|
|
|
|
} |
|
1688
|
|
|
|
|
|
|
|
|
1689
|
0
|
0
|
|
|
|
0
|
if( exists( $S->{maxLength} ) ) |
|
1690
|
|
|
|
|
|
|
{ |
|
1691
|
0
|
|
|
|
|
0
|
my $max = int( $S->{maxLength} ); |
|
1692
|
0
|
|
|
|
|
0
|
push( @body, <<JS_RUNTIME ); |
|
1693
|
|
|
|
|
|
|
if(inst.length > $max) |
|
1694
|
|
|
|
|
|
|
{ |
|
1695
|
|
|
|
|
|
|
_jsv_err(ctx, path, 'maxLength', 'string longer than maxLength $max', $qsp); |
|
1696
|
|
|
|
|
|
|
} |
|
1697
|
|
|
|
|
|
|
JS_RUNTIME |
|
1698
|
|
|
|
|
|
|
} |
|
1699
|
|
|
|
|
|
|
|
|
1700
|
0
|
0
|
0
|
|
|
0
|
if( exists( $S->{pattern} ) && |
|
|
|
|
0
|
|
|
|
|
|
1701
|
|
|
|
|
|
|
defined( $S->{pattern} ) && |
|
1702
|
|
|
|
|
|
|
length( $S->{pattern} ) ) |
|
1703
|
|
|
|
|
|
|
{ |
|
1704
|
0
|
|
|
|
|
0
|
my $pat = $S->{pattern}; |
|
1705
|
|
|
|
|
|
|
# my $qpat = _js_quote( $pat ); |
|
1706
|
|
|
|
|
|
|
# from \x{FF70} to \uFF70 |
|
1707
|
|
|
|
|
|
|
# from \p{Katakana} to \p{sc=Katakana} |
|
1708
|
0
|
|
|
|
|
0
|
my $qpat = _re_to_js( $pat ); |
|
1709
|
|
|
|
|
|
|
|
|
1710
|
0
|
0
|
|
|
|
0
|
if( $force_unicode ) |
|
1711
|
|
|
|
|
|
|
{ |
|
1712
|
|
|
|
|
|
|
# ecma >= 2018: always try with "u" flag (Unicode mode) |
|
1713
|
0
|
|
|
|
|
0
|
push( @body, <<JS_RUNTIME ); |
|
1714
|
|
|
|
|
|
|
try |
|
1715
|
|
|
|
|
|
|
{ |
|
1716
|
|
|
|
|
|
|
var re = new RegExp("$qpat", "u"); |
|
1717
|
|
|
|
|
|
|
if(!re.test(inst)) |
|
1718
|
|
|
|
|
|
|
{ |
|
1719
|
|
|
|
|
|
|
_jsv_err(ctx, path, 'pattern', 'string does not match pattern', $qsp); |
|
1720
|
|
|
|
|
|
|
} |
|
1721
|
|
|
|
|
|
|
} |
|
1722
|
|
|
|
|
|
|
catch(e) |
|
1723
|
|
|
|
|
|
|
{ |
|
1724
|
|
|
|
|
|
|
// Browser does not support Unicode property escapes or this pattern. |
|
1725
|
|
|
|
|
|
|
} |
|
1726
|
|
|
|
|
|
|
JS_RUNTIME |
|
1727
|
|
|
|
|
|
|
} |
|
1728
|
|
|
|
|
|
|
else |
|
1729
|
|
|
|
|
|
|
{ |
|
1730
|
|
|
|
|
|
|
# auto / ES5 mode: detect "advanced" patterns |
|
1731
|
0
|
0
|
|
|
|
0
|
if( $pat =~ /\\p\{|\\P\{|\\X|\\R|\(\?[A-Za-z]/ ) |
|
1732
|
|
|
|
|
|
|
{ |
|
1733
|
|
|
|
|
|
|
# Attempt Unicode mode with "u" flag, but gracefully fall back |
|
1734
|
0
|
|
|
|
|
0
|
push( @body, <<JS_RUNTIME ); |
|
1735
|
|
|
|
|
|
|
(function() |
|
1736
|
|
|
|
|
|
|
{ |
|
1737
|
|
|
|
|
|
|
var re = null; |
|
1738
|
|
|
|
|
|
|
try |
|
1739
|
|
|
|
|
|
|
{ |
|
1740
|
|
|
|
|
|
|
re = new RegExp("$qpat", "u"); |
|
1741
|
|
|
|
|
|
|
} |
|
1742
|
|
|
|
|
|
|
catch(e) |
|
1743
|
|
|
|
|
|
|
{ |
|
1744
|
|
|
|
|
|
|
// Older browser; skip Unicode-property-based pattern on client. |
|
1745
|
|
|
|
|
|
|
} |
|
1746
|
|
|
|
|
|
|
if(re && !re.test(inst)) |
|
1747
|
|
|
|
|
|
|
{ |
|
1748
|
|
|
|
|
|
|
_jsv_err(ctx, path, 'pattern', 'string does not match pattern', $qsp); |
|
1749
|
|
|
|
|
|
|
} |
|
1750
|
|
|
|
|
|
|
})(); |
|
1751
|
|
|
|
|
|
|
JS_RUNTIME |
|
1752
|
|
|
|
|
|
|
} |
|
1753
|
|
|
|
|
|
|
else |
|
1754
|
|
|
|
|
|
|
{ |
|
1755
|
|
|
|
|
|
|
# Simple ECMA 5-compatible pattern |
|
1756
|
0
|
|
|
|
|
0
|
push( @body, <<JS_RUNTIME ); |
|
1757
|
|
|
|
|
|
|
try |
|
1758
|
|
|
|
|
|
|
{ |
|
1759
|
|
|
|
|
|
|
var re = new RegExp("$qpat"); |
|
1760
|
|
|
|
|
|
|
if(!re.test(inst)) |
|
1761
|
|
|
|
|
|
|
{ |
|
1762
|
|
|
|
|
|
|
_jsv_err(ctx, path, 'pattern', 'string does not match pattern', $qsp); |
|
1763
|
|
|
|
|
|
|
} |
|
1764
|
|
|
|
|
|
|
} |
|
1765
|
|
|
|
|
|
|
catch(e) |
|
1766
|
|
|
|
|
|
|
{ |
|
1767
|
|
|
|
|
|
|
// If pattern is not JS-compatible, we silently skip on the client. |
|
1768
|
|
|
|
|
|
|
} |
|
1769
|
|
|
|
|
|
|
JS_RUNTIME |
|
1770
|
|
|
|
|
|
|
} |
|
1771
|
|
|
|
|
|
|
} |
|
1772
|
|
|
|
|
|
|
} |
|
1773
|
|
|
|
|
|
|
|
|
1774
|
0
|
|
|
|
|
0
|
push( @body, <<JS_RUNTIME ); |
|
1775
|
|
|
|
|
|
|
} |
|
1776
|
|
|
|
|
|
|
JS_RUNTIME |
|
1777
|
|
|
|
|
|
|
} |
|
1778
|
|
|
|
|
|
|
|
|
1779
|
|
|
|
|
|
|
# NOTE: minimum, maximum, etc |
|
1780
|
|
|
|
|
|
|
# 6) numeric bounds (minimum/maximum/exclusive*) |
|
1781
|
|
|
|
|
|
|
my $has_num_constraints = |
|
1782
|
|
|
|
|
|
|
exists( $S->{minimum} ) || |
|
1783
|
|
|
|
|
|
|
exists( $S->{maximum} ) || |
|
1784
|
|
|
|
|
|
|
exists( $S->{exclusiveMinimum} ) || |
|
1785
|
0
|
|
0
|
|
|
0
|
exists( $S->{exclusiveMaximum} ); |
|
1786
|
|
|
|
|
|
|
|
|
1787
|
0
|
0
|
|
|
|
0
|
if( $has_num_constraints ) |
|
1788
|
|
|
|
|
|
|
{ |
|
1789
|
|
|
|
|
|
|
# For consistency with string/pattern/etc, we report the |
|
1790
|
|
|
|
|
|
|
# schema pointer of the *owning schema* ($sp), not the |
|
1791
|
|
|
|
|
|
|
# child keyword location (.../minimum). |
|
1792
|
0
|
|
|
|
|
0
|
my $qsp_num = _js_quote( $sp ); |
|
1793
|
0
|
|
|
|
|
0
|
my $qmsg_expected_num = _js_quote( 'expected number but found ' ); |
|
1794
|
|
|
|
|
|
|
|
|
1795
|
0
|
|
|
|
|
0
|
push( @body, <<'JS_RUNTIME' ); |
|
1796
|
|
|
|
|
|
|
var t = _jsv_typeOf(inst); |
|
1797
|
|
|
|
|
|
|
|
|
1798
|
|
|
|
|
|
|
// Coerce numeric-looking strings to numbers, for friendlier UX |
|
1799
|
|
|
|
|
|
|
if(t === 'string' && /^[+-]?(?:\d+|\d*\.\d+)$/.test(inst)) |
|
1800
|
|
|
|
|
|
|
{ |
|
1801
|
|
|
|
|
|
|
inst = +inst; |
|
1802
|
|
|
|
|
|
|
t = _jsv_typeOf(inst); |
|
1803
|
|
|
|
|
|
|
} |
|
1804
|
|
|
|
|
|
|
|
|
1805
|
|
|
|
|
|
|
if(t === 'number' || t === 'integer') |
|
1806
|
|
|
|
|
|
|
{ |
|
1807
|
|
|
|
|
|
|
JS_RUNTIME |
|
1808
|
|
|
|
|
|
|
|
|
1809
|
0
|
0
|
|
|
|
0
|
if( exists( $S->{minimum} ) ) |
|
1810
|
|
|
|
|
|
|
{ |
|
1811
|
0
|
|
|
|
|
0
|
my $min = 0 + $S->{minimum}; |
|
1812
|
0
|
|
|
|
|
0
|
push( @body, <<JS_RUNTIME ); |
|
1813
|
|
|
|
|
|
|
if(inst < $min) |
|
1814
|
|
|
|
|
|
|
{ |
|
1815
|
|
|
|
|
|
|
_jsv_err(ctx, path, 'minimum', 'number is less than minimum $min', $qsp_num); |
|
1816
|
|
|
|
|
|
|
} |
|
1817
|
|
|
|
|
|
|
JS_RUNTIME |
|
1818
|
|
|
|
|
|
|
} |
|
1819
|
|
|
|
|
|
|
|
|
1820
|
0
|
0
|
|
|
|
0
|
if( exists( $S->{maximum} ) ) |
|
1821
|
|
|
|
|
|
|
{ |
|
1822
|
0
|
|
|
|
|
0
|
my $max = 0 + $S->{maximum}; |
|
1823
|
0
|
|
|
|
|
0
|
push( @body, <<JS_RUNTIME ); |
|
1824
|
|
|
|
|
|
|
if(inst > $max) |
|
1825
|
|
|
|
|
|
|
{ |
|
1826
|
|
|
|
|
|
|
_jsv_err(ctx, path, 'maximum', 'number is greater than maximum $max', $qsp_num); |
|
1827
|
|
|
|
|
|
|
} |
|
1828
|
|
|
|
|
|
|
JS_RUNTIME |
|
1829
|
|
|
|
|
|
|
} |
|
1830
|
|
|
|
|
|
|
|
|
1831
|
0
|
0
|
|
|
|
0
|
if( exists( $S->{exclusiveMinimum} ) ) |
|
1832
|
|
|
|
|
|
|
{ |
|
1833
|
0
|
|
|
|
|
0
|
my $emin = 0 + $S->{exclusiveMinimum}; |
|
1834
|
0
|
|
|
|
|
0
|
push( @body, <<JS_RUNTIME ); |
|
1835
|
|
|
|
|
|
|
if(inst <= $emin) |
|
1836
|
|
|
|
|
|
|
{ |
|
1837
|
|
|
|
|
|
|
_jsv_err(ctx, path, 'exclusiveMinimum', |
|
1838
|
|
|
|
|
|
|
'number is <= exclusiveMinimum $emin', $qsp_num); |
|
1839
|
|
|
|
|
|
|
} |
|
1840
|
|
|
|
|
|
|
JS_RUNTIME |
|
1841
|
|
|
|
|
|
|
} |
|
1842
|
|
|
|
|
|
|
|
|
1843
|
0
|
0
|
|
|
|
0
|
if( exists( $S->{exclusiveMaximum} ) ) |
|
1844
|
|
|
|
|
|
|
{ |
|
1845
|
0
|
|
|
|
|
0
|
my $emax = 0 + $S->{exclusiveMaximum}; |
|
1846
|
0
|
|
|
|
|
0
|
push( @body, <<JS_RUNTIME ); |
|
1847
|
|
|
|
|
|
|
if(inst >= $emax) |
|
1848
|
|
|
|
|
|
|
{ |
|
1849
|
|
|
|
|
|
|
_jsv_err(ctx, path, 'exclusiveMaximum', |
|
1850
|
|
|
|
|
|
|
'number is >= exclusiveMaximum $emax', $qsp_num); |
|
1851
|
|
|
|
|
|
|
} |
|
1852
|
|
|
|
|
|
|
JS_RUNTIME |
|
1853
|
|
|
|
|
|
|
} |
|
1854
|
|
|
|
|
|
|
|
|
1855
|
|
|
|
|
|
|
# Close the "if(t === 'number' || t === 'integer')" and, if needed, |
|
1856
|
|
|
|
|
|
|
# add a fallback type error when no explicit "type" keyword exists. |
|
1857
|
0
|
0
|
|
|
|
0
|
if( $has_type_keyword ) |
|
1858
|
|
|
|
|
|
|
{ |
|
1859
|
0
|
|
|
|
|
0
|
push( @body, <<'JS_RUNTIME' ); |
|
1860
|
|
|
|
|
|
|
} |
|
1861
|
|
|
|
|
|
|
JS_RUNTIME |
|
1862
|
|
|
|
|
|
|
} |
|
1863
|
|
|
|
|
|
|
else |
|
1864
|
|
|
|
|
|
|
{ |
|
1865
|
0
|
|
|
|
|
0
|
push( @body, <<JS_RUNTIME ); |
|
1866
|
|
|
|
|
|
|
} |
|
1867
|
|
|
|
|
|
|
else |
|
1868
|
|
|
|
|
|
|
{ |
|
1869
|
|
|
|
|
|
|
_jsv_err(ctx, path, 'type', $qmsg_expected_num + t, $qsp_num); |
|
1870
|
|
|
|
|
|
|
} |
|
1871
|
|
|
|
|
|
|
JS_RUNTIME |
|
1872
|
|
|
|
|
|
|
} |
|
1873
|
|
|
|
|
|
|
} |
|
1874
|
|
|
|
|
|
|
|
|
1875
|
|
|
|
|
|
|
# NOTE: items / minItems / maxItems |
|
1876
|
|
|
|
|
|
|
# 7) array items & minItems/maxItems |
|
1877
|
|
|
|
|
|
|
my $has_array_len = |
|
1878
|
|
|
|
|
|
|
exists( $S->{minItems} ) || |
|
1879
|
0
|
|
0
|
|
|
0
|
exists( $S->{maxItems} ); |
|
1880
|
|
|
|
|
|
|
|
|
1881
|
0
|
0
|
0
|
|
|
0
|
if( exists( $S->{items} ) || $has_array_len ) |
|
1882
|
|
|
|
|
|
|
{ |
|
1883
|
0
|
|
|
|
|
0
|
my $qsp = _js_quote( $sp ); |
|
1884
|
|
|
|
|
|
|
|
|
1885
|
|
|
|
|
|
|
# Precompile single-schema "items" |
|
1886
|
0
|
|
|
|
|
0
|
my $items_fn; |
|
1887
|
0
|
0
|
0
|
|
|
0
|
if( exists( $S->{items} ) && ref( $S->{items} ) eq 'HASH' ) |
|
1888
|
|
|
|
|
|
|
{ |
|
1889
|
0
|
|
|
|
|
0
|
my $items_ptr = _join_ptr( $sp, 'items' ); |
|
1890
|
0
|
|
|
|
|
0
|
$items_fn = $self->_compile_js_node( $S->{items}, $items_ptr, $seen, $funcs, $counter_ref, $root, $opts ); |
|
1891
|
|
|
|
|
|
|
} |
|
1892
|
|
|
|
|
|
|
|
|
1893
|
0
|
0
|
|
|
|
0
|
my $min_items = exists( $S->{minItems} ) ? int( $S->{minItems} ) : undef; |
|
1894
|
0
|
0
|
|
|
|
0
|
my $max_items = exists( $S->{maxItems} ) ? int( $S->{maxItems} ) : undef; |
|
1895
|
|
|
|
|
|
|
|
|
1896
|
0
|
|
|
|
|
0
|
my( $qmsg_min, $qmsg_max ); |
|
1897
|
0
|
0
|
|
|
|
0
|
if( defined( $min_items ) ) |
|
1898
|
|
|
|
|
|
|
{ |
|
1899
|
0
|
|
|
|
|
0
|
my $msg_min = "array has fewer than $min_items items"; |
|
1900
|
0
|
|
|
|
|
0
|
$qmsg_min = _js_quote( $msg_min ); |
|
1901
|
|
|
|
|
|
|
} |
|
1902
|
0
|
0
|
|
|
|
0
|
if( defined( $max_items ) ) |
|
1903
|
|
|
|
|
|
|
{ |
|
1904
|
0
|
|
|
|
|
0
|
my $msg_max = "array has more than $max_items items"; |
|
1905
|
0
|
|
|
|
|
0
|
$qmsg_max = _js_quote( $msg_max ); |
|
1906
|
|
|
|
|
|
|
} |
|
1907
|
|
|
|
|
|
|
|
|
1908
|
0
|
|
|
|
|
0
|
push( @body, <<JS_RUNTIME ); |
|
1909
|
|
|
|
|
|
|
if(_jsv_typeOf(inst) === 'array') |
|
1910
|
|
|
|
|
|
|
{ |
|
1911
|
|
|
|
|
|
|
JS_RUNTIME |
|
1912
|
|
|
|
|
|
|
|
|
1913
|
0
|
0
|
|
|
|
0
|
if( defined( $min_items ) ) |
|
1914
|
|
|
|
|
|
|
{ |
|
1915
|
0
|
|
|
|
|
0
|
push( @body, <<JS_RUNTIME ); |
|
1916
|
|
|
|
|
|
|
if(inst.length < $min_items) |
|
1917
|
|
|
|
|
|
|
{ |
|
1918
|
|
|
|
|
|
|
_jsv_err(ctx, path, 'minItems', $qmsg_min, $qsp); |
|
1919
|
|
|
|
|
|
|
} |
|
1920
|
|
|
|
|
|
|
JS_RUNTIME |
|
1921
|
|
|
|
|
|
|
} |
|
1922
|
|
|
|
|
|
|
|
|
1923
|
0
|
0
|
|
|
|
0
|
if( defined( $max_items ) ) |
|
1924
|
|
|
|
|
|
|
{ |
|
1925
|
0
|
|
|
|
|
0
|
push( @body, <<JS_RUNTIME ); |
|
1926
|
|
|
|
|
|
|
if(inst.length > $max_items) |
|
1927
|
|
|
|
|
|
|
{ |
|
1928
|
|
|
|
|
|
|
_jsv_err(ctx, path, 'maxItems', $qmsg_max, $qsp); |
|
1929
|
|
|
|
|
|
|
} |
|
1930
|
|
|
|
|
|
|
JS_RUNTIME |
|
1931
|
|
|
|
|
|
|
} |
|
1932
|
|
|
|
|
|
|
|
|
1933
|
0
|
0
|
|
|
|
0
|
if( $items_fn ) |
|
1934
|
|
|
|
|
|
|
{ |
|
1935
|
0
|
|
|
|
|
0
|
push( @body, <<JS_RUNTIME ); |
|
1936
|
|
|
|
|
|
|
for(var i = 0; i < inst.length; i++) |
|
1937
|
|
|
|
|
|
|
{ |
|
1938
|
|
|
|
|
|
|
$items_fn(inst[i], path + '/' + i, ctx); |
|
1939
|
|
|
|
|
|
|
if(ctx.maxErrors && ctx.errors && ctx.errors.length >= ctx.maxErrors) |
|
1940
|
|
|
|
|
|
|
{ |
|
1941
|
|
|
|
|
|
|
return; |
|
1942
|
|
|
|
|
|
|
} |
|
1943
|
|
|
|
|
|
|
} |
|
1944
|
|
|
|
|
|
|
JS_RUNTIME |
|
1945
|
|
|
|
|
|
|
} |
|
1946
|
|
|
|
|
|
|
|
|
1947
|
0
|
|
|
|
|
0
|
push( @body, <<JS_RUNTIME ); |
|
1948
|
|
|
|
|
|
|
} |
|
1949
|
|
|
|
|
|
|
JS_RUNTIME |
|
1950
|
|
|
|
|
|
|
} |
|
1951
|
|
|
|
|
|
|
|
|
1952
|
|
|
|
|
|
|
# NOTE: properties |
|
1953
|
|
|
|
|
|
|
# 8) properties (objects) – recurse into children |
|
1954
|
0
|
0
|
0
|
|
|
0
|
if( exists( $S->{properties} ) && |
|
1955
|
|
|
|
|
|
|
ref( $S->{properties} ) eq 'HASH' ) |
|
1956
|
|
|
|
|
|
|
{ |
|
1957
|
0
|
|
|
|
|
0
|
push( @body, <<JS_RUNTIME ); |
|
1958
|
|
|
|
|
|
|
if(_jsv_typeOf(inst) === 'object') |
|
1959
|
|
|
|
|
|
|
{ |
|
1960
|
|
|
|
|
|
|
JS_RUNTIME |
|
1961
|
|
|
|
|
|
|
|
|
1962
|
0
|
|
|
|
|
0
|
for my $p ( sort( keys( %{$S->{properties}} ) ) ) |
|
|
0
|
|
|
|
|
0
|
|
|
1963
|
|
|
|
|
|
|
{ |
|
1964
|
0
|
|
|
|
|
0
|
my $child = $S->{properties}->{ $p }; |
|
1965
|
0
|
|
|
|
|
0
|
my $child_ptr = _join_ptr( $sp, 'properties', $p ); |
|
1966
|
0
|
|
|
|
|
0
|
my $child_fn = $self->_compile_js_node( $child, $child_ptr, $seen, $funcs, $counter_ref, $root, $opts ); |
|
1967
|
|
|
|
|
|
|
|
|
1968
|
0
|
|
|
|
|
0
|
my $qp = _js_quote( $p ); |
|
1969
|
0
|
|
|
|
|
0
|
my $path_suffix = '/' . $p; |
|
1970
|
0
|
|
|
|
|
0
|
my $path_suffix_q = _js_quote( $path_suffix ); |
|
1971
|
|
|
|
|
|
|
|
|
1972
|
0
|
|
|
|
|
0
|
push( @body, <<JS_RUNTIME ); |
|
1973
|
|
|
|
|
|
|
if(_jsv_hasOwn(inst, $qp)) |
|
1974
|
|
|
|
|
|
|
{ |
|
1975
|
|
|
|
|
|
|
// $child_ptr |
|
1976
|
|
|
|
|
|
|
$child_fn(inst[$qp], path + $path_suffix_q, ctx); |
|
1977
|
|
|
|
|
|
|
} |
|
1978
|
|
|
|
|
|
|
JS_RUNTIME |
|
1979
|
|
|
|
|
|
|
} |
|
1980
|
|
|
|
|
|
|
|
|
1981
|
0
|
|
|
|
|
0
|
push( @body, <<JS_RUNTIME ); |
|
1982
|
|
|
|
|
|
|
} |
|
1983
|
|
|
|
|
|
|
JS_RUNTIME |
|
1984
|
|
|
|
|
|
|
} |
|
1985
|
|
|
|
|
|
|
|
|
1986
|
|
|
|
|
|
|
# NOTE: definitions – recurse into named schemas |
|
1987
|
0
|
0
|
0
|
|
|
0
|
if( exists( $S->{definitions} ) && |
|
1988
|
|
|
|
|
|
|
ref( $S->{definitions} ) eq 'HASH' ) |
|
1989
|
|
|
|
|
|
|
{ |
|
1990
|
0
|
|
|
|
|
0
|
for my $name ( sort keys %{ $S->{definitions} } ) |
|
|
0
|
|
|
|
|
0
|
|
|
1991
|
|
|
|
|
|
|
{ |
|
1992
|
0
|
|
|
|
|
0
|
my $child = $S->{definitions}->{ $name }; |
|
1993
|
0
|
|
|
|
|
0
|
my $child_ptr = _join_ptr( $sp, 'definitions', $name ); |
|
1994
|
0
|
|
|
|
|
0
|
my $child_fn = $self->_compile_js_node( $child, $child_ptr, $seen, $funcs, $counter_ref, $root, $opts ); |
|
1995
|
|
|
|
|
|
|
|
|
1996
|
|
|
|
|
|
|
# No runtime call needed here — definitions don't validate by themselves. |
|
1997
|
|
|
|
|
|
|
# We just need them compiled so pointer-based lookup can see them. |
|
1998
|
|
|
|
|
|
|
} |
|
1999
|
|
|
|
|
|
|
} |
|
2000
|
|
|
|
|
|
|
|
|
2001
|
|
|
|
|
|
|
# NOTE: contains |
|
2002
|
|
|
|
|
|
|
# 9) contains / minContains / maxContains (arrays) |
|
2003
|
0
|
0
|
|
|
|
0
|
if( exists( $S->{contains} ) ) |
|
2004
|
|
|
|
|
|
|
{ |
|
2005
|
0
|
|
|
|
|
0
|
my $contains_schema = $S->{contains}; |
|
2006
|
0
|
|
|
|
|
0
|
my $contains_ptr = _join_ptr( $sp, 'contains' ); |
|
2007
|
0
|
|
|
|
|
0
|
my $contains_fn = $self->_compile_js_node( $contains_schema, $contains_ptr, $seen, $funcs, $counter_ref, $root, $opts ); |
|
2008
|
0
|
|
|
|
|
0
|
my $qsp_contains = _js_quote( $contains_ptr ); |
|
2009
|
|
|
|
|
|
|
|
|
2010
|
0
|
|
|
|
|
0
|
my $have_min = exists( $S->{minContains} ); |
|
2011
|
0
|
|
|
|
|
0
|
my $have_max = exists( $S->{maxContains} ); |
|
2012
|
0
|
0
|
|
|
|
0
|
my $min = $have_min ? int( $S->{minContains} ) : 0; |
|
2013
|
0
|
0
|
|
|
|
0
|
my $max = $have_max ? int( $S->{maxContains} ) : 0; |
|
2014
|
|
|
|
|
|
|
|
|
2015
|
0
|
|
|
|
|
0
|
my $msg_min = "array has fewer than $min items matching contains subschema"; |
|
2016
|
0
|
|
|
|
|
0
|
my $qmsg_min = _js_quote( $msg_min ); |
|
2017
|
0
|
|
|
|
|
0
|
my $msg_max = "array has more than $max items matching contains subschema"; |
|
2018
|
0
|
|
|
|
|
0
|
my $qmsg_max = _js_quote( $msg_max ); |
|
2019
|
0
|
|
|
|
|
0
|
my $msg_cont = "array does not contain any item matching contains subschema"; |
|
2020
|
0
|
|
|
|
|
0
|
my $qmsg_cont= _js_quote( $msg_cont ); |
|
2021
|
|
|
|
|
|
|
|
|
2022
|
0
|
|
|
|
|
0
|
push( @body, <<JS_RUNTIME ); |
|
2023
|
|
|
|
|
|
|
if(_jsv_typeOf(inst) === 'array') |
|
2024
|
|
|
|
|
|
|
{ |
|
2025
|
|
|
|
|
|
|
var matchCount = 0; |
|
2026
|
|
|
|
|
|
|
for(var i = 0; i < inst.length; i++) |
|
2027
|
|
|
|
|
|
|
{ |
|
2028
|
|
|
|
|
|
|
var tmpCtx = { errors: [], maxErrors: ctx.maxErrors }; |
|
2029
|
|
|
|
|
|
|
// $contains_ptr |
|
2030
|
|
|
|
|
|
|
$contains_fn(inst[i], path + '/' + i, tmpCtx); |
|
2031
|
|
|
|
|
|
|
if(tmpCtx.errors.length === 0) |
|
2032
|
|
|
|
|
|
|
{ |
|
2033
|
|
|
|
|
|
|
matchCount++; |
|
2034
|
|
|
|
|
|
|
} |
|
2035
|
|
|
|
|
|
|
} |
|
2036
|
|
|
|
|
|
|
JS_RUNTIME |
|
2037
|
|
|
|
|
|
|
|
|
2038
|
0
|
0
|
|
|
|
0
|
if( $have_min ) |
|
2039
|
|
|
|
|
|
|
{ |
|
2040
|
0
|
|
|
|
|
0
|
push( @body, <<JS_RUNTIME ); |
|
2041
|
|
|
|
|
|
|
if(matchCount < $min) |
|
2042
|
|
|
|
|
|
|
{ |
|
2043
|
|
|
|
|
|
|
_jsv_err(ctx, path, 'minContains', $qmsg_min, $qsp_contains); |
|
2044
|
|
|
|
|
|
|
} |
|
2045
|
|
|
|
|
|
|
JS_RUNTIME |
|
2046
|
|
|
|
|
|
|
} |
|
2047
|
|
|
|
|
|
|
|
|
2048
|
0
|
0
|
|
|
|
0
|
if( $have_max ) |
|
2049
|
|
|
|
|
|
|
{ |
|
2050
|
0
|
|
|
|
|
0
|
push( @body, <<JS_RUNTIME ); |
|
2051
|
|
|
|
|
|
|
if(matchCount > $max) |
|
2052
|
|
|
|
|
|
|
{ |
|
2053
|
|
|
|
|
|
|
_jsv_err(ctx, path, 'maxContains', $qmsg_max, $qsp_contains); |
|
2054
|
|
|
|
|
|
|
} |
|
2055
|
|
|
|
|
|
|
JS_RUNTIME |
|
2056
|
|
|
|
|
|
|
} |
|
2057
|
|
|
|
|
|
|
|
|
2058
|
|
|
|
|
|
|
# Plain "contains" only if no min/max are present |
|
2059
|
0
|
0
|
0
|
|
|
0
|
if( !$have_min && !$have_max ) |
|
2060
|
|
|
|
|
|
|
{ |
|
2061
|
0
|
|
|
|
|
0
|
push( @body, <<JS_RUNTIME ); |
|
2062
|
|
|
|
|
|
|
if(matchCount === 0) |
|
2063
|
|
|
|
|
|
|
{ |
|
2064
|
|
|
|
|
|
|
_jsv_err(ctx, path, 'contains', $qmsg_cont, $qsp_contains); |
|
2065
|
|
|
|
|
|
|
} |
|
2066
|
|
|
|
|
|
|
JS_RUNTIME |
|
2067
|
|
|
|
|
|
|
} |
|
2068
|
|
|
|
|
|
|
|
|
2069
|
0
|
|
|
|
|
0
|
push( @body, <<JS_RUNTIME ); |
|
2070
|
|
|
|
|
|
|
} |
|
2071
|
|
|
|
|
|
|
JS_RUNTIME |
|
2072
|
|
|
|
|
|
|
} |
|
2073
|
|
|
|
|
|
|
|
|
2074
|
0
|
|
|
|
|
0
|
push( @body, <<JS_RUNTIME ); |
|
2075
|
|
|
|
|
|
|
} |
|
2076
|
|
|
|
|
|
|
JS_RUNTIME |
|
2077
|
|
|
|
|
|
|
|
|
2078
|
0
|
|
|
|
|
0
|
push( @$funcs, join( '', @body ) ); |
|
2079
|
|
|
|
|
|
|
|
|
2080
|
0
|
|
|
|
|
0
|
return( $fn ); |
|
2081
|
|
|
|
|
|
|
} |
|
2082
|
|
|
|
|
|
|
|
|
2083
|
|
|
|
|
|
|
sub _compile_node |
|
2084
|
|
|
|
|
|
|
{ |
|
2085
|
38
|
|
|
38
|
|
90
|
my( $root, $ptr, $S ) = @_; |
|
2086
|
|
|
|
|
|
|
|
|
2087
|
|
|
|
|
|
|
# Non-hash schemas (incl. booleans) => trivial pass |
|
2088
|
|
|
|
|
|
|
return sub |
|
2089
|
|
|
|
|
|
|
{ |
|
2090
|
0
|
|
|
0
|
|
0
|
my( $ctx, $inst ) = @_; |
|
2091
|
0
|
|
|
|
|
0
|
return( { ok => 1, props => {}, items => {} } ); |
|
2092
|
38
|
50
|
|
|
|
90
|
} unless( ref( $S ) eq 'HASH' ); |
|
2093
|
|
|
|
|
|
|
|
|
2094
|
|
|
|
|
|
|
# Capture presence and values so runtime avoids hash lookups |
|
2095
|
38
|
|
|
|
|
92
|
my $has_type = exists( $S->{type} ); |
|
2096
|
38
|
|
|
|
|
65
|
my $type_spec = $S->{type}; |
|
2097
|
|
|
|
|
|
|
|
|
2098
|
38
|
|
|
|
|
59
|
my $has_const = exists( $S->{const} ); |
|
2099
|
38
|
|
|
|
|
83
|
my $const_val = $S->{const}; |
|
2100
|
|
|
|
|
|
|
|
|
2101
|
38
|
|
|
|
|
68
|
my $has_enum = exists( $S->{enum} ); |
|
2102
|
38
|
|
|
|
|
60
|
my $enum_vals = $S->{enum}; |
|
2103
|
|
|
|
|
|
|
|
|
2104
|
38
|
|
|
|
|
66
|
my %numk = map{ $_ => $S->{ $_ } } grep{ exists( $S->{ $_ } ) } |
|
|
2
|
|
|
|
|
9
|
|
|
|
190
|
|
|
|
|
353
|
|
|
2105
|
|
|
|
|
|
|
qw( multipleOf minimum maximum exclusiveMinimum exclusiveMaximum ); |
|
2106
|
|
|
|
|
|
|
|
|
2107
|
38
|
100
|
33
|
|
|
227
|
my $has_strlen = ( exists( $S->{minLength} ) || exists( $S->{maxLength} ) || exists( $S->{pattern} ) ) ? 1 : 0; |
|
2108
|
38
|
|
|
|
|
57
|
my $has_format = exists( $S->{format} ); |
|
2109
|
38
|
|
|
|
|
49
|
my $format_name = $S->{format}; |
|
2110
|
|
|
|
|
|
|
|
|
2111
|
|
|
|
|
|
|
my $has_unique_keys = |
|
2112
|
|
|
|
|
|
|
exists( $S->{uniqueKeys} ) && |
|
2113
|
38
|
|
33
|
|
|
89
|
ref( $S->{uniqueKeys} ) eq 'ARRAY'; |
|
2114
|
|
|
|
|
|
|
|
|
2115
|
|
|
|
|
|
|
# Precompile child closures (same structure our interpreter walks) |
|
2116
|
38
|
|
|
|
|
48
|
my %child; |
|
2117
|
|
|
|
|
|
|
|
|
2118
|
|
|
|
|
|
|
# Arrays |
|
2119
|
38
|
50
|
|
|
|
82
|
if( ref( $S->{prefixItems} ) eq 'ARRAY' ) |
|
2120
|
|
|
|
|
|
|
{ |
|
2121
|
0
|
|
|
|
|
0
|
for my $i ( 0 .. $#{ $S->{prefixItems} } ) |
|
|
0
|
|
|
|
|
0
|
|
|
2122
|
|
|
|
|
|
|
{ |
|
2123
|
0
|
|
|
|
|
0
|
my $cp = _join_ptr( $ptr, "prefixItems/$i" ); |
|
2124
|
0
|
|
|
|
|
0
|
$child{ "prefix:$i" } = _compile_node( $root, $cp, $S->{prefixItems}->[$i] ); |
|
2125
|
|
|
|
|
|
|
} |
|
2126
|
|
|
|
|
|
|
} |
|
2127
|
38
|
50
|
|
|
|
88
|
if( ref( $S->{items} ) eq 'HASH' ) |
|
2128
|
|
|
|
|
|
|
{ |
|
2129
|
0
|
|
|
|
|
0
|
$child{ "items" } = _compile_node( $root, _join_ptr( $ptr, "items" ), $S->{items} ); |
|
2130
|
|
|
|
|
|
|
} |
|
2131
|
38
|
50
|
33
|
|
|
104
|
if( exists( $S->{contains} ) && ref( $S->{contains} ) ) |
|
2132
|
|
|
|
|
|
|
{ |
|
2133
|
0
|
|
|
|
|
0
|
$child{ "contains" } = _compile_node( $root, _join_ptr( $ptr, "contains" ), $S->{contains} ); |
|
2134
|
|
|
|
|
|
|
} |
|
2135
|
38
|
50
|
33
|
|
|
110
|
if( exists( $S->{unevaluatedItems} ) && ref( $S->{unevaluatedItems} ) eq 'HASH' ) |
|
2136
|
|
|
|
|
|
|
{ |
|
2137
|
0
|
|
|
|
|
0
|
$child{ "unevaluatedItems" } = _compile_node( $root, _join_ptr( $ptr, "unevaluatedItems" ), $S->{unevaluatedItems} ); |
|
2138
|
|
|
|
|
|
|
} |
|
2139
|
|
|
|
|
|
|
|
|
2140
|
|
|
|
|
|
|
# Objects |
|
2141
|
38
|
100
|
|
|
|
90
|
if( ref( $S->{properties} ) eq 'HASH' ) |
|
2142
|
|
|
|
|
|
|
{ |
|
2143
|
11
|
|
|
|
|
18
|
for my $k ( keys( %{$S->{properties}} ) ) |
|
|
11
|
|
|
|
|
38
|
|
|
2144
|
|
|
|
|
|
|
{ |
|
2145
|
15
|
|
|
|
|
56
|
my $cp = _join_ptr( $ptr, "properties/$k" ); |
|
2146
|
15
|
|
|
|
|
114
|
$child{ "prop:$k" } = _compile_node( $root, $cp, $S->{properties}->{ $k } ); |
|
2147
|
|
|
|
|
|
|
} |
|
2148
|
|
|
|
|
|
|
} |
|
2149
|
38
|
50
|
|
|
|
87
|
if( ref( $S->{patternProperties} ) eq 'HASH' ) |
|
2150
|
|
|
|
|
|
|
{ |
|
2151
|
0
|
|
|
|
|
0
|
for my $re ( keys( %{ $S->{patternProperties} } ) ) |
|
|
0
|
|
|
|
|
0
|
|
|
2152
|
|
|
|
|
|
|
{ |
|
2153
|
0
|
|
|
|
|
0
|
my $cp = _join_ptr( $ptr, "patternProperties/$re" ); |
|
2154
|
0
|
|
|
|
|
0
|
$child{ "pat:$re" } = _compile_node( $root, $cp, $S->{patternProperties}->{ $re } ); |
|
2155
|
|
|
|
|
|
|
} |
|
2156
|
|
|
|
|
|
|
} |
|
2157
|
38
|
50
|
66
|
|
|
120
|
if( exists( $S->{additionalProperties} ) && ref( $S->{additionalProperties} ) eq 'HASH' ) |
|
2158
|
|
|
|
|
|
|
{ |
|
2159
|
0
|
|
|
|
|
0
|
$child{ "additional" } = _compile_node( $root, _join_ptr( $ptr, "additionalProperties" ), $S->{additionalProperties} ); |
|
2160
|
|
|
|
|
|
|
} |
|
2161
|
38
|
50
|
33
|
|
|
88
|
if( exists( $S->{propertyNames} ) && ref( $S->{propertyNames} ) eq 'HASH' ) |
|
2162
|
|
|
|
|
|
|
{ |
|
2163
|
0
|
|
|
|
|
0
|
$child{ "propnames" } = _compile_node( $root, _join_ptr( $ptr, "propertyNames" ), $S->{propertyNames} ); |
|
2164
|
|
|
|
|
|
|
} |
|
2165
|
38
|
50
|
33
|
|
|
90
|
if( exists( $S->{dependentSchemas} ) && ref( $S->{dependentSchemas} ) eq 'HASH' ) |
|
2166
|
|
|
|
|
|
|
{ |
|
2167
|
0
|
|
|
|
|
0
|
for my $k ( keys( %{$S->{dependentSchemas}} ) ) |
|
|
0
|
|
|
|
|
0
|
|
|
2168
|
|
|
|
|
|
|
{ |
|
2169
|
0
|
|
|
|
|
0
|
my $cp = _join_ptr( $ptr, "dependentSchemas/$k" ); |
|
2170
|
0
|
|
|
|
|
0
|
$child{ "deps:$k" } = _compile_node( $root, $cp, $S->{dependentSchemas}->{ $k } ); |
|
2171
|
|
|
|
|
|
|
} |
|
2172
|
|
|
|
|
|
|
} |
|
2173
|
38
|
50
|
33
|
|
|
106
|
if( exists( $S->{unevaluatedProperties} ) && ref( $S->{unevaluatedProperties} ) eq 'HASH' ) |
|
2174
|
|
|
|
|
|
|
{ |
|
2175
|
0
|
|
|
|
|
0
|
$child{ "ueprops" } = _compile_node( $root, _join_ptr( $ptr, "unevaluatedProperties" ), $S->{unevaluatedProperties} ); |
|
2176
|
|
|
|
|
|
|
} |
|
2177
|
|
|
|
|
|
|
|
|
2178
|
|
|
|
|
|
|
# Combinators |
|
2179
|
38
|
|
|
|
|
71
|
for my $kw ( qw( allOf anyOf oneOf not ) ) |
|
2180
|
|
|
|
|
|
|
{ |
|
2181
|
152
|
50
|
|
|
|
270
|
next unless( exists( $S->{ $kw } ) ); |
|
2182
|
0
|
0
|
|
|
|
0
|
if( $kw eq 'not' ) |
|
2183
|
|
|
|
|
|
|
{ |
|
2184
|
0
|
0
|
|
|
|
0
|
$child{ "not" } = _compile_node( $root, _join_ptr( $ptr, "not" ), $S->{not} ) if( ref( $S->{not} ) ); |
|
2185
|
0
|
|
|
|
|
0
|
next; |
|
2186
|
|
|
|
|
|
|
} |
|
2187
|
0
|
0
|
|
|
|
0
|
if( ref( $S->{ $kw } ) eq 'ARRAY' ) |
|
2188
|
|
|
|
|
|
|
{ |
|
2189
|
0
|
|
|
|
|
0
|
for my $i ( 0 .. $#{$S->{ $kw }} ) |
|
|
0
|
|
|
|
|
0
|
|
|
2190
|
|
|
|
|
|
|
{ |
|
2191
|
0
|
|
|
|
|
0
|
my $cp = _join_ptr( $ptr, "$kw/$i" ); |
|
2192
|
0
|
|
|
|
|
0
|
$child{ "$kw:$i" } = _compile_node( $root, $cp, $S->{ $kw }->[$i] ); |
|
2193
|
|
|
|
|
|
|
} |
|
2194
|
|
|
|
|
|
|
} |
|
2195
|
|
|
|
|
|
|
} |
|
2196
|
|
|
|
|
|
|
|
|
2197
|
|
|
|
|
|
|
# Conditionals |
|
2198
|
38
|
50
|
33
|
|
|
87
|
if( exists( $S->{if} ) && ref( $S->{if} ) eq 'HASH' ) |
|
2199
|
|
|
|
|
|
|
{ |
|
2200
|
0
|
|
|
|
|
0
|
$child{ "if" } = _compile_node( $root, _join_ptr( $ptr, 'if' ), $S->{if} ); |
|
2201
|
0
|
0
|
0
|
|
|
0
|
$child{ "then" } = _compile_node( $root, _join_ptr( $ptr, 'then' ), $S->{then} ) if( exists( $S->{then} ) && ref( $S->{then} ) ); |
|
2202
|
0
|
0
|
0
|
|
|
0
|
$child{ "else" } = _compile_node( $root, _join_ptr( $ptr, 'else' ), $S->{else} ) if( exists( $S->{else} ) && ref( $S->{else} ) ); |
|
2203
|
|
|
|
|
|
|
} |
|
2204
|
|
|
|
|
|
|
|
|
2205
|
|
|
|
|
|
|
# Return specialized validator |
|
2206
|
|
|
|
|
|
|
return sub |
|
2207
|
|
|
|
|
|
|
{ |
|
2208
|
35
|
|
|
35
|
|
64
|
my( $ctx, $inst ) = @_; |
|
2209
|
|
|
|
|
|
|
|
|
2210
|
|
|
|
|
|
|
# Parity with interpreter: trace node visit |
|
2211
|
35
|
100
|
|
|
|
113
|
_t( $ctx, $ptr, 'node', undef, 'visit' ) if( $ctx->{trace_on} ); |
|
2212
|
|
|
|
|
|
|
|
|
2213
|
|
|
|
|
|
|
# Type / const / enum |
|
2214
|
35
|
50
|
|
|
|
63
|
if( $has_type ) { _k_type( $ctx, $inst, $type_spec, $ptr ) or return( _fail() ); } |
|
|
35
|
50
|
|
|
|
93
|
|
|
2215
|
35
|
0
|
|
|
|
76
|
if( $has_const ) { _k_const( $ctx, $inst, $const_val, $ptr ) or return( _fail() ); } |
|
|
0
|
50
|
|
|
|
0
|
|
|
2216
|
35
|
0
|
|
|
|
76
|
if( $has_enum ) { _k_enum( $ctx, $inst, $enum_vals, $ptr ) or return( _fail() ); } |
|
|
0
|
50
|
|
|
|
0
|
|
|
2217
|
|
|
|
|
|
|
|
|
2218
|
|
|
|
|
|
|
# uniqueKeys extension (compiled path) |
|
2219
|
35
|
0
|
33
|
|
|
84
|
if( $ctx->{unique_keys} && $has_unique_keys && ref( $inst ) eq 'ARRAY' ) |
|
|
|
|
33
|
|
|
|
|
|
2220
|
|
|
|
|
|
|
{ |
|
2221
|
0
|
|
|
|
|
0
|
my $r = _k_unique_keys( $ctx, $ptr, $S->{uniqueKeys}, $inst ); |
|
2222
|
0
|
0
|
|
|
|
0
|
return( $r ) unless( $r->{ok} ); |
|
2223
|
|
|
|
|
|
|
} |
|
2224
|
|
|
|
|
|
|
|
|
2225
|
|
|
|
|
|
|
# Numbers |
|
2226
|
35
|
100
|
|
|
|
78
|
if( _is_number( $inst ) ) |
|
2227
|
|
|
|
|
|
|
{ |
|
2228
|
6
|
|
|
|
|
14
|
for my $k ( qw( multipleOf minimum maximum exclusiveMinimum exclusiveMaximum ) ) |
|
2229
|
|
|
|
|
|
|
{ |
|
2230
|
27
|
100
|
|
|
|
67
|
next unless( exists( $numk{ $k } ) ); |
|
2231
|
2
|
100
|
|
|
|
8
|
_k_number( $ctx, $inst, $k, $numk{$k}, $ptr ) or return( _fail() ); |
|
2232
|
|
|
|
|
|
|
} |
|
2233
|
|
|
|
|
|
|
} |
|
2234
|
|
|
|
|
|
|
|
|
2235
|
|
|
|
|
|
|
# Strings |
|
2236
|
34
|
100
|
66
|
|
|
105
|
if( !ref( $inst ) && defined( $inst ) ) |
|
2237
|
|
|
|
|
|
|
{ |
|
2238
|
17
|
100
|
|
|
|
32
|
if( $has_strlen ) { _k_string( $ctx, $inst, $S, $ptr ) or return( _fail() ); } |
|
|
2
|
100
|
|
|
|
7
|
|
|
2239
|
16
|
100
|
|
|
|
49
|
if( $has_format ) { _k_format( $ctx, $inst, $format_name, $ptr ) or return( _fail() ); } |
|
|
2
|
100
|
|
|
|
9
|
|
|
2240
|
|
|
|
|
|
|
|
|
2241
|
|
|
|
|
|
|
# contentEncoding / contentMediaType / contentSchema (compiled path) |
|
2242
|
15
|
50
|
33
|
|
|
151
|
if( exists( $S->{contentEncoding} ) || |
|
|
|
|
33
|
|
|
|
|
|
2243
|
|
|
|
|
|
|
exists( $S->{contentMediaType} ) || |
|
2244
|
|
|
|
|
|
|
exists( $S->{contentSchema} ) ) |
|
2245
|
|
|
|
|
|
|
{ |
|
2246
|
0
|
0
|
|
|
|
0
|
my $assert = $ctx->{content_assert} ? 1 : 0; |
|
2247
|
0
|
|
|
|
|
0
|
my $bytes = "$inst"; |
|
2248
|
0
|
|
|
|
|
0
|
my $decoded_ref; |
|
2249
|
|
|
|
|
|
|
|
|
2250
|
0
|
0
|
|
|
|
0
|
if( exists( $S->{contentEncoding} ) ) |
|
2251
|
|
|
|
|
|
|
{ |
|
2252
|
0
|
|
|
|
|
0
|
my $dec = _content_decode( $ctx, $S->{contentEncoding}, $bytes ); |
|
2253
|
0
|
0
|
|
|
|
0
|
if( !defined( $dec ) ) |
|
2254
|
|
|
|
|
|
|
{ |
|
2255
|
0
|
0
|
|
|
|
0
|
return( _err_res( $ctx, $ptr, "contentEncoding '$S->{contentEncoding}' decode failed", 'contentEncoding' ) ) if( $assert ); |
|
2256
|
|
|
|
|
|
|
} |
|
2257
|
|
|
|
|
|
|
else |
|
2258
|
|
|
|
|
|
|
{ |
|
2259
|
0
|
|
|
|
|
0
|
$bytes = $dec; |
|
2260
|
|
|
|
|
|
|
} |
|
2261
|
|
|
|
|
|
|
} |
|
2262
|
|
|
|
|
|
|
|
|
2263
|
0
|
0
|
|
|
|
0
|
if( exists( $S->{contentMediaType} ) ) |
|
2264
|
|
|
|
|
|
|
{ |
|
2265
|
0
|
|
|
|
|
0
|
my( $mt, $params ) = _parse_media_type( $S->{contentMediaType} ); |
|
2266
|
0
|
0
|
|
|
|
0
|
if( my $cb = $ctx->{media_validators}->{ $mt } ) |
|
2267
|
|
|
|
|
|
|
{ |
|
2268
|
0
|
|
|
|
|
0
|
my( $ok, $msg, $maybe_decoded ) = _safe_invoke( $cb, $bytes, $params ); |
|
2269
|
0
|
0
|
|
|
|
0
|
if( !$ok ) |
|
2270
|
|
|
|
|
|
|
{ |
|
2271
|
0
|
0
|
0
|
|
|
0
|
return( _err_res( $ctx, $ptr, ( $msg || "contentMediaType '$mt' validation failed" ), 'contentMediaType' ) ) if( $assert ); |
|
2272
|
|
|
|
|
|
|
} |
|
2273
|
|
|
|
|
|
|
# If the media validator decoded into a Perl structure, keep it |
|
2274
|
0
|
0
|
|
|
|
0
|
$decoded_ref = $maybe_decoded if( ref( $maybe_decoded ) ); |
|
2275
|
|
|
|
|
|
|
# If it produced new octets/text, keep that too |
|
2276
|
0
|
0
|
0
|
|
|
0
|
$bytes = $maybe_decoded if( defined( $maybe_decoded ) && !ref( $maybe_decoded ) ); |
|
2277
|
|
|
|
|
|
|
} |
|
2278
|
|
|
|
|
|
|
else |
|
2279
|
|
|
|
|
|
|
{ |
|
2280
|
0
|
0
|
0
|
|
|
0
|
if( $mt =~ m{\Atext/} && ( ( $params->{charset} || '' ) =~ /\Autf-?8\z/i ) ) |
|
|
|
|
0
|
|
|
|
|
|
2281
|
|
|
|
|
|
|
{ |
|
2282
|
0
|
|
|
|
|
0
|
local $@; |
|
2283
|
|
|
|
|
|
|
my $ok = eval |
|
2284
|
0
|
0
|
|
|
|
0
|
{ |
|
2285
|
0
|
|
|
|
|
0
|
require Encode; |
|
2286
|
0
|
|
|
|
|
0
|
Encode::decode( 'UTF-8', $bytes, Encode::FB_CROAK ); |
|
2287
|
0
|
|
|
|
|
0
|
1; |
|
2288
|
|
|
|
|
|
|
} ? 1 : 0; |
|
2289
|
0
|
0
|
0
|
|
|
0
|
if( !$ok && $assert ) |
|
2290
|
|
|
|
|
|
|
{ |
|
2291
|
0
|
|
|
|
|
0
|
return( _err_res( $ctx, $ptr, "contentMediaType '$mt' invalid UTF-8", 'contentMediaType' ) ); |
|
2292
|
|
|
|
|
|
|
} |
|
2293
|
|
|
|
|
|
|
} |
|
2294
|
|
|
|
|
|
|
} |
|
2295
|
|
|
|
|
|
|
} |
|
2296
|
|
|
|
|
|
|
|
|
2297
|
0
|
0
|
|
|
|
0
|
if( exists( $S->{contentSchema} ) ) |
|
2298
|
|
|
|
|
|
|
{ |
|
2299
|
0
|
|
|
|
|
0
|
my $val; |
|
2300
|
|
|
|
|
|
|
|
|
2301
|
0
|
0
|
|
|
|
0
|
if( ref( $decoded_ref ) ) |
|
2302
|
|
|
|
|
|
|
{ |
|
2303
|
|
|
|
|
|
|
# already decoded by media validator (e.g. application/json) |
|
2304
|
0
|
|
|
|
|
0
|
$val = $decoded_ref; |
|
2305
|
|
|
|
|
|
|
} |
|
2306
|
|
|
|
|
|
|
else |
|
2307
|
|
|
|
|
|
|
{ |
|
2308
|
0
|
|
|
|
|
0
|
local $@; |
|
2309
|
|
|
|
|
|
|
# still a string of bytes; try JSON decode now |
|
2310
|
0
|
|
|
|
|
0
|
$val = eval{ JSON->new->allow_nonref(1)->utf8(1)->decode( $bytes ) }; |
|
|
0
|
|
|
|
|
0
|
|
|
2311
|
|
|
|
|
|
|
} |
|
2312
|
|
|
|
|
|
|
|
|
2313
|
0
|
0
|
|
|
|
0
|
if( !defined( $val ) ) |
|
2314
|
|
|
|
|
|
|
{ |
|
2315
|
0
|
0
|
|
|
|
0
|
return( _err_res( $ctx, $ptr, "contentSchema present but payload not JSON-decodable", 'contentSchema' ) ) if( $assert ); |
|
2316
|
|
|
|
|
|
|
} |
|
2317
|
|
|
|
|
|
|
else |
|
2318
|
|
|
|
|
|
|
{ |
|
2319
|
0
|
|
|
|
|
0
|
my $r = _v( $ctx, _join_ptr( $ptr, 'contentSchema' ), $S->{contentSchema}, $val ); |
|
2320
|
0
|
0
|
|
|
|
0
|
return( $r ) unless( $r->{ok} ); |
|
2321
|
|
|
|
|
|
|
} |
|
2322
|
|
|
|
|
|
|
} |
|
2323
|
|
|
|
|
|
|
} |
|
2324
|
|
|
|
|
|
|
} |
|
2325
|
|
|
|
|
|
|
|
|
2326
|
32
|
|
|
|
|
96
|
my %ann_props; |
|
2327
|
|
|
|
|
|
|
my %ann_items; |
|
2328
|
|
|
|
|
|
|
|
|
2329
|
|
|
|
|
|
|
# Arrays |
|
2330
|
32
|
50
|
|
|
|
67
|
if( ref( $inst ) eq 'ARRAY' ) |
|
2331
|
|
|
|
|
|
|
{ |
|
2332
|
0
|
|
|
|
|
0
|
my $r = _k_array_all( $ctx, $ptr, $S, $inst ); |
|
2333
|
0
|
0
|
|
|
|
0
|
return( $r ) unless( $r->{ok} ); |
|
2334
|
0
|
|
|
|
|
0
|
%ann_items = ( %ann_items, %{$r->{items}} ); |
|
|
0
|
|
|
|
|
0
|
|
|
2335
|
|
|
|
|
|
|
} |
|
2336
|
|
|
|
|
|
|
|
|
2337
|
|
|
|
|
|
|
# Objects |
|
2338
|
32
|
100
|
|
|
|
64
|
if( ref( $inst ) eq 'HASH' ) |
|
2339
|
|
|
|
|
|
|
{ |
|
2340
|
17
|
|
|
|
|
51
|
my $r = _k_object_all( $ctx, $ptr, $S, $inst ); |
|
2341
|
17
|
100
|
|
|
|
98
|
return( $r ) unless( $r->{ok} ); |
|
2342
|
10
|
|
|
|
|
21
|
%ann_props = ( %ann_props, %{$r->{props}} ); |
|
|
10
|
|
|
|
|
38
|
|
|
2343
|
|
|
|
|
|
|
} |
|
2344
|
|
|
|
|
|
|
|
|
2345
|
|
|
|
|
|
|
# Combinators |
|
2346
|
|
|
|
|
|
|
# allOf: all subschemas must pass, and we merge their annotations. |
|
2347
|
25
|
50
|
33
|
|
|
66
|
if( exists( $S->{allOf} ) && ref( $S->{allOf} ) eq 'ARRAY' ) |
|
2348
|
|
|
|
|
|
|
{ |
|
2349
|
0
|
|
|
|
|
0
|
my( %p, %it ); |
|
2350
|
0
|
|
|
|
|
0
|
for my $i ( 0 .. $#{ $S->{allOf} } ) |
|
|
0
|
|
|
|
|
0
|
|
|
2351
|
|
|
|
|
|
|
{ |
|
2352
|
0
|
|
|
|
|
0
|
my $r = $child{ "allOf:$i" }->( $ctx, $inst ); |
|
2353
|
0
|
0
|
|
|
|
0
|
return $r unless $r->{ok}; |
|
2354
|
0
|
|
|
|
|
0
|
%p = ( %p, %{$r->{props}} ); |
|
|
0
|
|
|
|
|
0
|
|
|
2355
|
0
|
|
|
|
|
0
|
%it = ( %it, %{$r->{items}} ); |
|
|
0
|
|
|
|
|
0
|
|
|
2356
|
|
|
|
|
|
|
} |
|
2357
|
0
|
|
|
|
|
0
|
%ann_props = ( %ann_props, %p ); |
|
2358
|
0
|
|
|
|
|
0
|
%ann_items = ( %ann_items, %it ); |
|
2359
|
|
|
|
|
|
|
} |
|
2360
|
|
|
|
|
|
|
|
|
2361
|
|
|
|
|
|
|
# anyOf: at least one subschema must pass; do NOT leak errors from |
|
2362
|
|
|
|
|
|
|
# non-selected branches into the main context. |
|
2363
|
25
|
50
|
33
|
|
|
66
|
if( exists( $S->{anyOf} ) && ref( $S->{anyOf} ) eq 'ARRAY' ) |
|
2364
|
|
|
|
|
|
|
{ |
|
2365
|
0
|
|
|
|
|
0
|
my $ok = 0; |
|
2366
|
0
|
|
|
|
|
0
|
my( %p, %it ); |
|
2367
|
0
|
|
|
|
|
0
|
my @branch_errs; |
|
2368
|
|
|
|
|
|
|
|
|
2369
|
0
|
|
|
|
|
0
|
for my $i ( 0 .. $#{ $S->{anyOf} } ) |
|
|
0
|
|
|
|
|
0
|
|
|
2370
|
|
|
|
|
|
|
{ |
|
2371
|
|
|
|
|
|
|
# Shadow context for this branch |
|
2372
|
0
|
|
|
|
|
0
|
my %shadow = %$ctx; |
|
2373
|
0
|
|
|
|
|
0
|
my @errs; |
|
2374
|
0
|
|
|
|
|
0
|
$shadow{errors} = \@errs; |
|
2375
|
0
|
|
|
|
|
0
|
$shadow{error_count} = 0; |
|
2376
|
|
|
|
|
|
|
|
|
2377
|
0
|
|
|
|
|
0
|
my $r = $child{ "anyOf:$i" }->( \%shadow, $inst ); |
|
2378
|
|
|
|
|
|
|
|
|
2379
|
0
|
0
|
|
|
|
0
|
if( $r->{ok} ) |
|
2380
|
|
|
|
|
|
|
{ |
|
2381
|
0
|
|
|
|
|
0
|
$ok = 1; |
|
2382
|
0
|
|
|
|
|
0
|
%p = ( %p, %{$r->{props}} ); |
|
|
0
|
|
|
|
|
0
|
|
|
2383
|
0
|
|
|
|
|
0
|
%it = ( %it, %{$r->{items}} ); |
|
|
0
|
|
|
|
|
0
|
|
|
2384
|
0
|
|
|
|
|
0
|
last; |
|
2385
|
|
|
|
|
|
|
} |
|
2386
|
|
|
|
|
|
|
|
|
2387
|
0
|
|
|
|
|
0
|
push( @branch_errs, \@errs ); |
|
2388
|
|
|
|
|
|
|
} |
|
2389
|
|
|
|
|
|
|
|
|
2390
|
0
|
0
|
|
|
|
0
|
unless( $ok ) |
|
2391
|
|
|
|
|
|
|
{ |
|
2392
|
|
|
|
|
|
|
# No branch matched: merge collected branch errors into main context |
|
2393
|
0
|
|
|
|
|
0
|
for my $aref ( @branch_errs ) |
|
2394
|
|
|
|
|
|
|
{ |
|
2395
|
0
|
0
|
|
|
|
0
|
next unless( @$aref ); |
|
2396
|
0
|
|
|
|
|
0
|
push( @{$ctx->{errors}}, @$aref ); |
|
|
0
|
|
|
|
|
0
|
|
|
2397
|
0
|
|
|
|
|
0
|
$ctx->{error_count} += scalar( @$aref ); |
|
2398
|
|
|
|
|
|
|
} |
|
2399
|
|
|
|
|
|
|
|
|
2400
|
0
|
|
|
|
|
0
|
return( _err_res( $ctx, $ptr, "instance does not satisfy anyOf", 'anyOf' ) ); |
|
2401
|
|
|
|
|
|
|
} |
|
2402
|
|
|
|
|
|
|
|
|
2403
|
0
|
|
|
|
|
0
|
%ann_props = ( %ann_props, %p ); |
|
2404
|
0
|
|
|
|
|
0
|
%ann_items = ( %ann_items, %it ); |
|
2405
|
|
|
|
|
|
|
} |
|
2406
|
|
|
|
|
|
|
|
|
2407
|
|
|
|
|
|
|
# oneOf: exactly one subschema must pass; again, isolate branch errors. |
|
2408
|
25
|
50
|
33
|
|
|
123
|
if( exists( $S->{oneOf} ) && ref( $S->{oneOf} ) eq 'ARRAY' ) |
|
2409
|
|
|
|
|
|
|
{ |
|
2410
|
0
|
|
|
|
|
0
|
my $hits = 0; |
|
2411
|
|
|
|
|
|
|
|
|
2412
|
0
|
|
|
|
|
0
|
for my $i ( 0 .. $#{ $S->{oneOf} } ) |
|
|
0
|
|
|
|
|
0
|
|
|
2413
|
|
|
|
|
|
|
{ |
|
2414
|
0
|
|
|
|
|
0
|
my %shadow = %$ctx; |
|
2415
|
0
|
|
|
|
|
0
|
my @errs; |
|
2416
|
0
|
|
|
|
|
0
|
$shadow{errors} = \@errs; |
|
2417
|
0
|
|
|
|
|
0
|
$shadow{error_count} = 0; |
|
2418
|
|
|
|
|
|
|
|
|
2419
|
0
|
|
|
|
|
0
|
my $r = $child{ "oneOf:$i" }->( \%shadow, $inst ); |
|
2420
|
0
|
0
|
|
|
|
0
|
$hits++ if( $r->{ok} ); |
|
2421
|
|
|
|
|
|
|
} |
|
2422
|
|
|
|
|
|
|
|
|
2423
|
0
|
0
|
|
|
|
0
|
return( _err_res( $ctx, $ptr, "instance satisfies $hits subschemas in oneOf (expected exactly 1)", 'oneOf' ) ) |
|
2424
|
|
|
|
|
|
|
unless( $hits == 1 ); |
|
2425
|
|
|
|
|
|
|
} |
|
2426
|
|
|
|
|
|
|
|
|
2427
|
|
|
|
|
|
|
# not: the inner schema must **fail**; its own errors are irrelevant |
|
2428
|
|
|
|
|
|
|
# on success, so we run it entirely in a shadow context. |
|
2429
|
25
|
50
|
33
|
|
|
54
|
if( exists( $S->{not} ) && ref( $S->{not} ) ) |
|
2430
|
|
|
|
|
|
|
{ |
|
2431
|
0
|
|
|
|
|
0
|
my %shadow = %$ctx; |
|
2432
|
0
|
|
|
|
|
0
|
my @errs; |
|
2433
|
0
|
|
|
|
|
0
|
$shadow{errors} = \@errs; |
|
2434
|
0
|
|
|
|
|
0
|
$shadow{error_count} = 0; |
|
2435
|
|
|
|
|
|
|
|
|
2436
|
0
|
|
|
|
|
0
|
my $r = $child{ "not" }->( \%shadow, $inst ); |
|
2437
|
|
|
|
|
|
|
|
|
2438
|
|
|
|
|
|
|
# If inner schema passes, then "not" fails |
|
2439
|
|
|
|
|
|
|
return( _err_res( $ctx, $ptr, "instance matches forbidden not-schema", 'not' ) ) |
|
2440
|
0
|
0
|
|
|
|
0
|
if( $r->{ok} ); |
|
2441
|
|
|
|
|
|
|
|
|
2442
|
|
|
|
|
|
|
# Otherwise, "not" is satisfied; ignore inner errors entirely |
|
2443
|
|
|
|
|
|
|
} |
|
2444
|
|
|
|
|
|
|
|
|
2445
|
|
|
|
|
|
|
# Conditionals |
|
2446
|
25
|
50
|
33
|
|
|
70
|
if( exists( $S->{if} ) && ref( $S->{if} ) ) |
|
2447
|
|
|
|
|
|
|
{ |
|
2448
|
0
|
|
|
|
|
0
|
my $cond = $child{ "if" }->( $ctx, $inst ); |
|
2449
|
0
|
0
|
|
|
|
0
|
if( $cond->{ok} ) |
|
2450
|
|
|
|
|
|
|
{ |
|
2451
|
0
|
0
|
|
|
|
0
|
if( exists( $child{ "then" } ) ) |
|
2452
|
|
|
|
|
|
|
{ |
|
2453
|
0
|
|
|
|
|
0
|
my $r = $child{ "then" }->( $ctx, $inst ); |
|
2454
|
0
|
0
|
|
|
|
0
|
return( $r ) unless( $r->{ok} ); |
|
2455
|
|
|
|
|
|
|
} |
|
2456
|
|
|
|
|
|
|
} |
|
2457
|
|
|
|
|
|
|
else |
|
2458
|
|
|
|
|
|
|
{ |
|
2459
|
0
|
0
|
|
|
|
0
|
if( exists( $child{ "else" } ) ) |
|
2460
|
|
|
|
|
|
|
{ |
|
2461
|
0
|
|
|
|
|
0
|
my $r = $child{ "else" }->( $ctx, $inst ); |
|
2462
|
0
|
0
|
|
|
|
0
|
return( $r ) unless( $r->{ok} ); |
|
2463
|
|
|
|
|
|
|
} |
|
2464
|
|
|
|
|
|
|
} |
|
2465
|
|
|
|
|
|
|
} |
|
2466
|
|
|
|
|
|
|
|
|
2467
|
25
|
100
|
|
|
|
73
|
_t( $ctx, $ptr, 'node', undef, 'pass' ) if( $ctx->{trace_on} ); |
|
2468
|
25
|
|
|
|
|
125
|
return( { ok => 1, props => \%ann_props, items => \%ann_items } ); |
|
2469
|
38
|
|
|
|
|
905
|
}; |
|
2470
|
|
|
|
|
|
|
} |
|
2471
|
|
|
|
|
|
|
|
|
2472
|
|
|
|
|
|
|
# Compilation / Indexing |
|
2473
|
|
|
|
|
|
|
sub _compile_root |
|
2474
|
|
|
|
|
|
|
{ |
|
2475
|
114
|
|
|
114
|
|
313
|
my( $schema ) = @_; |
|
2476
|
|
|
|
|
|
|
|
|
2477
|
114
|
50
|
|
|
|
463
|
if( ref( $schema ) eq 'HASH' ) |
|
2478
|
|
|
|
|
|
|
{ |
|
2479
|
114
|
100
|
33
|
|
|
571
|
$schema->{'$defs'} ||= delete( $schema->{definitions} ) if( exists( $schema->{definitions} ) ); |
|
2480
|
|
|
|
|
|
|
} |
|
2481
|
|
|
|
|
|
|
|
|
2482
|
114
|
100
|
66
|
|
|
1034
|
my $base = _normalize_uri( ( ref( $schema ) eq 'HASH' && $schema->{'$id'} ) ? $schema->{'$id'} : '#' ); |
|
2483
|
|
|
|
|
|
|
|
|
2484
|
114
|
|
|
|
|
288
|
my $anchors = {}; |
|
2485
|
114
|
|
|
|
|
202
|
my $id_index = {}; |
|
2486
|
|
|
|
|
|
|
|
|
2487
|
114
|
|
|
|
|
500
|
_index_schema_202012( $schema, $base, '#', $anchors, $id_index ); |
|
2488
|
|
|
|
|
|
|
|
|
2489
|
|
|
|
|
|
|
return({ |
|
2490
|
114
|
|
|
|
|
908
|
schema => $schema, |
|
2491
|
|
|
|
|
|
|
anchors => $anchors, # "#/a/b/0" |
|
2492
|
|
|
|
|
|
|
id_index => $id_index, # absolute IDs and #anchors |
|
2493
|
|
|
|
|
|
|
base => $base, |
|
2494
|
|
|
|
|
|
|
fn_index => {}, # cache of ptr => compiled closure |
|
2495
|
|
|
|
|
|
|
}); |
|
2496
|
|
|
|
|
|
|
} |
|
2497
|
|
|
|
|
|
|
|
|
2498
|
|
|
|
|
|
|
sub _content_decode |
|
2499
|
|
|
|
|
|
|
{ |
|
2500
|
9
|
|
|
9
|
|
22
|
my( $ctx, $enc, $s ) = @_; |
|
2501
|
9
|
|
50
|
|
|
26
|
$enc = lc( $enc // '' ); |
|
2502
|
|
|
|
|
|
|
|
|
2503
|
9
|
100
|
|
|
|
30
|
if( my $cb = $ctx->{content_decoders}->{ $enc } ) |
|
2504
|
|
|
|
|
|
|
{ |
|
2505
|
3
|
|
|
|
|
10
|
my( $ok, $msg, $out ) = _safe_invoke( $cb, $s ); |
|
2506
|
3
|
50
|
|
|
|
14
|
return( $ok ? $out : undef ); |
|
2507
|
|
|
|
|
|
|
} |
|
2508
|
|
|
|
|
|
|
|
|
2509
|
6
|
50
|
|
|
|
15
|
if( $enc eq 'base64' ) |
|
2510
|
|
|
|
|
|
|
{ |
|
2511
|
6
|
|
|
|
|
36
|
my $out = _strict_base64_decode( $s ); |
|
2512
|
|
|
|
|
|
|
# undef on failure is exactly what we want |
|
2513
|
6
|
|
|
|
|
15
|
return( $out ); |
|
2514
|
|
|
|
|
|
|
} |
|
2515
|
|
|
|
|
|
|
|
|
2516
|
|
|
|
|
|
|
# Unknown encoding (annotation only unless assert) |
|
2517
|
0
|
|
|
|
|
0
|
return; |
|
2518
|
|
|
|
|
|
|
} |
|
2519
|
|
|
|
|
|
|
|
|
2520
|
|
|
|
|
|
|
# Errors, utils, pointers, URIs |
|
2521
|
|
|
|
|
|
|
sub _err |
|
2522
|
|
|
|
|
|
|
{ |
|
2523
|
176
|
|
|
176
|
|
389
|
my( $ctx, $schema_ptr, $msg, $keyword ) = @_; |
|
2524
|
176
|
50
|
|
|
|
612
|
return(0) if( $ctx->{error_count} >= $ctx->{max_errors} ); |
|
2525
|
|
|
|
|
|
|
|
|
2526
|
|
|
|
|
|
|
# Instance path: use current ptr_stack top if available, else '#' |
|
2527
|
|
|
|
|
|
|
my $inst_path = $ctx->{ptr_stack} && @{$ctx->{ptr_stack}} |
|
2528
|
176
|
50
|
33
|
|
|
540
|
? ( $ctx->{ptr_stack}->[-1] // '#' ) |
|
|
|
|
50
|
|
|
|
|
|
2529
|
|
|
|
|
|
|
: '#'; |
|
2530
|
|
|
|
|
|
|
|
|
2531
|
176
|
|
|
|
|
345
|
push( @{$ctx->{errors}}, JSON::Schema::Validate::Error->new( |
|
|
176
|
|
|
|
|
1059
|
|
|
2532
|
|
|
|
|
|
|
path => $inst_path, |
|
2533
|
|
|
|
|
|
|
message => $msg, |
|
2534
|
|
|
|
|
|
|
keyword => $keyword, # may be undef (back-compat) |
|
2535
|
|
|
|
|
|
|
schema_pointer => $schema_ptr, # where in the schema this came from |
|
2536
|
|
|
|
|
|
|
)); |
|
2537
|
176
|
|
|
|
|
309
|
$ctx->{error_count}++; |
|
2538
|
176
|
|
|
|
|
550
|
return(0); |
|
2539
|
|
|
|
|
|
|
} |
|
2540
|
|
|
|
|
|
|
|
|
2541
|
|
|
|
|
|
|
sub _err_res |
|
2542
|
|
|
|
|
|
|
{ |
|
2543
|
85
|
|
|
85
|
|
175
|
my( $ctx, $schema_ptr, $msg, $keyword ) = @_; |
|
2544
|
85
|
|
|
|
|
231
|
_err( $ctx, $schema_ptr, $msg, $keyword ); |
|
2545
|
85
|
|
|
|
|
370
|
return( { ok => 0, props => {}, items => {} } ); |
|
2546
|
|
|
|
|
|
|
} |
|
2547
|
|
|
|
|
|
|
|
|
2548
|
|
|
|
|
|
|
# Used by _prune* methods |
|
2549
|
|
|
|
|
|
|
sub _extract_array_shape |
|
2550
|
|
|
|
|
|
|
{ |
|
2551
|
1
|
|
|
1
|
|
2
|
my( $self, $schema, $out ) = @_; |
|
2552
|
|
|
|
|
|
|
|
|
2553
|
1
|
50
|
|
|
|
4
|
return unless( ref( $schema ) eq 'HASH' ); |
|
2554
|
|
|
|
|
|
|
|
|
2555
|
1
|
50
|
|
|
|
4
|
if( ref( $schema->{prefixItems} ) eq 'ARRAY' ) |
|
2556
|
|
|
|
|
|
|
{ |
|
2557
|
1
|
|
|
|
|
3
|
my $src = $schema->{prefixItems}; |
|
2558
|
1
|
|
|
|
|
5
|
for my $i ( 0 .. $#$src ) |
|
2559
|
|
|
|
|
|
|
{ |
|
2560
|
|
|
|
|
|
|
# First win: do not override an existing prefix schema at this index |
|
2561
|
|
|
|
|
|
|
$out->{prefix_items}->[ $i ] = $src->[ $i ] |
|
2562
|
1
|
50
|
|
|
|
6
|
unless( defined( $out->{prefix_items}->[ $i ] ) ); |
|
2563
|
|
|
|
|
|
|
} |
|
2564
|
|
|
|
|
|
|
} |
|
2565
|
|
|
|
|
|
|
|
|
2566
|
1
|
50
|
33
|
|
|
8
|
if( exists( $schema->{items} ) && ref( $schema->{items} ) eq 'HASH' ) |
|
2567
|
|
|
|
|
|
|
{ |
|
2568
|
|
|
|
|
|
|
# Again, first win: if we already have items from another branch, keep it. |
|
2569
|
1
|
50
|
|
|
|
41
|
$out->{items} = $schema->{items} unless( $out->{items} ); |
|
2570
|
|
|
|
|
|
|
} |
|
2571
|
|
|
|
|
|
|
|
|
2572
|
|
|
|
|
|
|
# allOf mixins for arrays as well |
|
2573
|
1
|
50
|
|
|
|
8
|
if( ref( $schema->{allOf} ) eq 'ARRAY' ) |
|
2574
|
|
|
|
|
|
|
{ |
|
2575
|
0
|
|
|
|
|
0
|
foreach my $sub ( @{$schema->{allOf}} ) |
|
|
0
|
|
|
|
|
0
|
|
|
2576
|
|
|
|
|
|
|
{ |
|
2577
|
0
|
|
|
|
|
0
|
$self->_extract_array_shape( $sub, $out ); |
|
2578
|
|
|
|
|
|
|
} |
|
2579
|
|
|
|
|
|
|
} |
|
2580
|
|
|
|
|
|
|
|
|
2581
|
|
|
|
|
|
|
# anyOf / oneOf / not ignored for same reason as objects. |
|
2582
|
|
|
|
|
|
|
} |
|
2583
|
|
|
|
|
|
|
|
|
2584
|
|
|
|
|
|
|
# Used by _prune* methods |
|
2585
|
|
|
|
|
|
|
sub _extract_object_shape |
|
2586
|
|
|
|
|
|
|
{ |
|
2587
|
8
|
|
|
8
|
|
15
|
my( $self, $schema, $out ) = @_; |
|
2588
|
|
|
|
|
|
|
|
|
2589
|
8
|
50
|
|
|
|
25
|
return unless( ref( $schema ) eq 'HASH' ); |
|
2590
|
|
|
|
|
|
|
|
|
2591
|
|
|
|
|
|
|
# Direct properties |
|
2592
|
8
|
100
|
|
|
|
19
|
if( ref( $schema->{properties} ) eq 'HASH' ) |
|
2593
|
|
|
|
|
|
|
{ |
|
2594
|
6
|
|
|
|
|
8
|
foreach my $k ( keys( %{$schema->{properties}} ) ) |
|
|
6
|
|
|
|
|
20
|
|
|
2595
|
|
|
|
|
|
|
{ |
|
2596
|
|
|
|
|
|
|
# First win: do not override an already-collected subschema |
|
2597
|
|
|
|
|
|
|
$out->{props}->{ $k } = $schema->{properties}->{ $k } |
|
2598
|
7
|
50
|
|
|
|
27
|
unless( exists( $out->{props}->{ $k } ) ); |
|
2599
|
|
|
|
|
|
|
} |
|
2600
|
|
|
|
|
|
|
} |
|
2601
|
|
|
|
|
|
|
|
|
2602
|
|
|
|
|
|
|
# patternProperties |
|
2603
|
8
|
100
|
|
|
|
23
|
if( ref( $schema->{patternProperties} ) eq 'HASH' ) |
|
2604
|
|
|
|
|
|
|
{ |
|
2605
|
1
|
|
|
|
|
2
|
foreach my $re ( keys( %{$schema->{patternProperties}} ) ) |
|
|
1
|
|
|
|
|
4
|
|
|
2606
|
|
|
|
|
|
|
{ |
|
2607
|
1
|
|
|
|
|
3
|
push( @{$out->{patterns}}, [ $re, $schema->{patternProperties}->{ $re } ] ); |
|
|
1
|
|
|
|
|
5
|
|
|
2608
|
|
|
|
|
|
|
} |
|
2609
|
|
|
|
|
|
|
} |
|
2610
|
|
|
|
|
|
|
|
|
2611
|
|
|
|
|
|
|
# additionalProperties |
|
2612
|
8
|
100
|
|
|
|
31
|
if( exists( $schema->{additionalProperties} ) ) |
|
2613
|
|
|
|
|
|
|
{ |
|
2614
|
7
|
|
|
|
|
13
|
my $ap = $schema->{additionalProperties}; |
|
2615
|
|
|
|
|
|
|
|
|
2616
|
|
|
|
|
|
|
# JSON booleans or plain scalars |
|
2617
|
7
|
50
|
33
|
|
|
64
|
if( !ref( $ap ) || ( blessed( $ap ) && $ap->isa( 'JSON::PP::Boolean' ) ) ) |
|
|
|
0
|
33
|
|
|
|
|
|
2618
|
|
|
|
|
|
|
{ |
|
2619
|
7
|
100
|
|
|
|
65
|
if( $ap ) |
|
2620
|
|
|
|
|
|
|
{ |
|
2621
|
|
|
|
|
|
|
# true: additionalProperties allowed; keep any stricter setting we might already have |
|
2622
|
1
|
50
|
33
|
|
|
16
|
$out->{allow_ap} = 1 unless( defined( $out->{allow_ap} ) && !$out->{allow_ap} ); |
|
2623
|
|
|
|
|
|
|
# do not touch ap_schema here |
|
2624
|
|
|
|
|
|
|
} |
|
2625
|
|
|
|
|
|
|
else |
|
2626
|
|
|
|
|
|
|
{ |
|
2627
|
|
|
|
|
|
|
# false: forbidden regardless of earlier "true" |
|
2628
|
6
|
|
|
|
|
58
|
$out->{allow_ap} = 0; |
|
2629
|
6
|
|
|
|
|
13
|
$out->{ap_schema} = undef; |
|
2630
|
|
|
|
|
|
|
} |
|
2631
|
|
|
|
|
|
|
} |
|
2632
|
|
|
|
|
|
|
elsif( ref( $ap ) eq 'HASH' ) |
|
2633
|
|
|
|
|
|
|
{ |
|
2634
|
|
|
|
|
|
|
# Schema for additionals |
|
2635
|
0
|
|
|
|
|
0
|
$out->{allow_ap} = 1; |
|
2636
|
0
|
|
|
|
|
0
|
$out->{ap_schema} = $ap; |
|
2637
|
|
|
|
|
|
|
} |
|
2638
|
|
|
|
|
|
|
} |
|
2639
|
|
|
|
|
|
|
|
|
2640
|
|
|
|
|
|
|
# allOf mixins: merge their object keywords as well. |
|
2641
|
8
|
100
|
|
|
|
26
|
if( ref( $schema->{allOf} ) eq 'ARRAY' ) |
|
2642
|
|
|
|
|
|
|
{ |
|
2643
|
1
|
|
|
|
|
3
|
foreach my $sub ( @{$schema->{allOf}} ) |
|
|
1
|
|
|
|
|
5
|
|
|
2644
|
|
|
|
|
|
|
{ |
|
2645
|
2
|
|
|
|
|
9
|
$self->_extract_object_shape( $sub, $out ); |
|
2646
|
|
|
|
|
|
|
} |
|
2647
|
|
|
|
|
|
|
} |
|
2648
|
|
|
|
|
|
|
|
|
2649
|
|
|
|
|
|
|
# NOTE: |
|
2650
|
|
|
|
|
|
|
# We intentionally ignore anyOf / oneOf / not for pruning. |
|
2651
|
|
|
|
|
|
|
# Without performing full validation against each branch, we cannot |
|
2652
|
|
|
|
|
|
|
# safely decide which properties are truly forbidden, so we err on |
|
2653
|
|
|
|
|
|
|
# the side of *keeping* more rather than over-pruning. |
|
2654
|
|
|
|
|
|
|
} |
|
2655
|
|
|
|
|
|
|
|
|
2656
|
91
|
|
|
91
|
|
537
|
sub _fail { return( { ok => 0, props => {}, items => {} } ); } |
|
2657
|
|
|
|
|
|
|
|
|
2658
|
|
|
|
|
|
|
sub _first_error_text |
|
2659
|
|
|
|
|
|
|
{ |
|
2660
|
0
|
|
|
0
|
|
0
|
my( $errs ) = @_; |
|
2661
|
0
|
0
|
|
|
|
0
|
return( '' ) unless( @$errs ); |
|
2662
|
0
|
|
|
|
|
0
|
my $e = $errs->[0]; |
|
2663
|
0
|
|
|
|
|
0
|
return( "$e" ); |
|
2664
|
|
|
|
|
|
|
} |
|
2665
|
|
|
|
|
|
|
|
|
2666
|
|
|
|
|
|
|
sub _get_args_as_hash |
|
2667
|
|
|
|
|
|
|
{ |
|
2668
|
341
|
|
|
341
|
|
620
|
my $self = shift( @_ ); |
|
2669
|
341
|
100
|
|
|
|
1160
|
return( {} ) if( !scalar( @_ ) ); |
|
2670
|
21
|
|
|
|
|
52
|
my $ref = {}; |
|
2671
|
21
|
100
|
66
|
|
|
187
|
if( scalar( @_ ) == 1 && |
|
|
|
50
|
50
|
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
2672
|
|
|
|
|
|
|
defined( $_[0] ) && |
|
2673
|
|
|
|
|
|
|
( ref( $_[0] ) || '' ) eq 'HASH' ) |
|
2674
|
|
|
|
|
|
|
{ |
|
2675
|
2
|
|
|
|
|
5
|
$ref = shift( @_ ); |
|
2676
|
|
|
|
|
|
|
} |
|
2677
|
|
|
|
|
|
|
elsif( !( scalar( @_ ) % 2 ) ) |
|
2678
|
|
|
|
|
|
|
{ |
|
2679
|
19
|
|
|
|
|
85
|
$ref = { @_ }; |
|
2680
|
|
|
|
|
|
|
} |
|
2681
|
|
|
|
|
|
|
else |
|
2682
|
|
|
|
|
|
|
{ |
|
2683
|
0
|
|
|
|
|
0
|
die( "Uneven number of parameters provided: '", join( "', '", map( overload::StrVal( $_ ), @_ ) ), "'" ); |
|
2684
|
|
|
|
|
|
|
} |
|
2685
|
21
|
|
|
|
|
51
|
return( $ref ); |
|
2686
|
|
|
|
|
|
|
} |
|
2687
|
|
|
|
|
|
|
|
|
2688
|
|
|
|
|
|
|
sub _index_schema_202012 |
|
2689
|
|
|
|
|
|
|
{ |
|
2690
|
1999
|
|
|
1999
|
|
3310
|
my( $node, $base_uri, $ptr, $anchors, $id_index ) = @_; |
|
2691
|
|
|
|
|
|
|
|
|
2692
|
1999
|
|
|
|
|
5074
|
$anchors->{ $ptr } = $node; |
|
2693
|
|
|
|
|
|
|
|
|
2694
|
1999
|
|
|
|
|
2181
|
my $here_base = $base_uri; |
|
2695
|
|
|
|
|
|
|
|
|
2696
|
1999
|
100
|
|
|
|
5088
|
if( ref( $node ) eq 'HASH' ) |
|
|
|
100
|
|
|
|
|
|
|
2697
|
|
|
|
|
|
|
{ |
|
2698
|
790
|
50
|
66
|
|
|
1655
|
if( exists( $node->{'$id'} ) && defined( $node->{'$id'} ) && $node->{'$id'} ne '' ) |
|
|
|
|
66
|
|
|
|
|
|
2699
|
|
|
|
|
|
|
{ |
|
2700
|
15
|
|
|
|
|
56
|
$here_base = _resolve_uri( $base_uri, $node->{'$id'} ); |
|
2701
|
15
|
|
|
|
|
45
|
$id_index->{ $here_base } = $node; |
|
2702
|
|
|
|
|
|
|
} |
|
2703
|
|
|
|
|
|
|
|
|
2704
|
790
|
50
|
66
|
|
|
1549
|
if( exists( $node->{'$anchor'} ) && defined( $node->{'$anchor'} ) && $node->{'$anchor'} ne '' ) |
|
|
|
|
66
|
|
|
|
|
|
2705
|
|
|
|
|
|
|
{ |
|
2706
|
1
|
|
|
|
|
3
|
my $abs = $here_base . '#' . $node->{'$anchor'}; |
|
2707
|
1
|
|
|
|
|
3
|
$id_index->{ $abs } = $node; |
|
2708
|
|
|
|
|
|
|
} |
|
2709
|
|
|
|
|
|
|
|
|
2710
|
790
|
50
|
66
|
|
|
1379
|
if( exists( $node->{'$dynamicAnchor'} ) && defined( $node->{'$dynamicAnchor'} ) && $node->{'$dynamicAnchor'} ne '' ) |
|
|
|
|
66
|
|
|
|
|
|
2711
|
|
|
|
|
|
|
{ |
|
2712
|
2
|
|
|
|
|
7
|
my $abs = $here_base . '#dyn:' . $node->{'$dynamicAnchor'}; |
|
2713
|
2
|
|
|
|
|
33
|
$id_index->{ $abs } = $node; |
|
2714
|
|
|
|
|
|
|
} |
|
2715
|
|
|
|
|
|
|
|
|
2716
|
790
|
|
|
|
|
2200
|
for my $k ( sort( keys( %$node ) ) ) |
|
2717
|
|
|
|
|
|
|
{ |
|
2718
|
1569
|
|
|
|
|
2146
|
my $child = $node->{ $k }; |
|
2719
|
1569
|
|
|
|
|
2246
|
my $child_ptr = _join_ptr( $ptr, $k ); |
|
2720
|
1569
|
|
|
|
|
3084
|
_index_schema_202012( $child, $here_base, $child_ptr, $anchors, $id_index ); |
|
2721
|
|
|
|
|
|
|
} |
|
2722
|
|
|
|
|
|
|
} |
|
2723
|
|
|
|
|
|
|
elsif( ref( $node ) eq 'ARRAY' ) |
|
2724
|
|
|
|
|
|
|
{ |
|
2725
|
233
|
|
|
|
|
515
|
for my $i ( 0 .. $#$node ) |
|
2726
|
|
|
|
|
|
|
{ |
|
2727
|
311
|
|
|
|
|
489
|
my $child = $node->[$i]; |
|
2728
|
311
|
|
|
|
|
437
|
my $child_ptr = _join_ptr( $ptr, $i ); |
|
2729
|
311
|
|
|
|
|
586
|
_index_schema_202012( $child, $here_base, $child_ptr, $anchors, $id_index ); |
|
2730
|
|
|
|
|
|
|
} |
|
2731
|
|
|
|
|
|
|
} |
|
2732
|
|
|
|
|
|
|
} |
|
2733
|
|
|
|
|
|
|
|
|
2734
|
|
|
|
|
|
|
sub _inst_addr |
|
2735
|
|
|
|
|
|
|
{ |
|
2736
|
364
|
|
|
364
|
|
605
|
my( $inst, $ptr ) = @_; |
|
2737
|
364
|
50
|
|
|
|
688
|
return( "SCALAR:$ptr" ) unless( ref( $inst ) ); |
|
2738
|
364
|
|
|
|
|
1119
|
return( ref( $inst ) . ':' . refaddr( $inst ) ); |
|
2739
|
|
|
|
|
|
|
} |
|
2740
|
|
|
|
|
|
|
|
|
2741
|
|
|
|
|
|
|
# truthy helpers |
|
2742
|
28
|
100
|
|
28
|
|
244
|
sub _is_hash { my $v = shift; return ref($v) eq 'HASH' ? 1 : 0; } |
|
|
28
|
|
|
|
|
144
|
|
|
2743
|
|
|
|
|
|
|
|
|
2744
|
|
|
|
|
|
|
sub _is_number |
|
2745
|
|
|
|
|
|
|
{ |
|
2746
|
923
|
|
|
923
|
|
1533
|
my( $v ) = @_; |
|
2747
|
|
|
|
|
|
|
|
|
2748
|
923
|
100
|
|
|
|
2004
|
return(0) if( ref( $v ) ); |
|
2749
|
588
|
100
|
|
|
|
996
|
return(0) unless( defined( $v ) ); |
|
2750
|
|
|
|
|
|
|
|
|
2751
|
|
|
|
|
|
|
# Strict JSON typing: accept only scalars that actually carry numeric flags. |
|
2752
|
|
|
|
|
|
|
# JSON marks numbers with IOK/NOK; plain strings (even "12") will not have them. |
|
2753
|
582
|
|
|
|
|
2236
|
my $sv = B::svref_2object( \$v ); |
|
2754
|
582
|
|
|
|
|
2656
|
my $flags = $sv->FLAGS; |
|
2755
|
|
|
|
|
|
|
|
|
2756
|
582
|
|
|
|
|
856
|
local $@; |
|
2757
|
|
|
|
|
|
|
# SVf_IOK = 0x02000000, SVf_NOK = 0x04000000 on most builds; |
|
2758
|
|
|
|
|
|
|
# we do not hardcode constants—B::SV’s FLAGS is stable to test with these bitmasks. |
|
2759
|
|
|
|
|
|
|
# Use string eval to avoid importing platform-specific constants. |
|
2760
|
582
|
|
50
|
|
|
868
|
my $SVf_IOK = eval{ B::SVf_IOK() } || 0x02000000; |
|
2761
|
582
|
|
50
|
|
|
738
|
my $SVf_NOK = eval{ B::SVf_NOK() } || 0x04000000; |
|
2762
|
|
|
|
|
|
|
|
|
2763
|
582
|
100
|
|
|
|
2650
|
return( ( $flags & ( $SVf_IOK | $SVf_NOK ) ) ? 1 : 0 ); |
|
2764
|
|
|
|
|
|
|
} |
|
2765
|
|
|
|
|
|
|
|
|
2766
|
28
|
50
|
|
28
|
|
46
|
sub _is_true { my $v = shift( @_ ); return( ref( $v ) eq 'HASH' ? 0 : $v ? 1 : 0 ); } |
|
|
28
|
100
|
|
|
|
399
|
|
|
2767
|
|
|
|
|
|
|
|
|
2768
|
|
|
|
|
|
|
sub _join_ptr |
|
2769
|
|
|
|
|
|
|
{ |
|
2770
|
2917
|
|
|
2917
|
|
4781
|
my( $base, @tokens ) = @_; |
|
2771
|
|
|
|
|
|
|
|
|
2772
|
|
|
|
|
|
|
# Default base to '#' if not provided |
|
2773
|
2917
|
50
|
33
|
|
|
8351
|
$base = '#' unless( defined( $base ) && length( $base ) ); |
|
2774
|
|
|
|
|
|
|
|
|
2775
|
2917
|
|
|
|
|
3297
|
my $ptr = $base; |
|
2776
|
|
|
|
|
|
|
|
|
2777
|
2917
|
|
|
|
|
3464
|
for my $token ( @tokens ) |
|
2778
|
|
|
|
|
|
|
{ |
|
2779
|
2917
|
50
|
|
|
|
4394
|
next unless( defined( $token ) ); |
|
2780
|
|
|
|
|
|
|
|
|
2781
|
|
|
|
|
|
|
# Proper rfc6901 JSON Pointer escaping |
|
2782
|
2917
|
|
|
|
|
4187
|
$token =~ s/~/~0/g; |
|
2783
|
2917
|
|
|
|
|
4129
|
$token =~ s/\//~1/g; |
|
2784
|
|
|
|
|
|
|
|
|
2785
|
2917
|
100
|
|
|
|
4954
|
if( $ptr eq '#' ) |
|
2786
|
|
|
|
|
|
|
{ |
|
2787
|
1015
|
|
|
|
|
1904
|
$ptr = "#/$token"; |
|
2788
|
|
|
|
|
|
|
} |
|
2789
|
|
|
|
|
|
|
else |
|
2790
|
|
|
|
|
|
|
{ |
|
2791
|
1902
|
|
|
|
|
3096
|
$ptr .= "/$token"; |
|
2792
|
|
|
|
|
|
|
} |
|
2793
|
|
|
|
|
|
|
} |
|
2794
|
|
|
|
|
|
|
|
|
2795
|
2917
|
|
|
|
|
6016
|
return( $ptr ); |
|
2796
|
|
|
|
|
|
|
} |
|
2797
|
|
|
|
|
|
|
|
|
2798
|
|
|
|
|
|
|
sub _js_quote |
|
2799
|
|
|
|
|
|
|
{ |
|
2800
|
0
|
|
|
0
|
|
0
|
my $s = shift( @_ ); |
|
2801
|
0
|
0
|
|
|
|
0
|
$s = '' unless( defined( $s ) ); |
|
2802
|
0
|
|
|
|
|
0
|
$s =~ s/\\/\\\\/g; |
|
2803
|
0
|
|
|
|
|
0
|
$s =~ s/'/\\'/g; |
|
2804
|
0
|
|
|
|
|
0
|
$s =~ s/\r\n/\n/g; |
|
2805
|
0
|
|
|
|
|
0
|
$s =~ s/\r/\n/g; |
|
2806
|
0
|
|
|
|
|
0
|
$s =~ s/\n/\\n/g; |
|
2807
|
0
|
|
|
|
|
0
|
return( "'$s'" ); |
|
2808
|
|
|
|
|
|
|
} |
|
2809
|
|
|
|
|
|
|
|
|
2810
|
|
|
|
|
|
|
sub _json_equal |
|
2811
|
|
|
|
|
|
|
{ |
|
2812
|
21
|
|
|
21
|
|
27
|
my( $a, $b ) = @_; |
|
2813
|
21
|
|
|
|
|
68
|
return( _canon( $a ) eq _canon( $b ) ); |
|
2814
|
|
|
|
|
|
|
} |
|
2815
|
|
|
|
|
|
|
|
|
2816
|
|
|
|
|
|
|
# Very small JSON Pointer resolver for internal refs ("#/...") for JS compile |
|
2817
|
|
|
|
|
|
|
sub _jsv_resolve_internal_ref |
|
2818
|
|
|
|
|
|
|
{ |
|
2819
|
9
|
|
|
9
|
|
16
|
my( $root, $ptr ) = @_; |
|
2820
|
|
|
|
|
|
|
|
|
2821
|
9
|
50
|
33
|
|
|
36
|
return( $root ) if( !defined( $ptr ) || $ptr eq '' || $ptr eq '#' ); |
|
|
|
|
33
|
|
|
|
|
|
2822
|
|
|
|
|
|
|
|
|
2823
|
|
|
|
|
|
|
# Expect something like "#/definitions/address" |
|
2824
|
9
|
|
|
|
|
23
|
$ptr =~ s/^#//; |
|
2825
|
|
|
|
|
|
|
|
|
2826
|
9
|
|
|
|
|
34
|
my @tokens = split( /\//, $ptr ); |
|
2827
|
9
|
50
|
33
|
|
|
26
|
shift( @tokens ) if( @tokens && $tokens[0] eq '' ); |
|
2828
|
|
|
|
|
|
|
|
|
2829
|
9
|
|
|
|
|
11
|
my $node = $root; |
|
2830
|
|
|
|
|
|
|
|
|
2831
|
|
|
|
|
|
|
TOKEN: |
|
2832
|
9
|
|
|
|
|
14
|
for my $tok ( @tokens ) |
|
2833
|
|
|
|
|
|
|
{ |
|
2834
|
|
|
|
|
|
|
# rfc6901 JSON Pointer unescaping |
|
2835
|
17
|
|
|
|
|
18
|
$tok =~ s/~1/\//g; |
|
2836
|
17
|
|
|
|
|
22
|
$tok =~ s/~0/~/g; |
|
2837
|
|
|
|
|
|
|
|
|
2838
|
17
|
50
|
|
|
|
23
|
if( ref( $node ) eq 'HASH' ) |
|
|
|
0
|
|
|
|
|
|
|
2839
|
|
|
|
|
|
|
{ |
|
2840
|
|
|
|
|
|
|
# Draft 2020-12 alias: allow "definitions" to hit "$defs" if needed |
|
2841
|
17
|
100
|
66
|
|
|
55
|
if( $tok eq 'definitions' && |
|
|
|
|
100
|
|
|
|
|
|
2842
|
|
|
|
|
|
|
!exists( $node->{definitions} ) && |
|
2843
|
|
|
|
|
|
|
exists( $node->{'$defs'} ) ) |
|
2844
|
|
|
|
|
|
|
{ |
|
2845
|
8
|
|
|
|
|
12
|
$tok = '$defs'; |
|
2846
|
|
|
|
|
|
|
} |
|
2847
|
|
|
|
|
|
|
|
|
2848
|
17
|
100
|
|
|
|
24
|
unless( exists( $node->{ $tok } ) ) |
|
2849
|
|
|
|
|
|
|
{ |
|
2850
|
|
|
|
|
|
|
# Optional: help debug resolution problems |
|
2851
|
1
|
50
|
|
|
|
3
|
warn( "_jsv_resolve_internal_ref: token '$tok' not found in current hash for pointer '$ptr'\n" ) |
|
2852
|
|
|
|
|
|
|
if( $JSON::Schema::Validate::DEBUG ); |
|
2853
|
1
|
|
|
|
|
3
|
return; |
|
2854
|
|
|
|
|
|
|
} |
|
2855
|
16
|
|
|
|
|
22
|
$node = $node->{ $tok }; |
|
2856
|
|
|
|
|
|
|
} |
|
2857
|
|
|
|
|
|
|
elsif( ref( $node ) eq 'ARRAY' ) |
|
2858
|
|
|
|
|
|
|
{ |
|
2859
|
0
|
0
|
0
|
|
|
0
|
unless( $tok =~ /^\d+$/ && $tok < @$node ) |
|
2860
|
|
|
|
|
|
|
{ |
|
2861
|
0
|
0
|
|
|
|
0
|
warn( "_jsv_resolve_internal_ref: array index '$tok' out of range for pointer '$ptr'\n" ) |
|
2862
|
|
|
|
|
|
|
if( $JSON::Schema::Validate::DEBUG ); |
|
2863
|
0
|
|
|
|
|
0
|
return; |
|
2864
|
|
|
|
|
|
|
} |
|
2865
|
0
|
|
|
|
|
0
|
$node = $node->[ $tok ]; |
|
2866
|
|
|
|
|
|
|
} |
|
2867
|
|
|
|
|
|
|
else |
|
2868
|
|
|
|
|
|
|
{ |
|
2869
|
0
|
0
|
|
|
|
0
|
warn( "_jsv_resolve_internal_ref: reached non-container node while resolving '$ptr'\n" ) |
|
2870
|
|
|
|
|
|
|
if( $JSON::Schema::Validate::DEBUG ); |
|
2871
|
0
|
|
|
|
|
0
|
return; |
|
2872
|
|
|
|
|
|
|
} |
|
2873
|
|
|
|
|
|
|
} |
|
2874
|
|
|
|
|
|
|
|
|
2875
|
8
|
|
|
|
|
14
|
return( $node ); |
|
2876
|
|
|
|
|
|
|
} |
|
2877
|
|
|
|
|
|
|
|
|
2878
|
|
|
|
|
|
|
# Keyword groups |
|
2879
|
|
|
|
|
|
|
sub _k_array_all |
|
2880
|
|
|
|
|
|
|
{ |
|
2881
|
32
|
|
|
32
|
|
81
|
my( $ctx, $sp, $S, $A ) = @_; |
|
2882
|
|
|
|
|
|
|
|
|
2883
|
32
|
50
|
66
|
|
|
112
|
if( exists( $S->{minItems} ) && @$A < $S->{minItems} ) |
|
2884
|
|
|
|
|
|
|
{ |
|
2885
|
0
|
|
|
|
|
0
|
return( _err_res( $ctx, $sp, "array has fewer than minItems $S->{minItems}", 'minItems' ) ); |
|
2886
|
|
|
|
|
|
|
} |
|
2887
|
32
|
50
|
33
|
|
|
115
|
if( exists( $S->{maxItems} ) && @$A > $S->{maxItems} ) |
|
2888
|
|
|
|
|
|
|
{ |
|
2889
|
0
|
|
|
|
|
0
|
return( _err_res( $ctx, $sp, "array has more than maxItems $S->{maxItems}", 'maxItems' ) ); |
|
2890
|
|
|
|
|
|
|
} |
|
2891
|
|
|
|
|
|
|
|
|
2892
|
32
|
50
|
|
|
|
86
|
if( $S->{uniqueItems} ) |
|
2893
|
|
|
|
|
|
|
{ |
|
2894
|
0
|
|
|
|
|
0
|
my %seen; |
|
2895
|
0
|
|
|
|
|
0
|
for my $i ( 0 .. $#$A ) |
|
2896
|
|
|
|
|
|
|
{ |
|
2897
|
0
|
|
|
|
|
0
|
my $k = _canon( $A->[$i] ); |
|
2898
|
0
|
0
|
|
|
|
0
|
if( $seen{ $k }++ ) |
|
2899
|
|
|
|
|
|
|
{ |
|
2900
|
0
|
|
|
|
|
0
|
return( _err_res( $ctx, _join_ptr( $sp, $i ), "array items not unique", 'uniqueItems' ) ); |
|
2901
|
|
|
|
|
|
|
} |
|
2902
|
|
|
|
|
|
|
} |
|
2903
|
|
|
|
|
|
|
} |
|
2904
|
|
|
|
|
|
|
|
|
2905
|
32
|
|
|
|
|
49
|
my %items_ann; |
|
2906
|
|
|
|
|
|
|
|
|
2907
|
32
|
100
|
|
|
|
197
|
if( ref( $S->{prefixItems} ) eq 'ARRAY' ) |
|
|
|
100
|
|
|
|
|
|
|
2908
|
|
|
|
|
|
|
{ |
|
2909
|
5
|
|
|
|
|
9
|
my $tuple = $S->{prefixItems}; |
|
2910
|
5
|
|
|
|
|
15
|
for my $i ( 0 .. $#$A ) |
|
2911
|
|
|
|
|
|
|
{ |
|
2912
|
16
|
|
|
|
|
19
|
push( @{$ctx->{ptr_stack}}, _join_ptr( $sp, $i ) ); |
|
|
16
|
|
|
|
|
34
|
|
|
2913
|
|
|
|
|
|
|
|
|
2914
|
16
|
100
|
66
|
|
|
70
|
if( $i <= $#$tuple ) |
|
|
|
100
|
|
|
|
|
|
|
2915
|
|
|
|
|
|
|
{ |
|
2916
|
10
|
|
|
|
|
43
|
my $r = _v( $ctx, _join_ptr( $sp, "prefixItems/$i" ), $tuple->[$i], $A->[$i] ); |
|
2917
|
10
|
50
|
|
|
|
26
|
return( $r ) unless( $r->{ok} ); |
|
2918
|
10
|
|
|
|
|
51
|
$items_ann{ $i } = 1; |
|
2919
|
|
|
|
|
|
|
} |
|
2920
|
|
|
|
|
|
|
elsif( exists( $S->{items} ) && ref( $S->{items} ) eq 'HASH' ) |
|
2921
|
|
|
|
|
|
|
{ |
|
2922
|
5
|
|
|
|
|
10
|
my $r = _v( $ctx, _join_ptr( $sp, "items" ), $S->{items}, $A->[$i] ); |
|
2923
|
5
|
100
|
|
|
|
13
|
return( $r ) unless( $r->{ok} ); |
|
2924
|
4
|
|
|
|
|
14
|
$items_ann{ $i } = 1; |
|
2925
|
|
|
|
|
|
|
} |
|
2926
|
|
|
|
|
|
|
|
|
2927
|
15
|
|
|
|
|
16
|
pop( @{$ctx->{ptr_stack}} ); |
|
|
15
|
|
|
|
|
38
|
|
|
2928
|
|
|
|
|
|
|
} |
|
2929
|
|
|
|
|
|
|
} |
|
2930
|
|
|
|
|
|
|
elsif( ref( $S->{items} ) eq 'HASH' ) |
|
2931
|
|
|
|
|
|
|
{ |
|
2932
|
14
|
|
|
|
|
46
|
for my $i ( 0 .. $#$A ) |
|
2933
|
|
|
|
|
|
|
{ |
|
2934
|
27
|
|
|
|
|
33
|
push( @{$ctx->{ptr_stack}}, _join_ptr( $sp, $i ) ); |
|
|
27
|
|
|
|
|
60
|
|
|
2935
|
27
|
|
|
|
|
45
|
my $r = _v( $ctx, _join_ptr( $sp, "items" ), $S->{items}, $A->[$i] ); |
|
2936
|
27
|
100
|
|
|
|
87
|
return( $r ) unless( $r->{ok} ); |
|
2937
|
25
|
|
|
|
|
60
|
$items_ann{ $i } = 1; |
|
2938
|
25
|
|
|
|
|
29
|
pop( @{$ctx->{ptr_stack}} ); |
|
|
25
|
|
|
|
|
97
|
|
|
2939
|
|
|
|
|
|
|
} |
|
2940
|
|
|
|
|
|
|
} |
|
2941
|
|
|
|
|
|
|
|
|
2942
|
29
|
100
|
|
|
|
83
|
if( exists( $S->{contains} ) ) |
|
2943
|
|
|
|
|
|
|
{ |
|
2944
|
10
|
|
|
|
|
16
|
my $matches = 0; |
|
2945
|
|
|
|
|
|
|
|
|
2946
|
|
|
|
|
|
|
# Quiet sub-context to avoid emitting errors for non-matching items |
|
2947
|
10
|
|
|
|
|
35
|
for my $i ( 0 .. $#$A ) |
|
2948
|
|
|
|
|
|
|
{ |
|
2949
|
29
|
|
|
|
|
301
|
my %shadow = %$ctx; |
|
2950
|
29
|
|
|
|
|
99
|
my @errs; |
|
2951
|
29
|
|
|
|
|
48
|
$shadow{errors} = \@errs; |
|
2952
|
29
|
|
|
|
|
41
|
$shadow{error_count} = 0; |
|
2953
|
|
|
|
|
|
|
|
|
2954
|
29
|
|
|
|
|
81
|
my $tmp = _v( \%shadow, _join_ptr( $sp, "contains" ), $S->{contains}, $A->[$i] ); |
|
2955
|
29
|
100
|
|
|
|
248
|
$matches++ if( $tmp->{ok} ); |
|
2956
|
|
|
|
|
|
|
} |
|
2957
|
|
|
|
|
|
|
|
|
2958
|
10
|
50
|
|
|
|
31
|
my $minc = defined( $S->{minContains} ) ? $S->{minContains} : 1; |
|
2959
|
10
|
100
|
|
|
|
27
|
my $maxc = defined( $S->{maxContains} ) ? $S->{maxContains} : ( 2**31 - 1 ); |
|
2960
|
|
|
|
|
|
|
|
|
2961
|
10
|
100
|
|
|
|
41
|
return( _err_res( $ctx, $sp, "contains matched $matches < minContains $minc", 'minContains' ) ) if( $matches < $minc ); |
|
2962
|
7
|
100
|
|
|
|
31
|
return( _err_res( $ctx, $sp, "contains matched $matches > maxContains $maxc", 'maxContains' ) ) if( $matches > $maxc ); |
|
2963
|
|
|
|
|
|
|
} |
|
2964
|
|
|
|
|
|
|
|
|
2965
|
24
|
100
|
|
|
|
64
|
if( exists( $S->{unevaluatedItems} ) ) |
|
2966
|
|
|
|
|
|
|
{ |
|
2967
|
3
|
|
|
|
|
6
|
my @unknown = (); |
|
2968
|
3
|
|
|
|
|
10
|
for my $i ( 0 .. $#$A ) |
|
2969
|
|
|
|
|
|
|
{ |
|
2970
|
10
|
100
|
|
|
|
32
|
next if( $items_ann{ $i } ); |
|
2971
|
1
|
|
|
|
|
3
|
push( @unknown, $i ); |
|
2972
|
|
|
|
|
|
|
} |
|
2973
|
3
|
|
|
|
|
6
|
my $UE = $S->{unevaluatedItems}; |
|
2974
|
3
|
50
|
33
|
|
|
9
|
if( !_is_true( $UE ) && !_is_hash( $UE ) ) |
|
|
|
0
|
|
|
|
|
|
|
2975
|
|
|
|
|
|
|
{ |
|
2976
|
3
|
100
|
|
|
|
22
|
return( _err_res( $ctx, $sp, "unevaluatedItems not allowed at indices: " . join( ',', @unknown ), 'unevaluatedItems' ) ) if( @unknown ); |
|
2977
|
|
|
|
|
|
|
} |
|
2978
|
|
|
|
|
|
|
elsif( ref( $UE ) eq 'HASH' ) |
|
2979
|
|
|
|
|
|
|
{ |
|
2980
|
0
|
|
|
|
|
0
|
for my $i ( @unknown ) |
|
2981
|
|
|
|
|
|
|
{ |
|
2982
|
0
|
|
|
|
|
0
|
my $r = _v( $ctx, _join_ptr( $sp, "unevaluatedItems" ), $UE, $A->[$i] ); |
|
2983
|
0
|
0
|
|
|
|
0
|
return( $r ) unless( $r->{ok} ); |
|
2984
|
0
|
|
|
|
|
0
|
$items_ann{ $i } = 1; |
|
2985
|
|
|
|
|
|
|
} |
|
2986
|
|
|
|
|
|
|
} |
|
2987
|
|
|
|
|
|
|
} |
|
2988
|
|
|
|
|
|
|
|
|
2989
|
23
|
|
|
|
|
88
|
return( { ok => 1, props => {}, items => \%items_ann } ); |
|
2990
|
|
|
|
|
|
|
} |
|
2991
|
|
|
|
|
|
|
|
|
2992
|
|
|
|
|
|
|
sub _k_combinator |
|
2993
|
|
|
|
|
|
|
{ |
|
2994
|
25
|
|
|
25
|
|
44
|
my( $ctx, $sp, $S, $inst, $kw ) = @_; |
|
2995
|
|
|
|
|
|
|
|
|
2996
|
|
|
|
|
|
|
|
|
2997
|
|
|
|
|
|
|
# allOf: all subschemas must pass, we merge their annotations |
|
2998
|
25
|
100
|
|
|
|
45
|
if( $kw eq 'allOf' ) |
|
2999
|
|
|
|
|
|
|
{ |
|
3000
|
10
|
|
|
|
|
12
|
my %props; |
|
3001
|
|
|
|
|
|
|
my %items; |
|
3002
|
|
|
|
|
|
|
|
|
3003
|
10
|
|
|
|
|
8
|
for my $i ( 0 .. $#{$S->{allOf}} ) |
|
|
10
|
|
|
|
|
23
|
|
|
3004
|
|
|
|
|
|
|
{ |
|
3005
|
16
|
|
|
|
|
43
|
my $r = _v( $ctx, _join_ptr( $sp, "allOf/$i" ), $S->{allOf}->[ $i ], $inst ); |
|
3006
|
16
|
100
|
|
|
|
34
|
return( $r ) unless( $r->{ok} ); |
|
3007
|
|
|
|
|
|
|
|
|
3008
|
13
|
|
|
|
|
19
|
%props = ( %props, %{$r->{props}} ); |
|
|
13
|
|
|
|
|
18
|
|
|
3009
|
13
|
|
|
|
|
15
|
%items = ( %items, %{$r->{items}} ); |
|
|
13
|
|
|
|
|
26
|
|
|
3010
|
|
|
|
|
|
|
} |
|
3011
|
|
|
|
|
|
|
|
|
3012
|
7
|
|
|
|
|
22
|
return( { ok => 1, props => \%props, items => \%items } ); |
|
3013
|
|
|
|
|
|
|
} |
|
3014
|
|
|
|
|
|
|
|
|
3015
|
|
|
|
|
|
|
# anyOf: at least one subschema must pass. |
|
3016
|
|
|
|
|
|
|
# Errors from failing branches must NOT leak into the main context. |
|
3017
|
15
|
100
|
|
|
|
31
|
if( $kw eq 'anyOf' ) |
|
3018
|
|
|
|
|
|
|
{ |
|
3019
|
2
|
|
|
|
|
5
|
my @branch_errs; |
|
3020
|
|
|
|
|
|
|
|
|
3021
|
2
|
|
|
|
|
2
|
for my $i ( 0 .. $#{$S->{anyOf}} ) |
|
|
2
|
|
|
|
|
6
|
|
|
3022
|
|
|
|
|
|
|
{ |
|
3023
|
|
|
|
|
|
|
# Shadow context: isolate errors for this branch |
|
3024
|
3
|
|
|
|
|
20
|
my %shadow = %$ctx; |
|
3025
|
3
|
|
|
|
|
5
|
my @errs; |
|
3026
|
3
|
|
|
|
|
5
|
$shadow{errors} = \@errs; |
|
3027
|
3
|
|
|
|
|
6
|
$shadow{error_count} = 0; |
|
3028
|
|
|
|
|
|
|
|
|
3029
|
3
|
|
|
|
|
9
|
my $r = _v( \%shadow, _join_ptr( $sp, "anyOf/$i" ), $S->{anyOf}->[ $i ], $inst ); |
|
3030
|
|
|
|
|
|
|
|
|
3031
|
3
|
100
|
|
|
|
7
|
if( $r->{ok} ) |
|
3032
|
|
|
|
|
|
|
{ |
|
3033
|
|
|
|
|
|
|
# One branch passed: combinator satisfied. |
|
3034
|
|
|
|
|
|
|
# Ignore all other branch errors. |
|
3035
|
1
|
|
|
|
|
6
|
return( { ok => 1, props => {}, items => {} } ); |
|
3036
|
|
|
|
|
|
|
} |
|
3037
|
|
|
|
|
|
|
|
|
3038
|
2
|
|
|
|
|
9
|
push( @branch_errs, \@errs ); |
|
3039
|
|
|
|
|
|
|
} |
|
3040
|
|
|
|
|
|
|
|
|
3041
|
|
|
|
|
|
|
# No branch matched: merge collected branch errors into main context |
|
3042
|
1
|
|
|
|
|
2
|
for my $aref ( @branch_errs ) |
|
3043
|
|
|
|
|
|
|
{ |
|
3044
|
2
|
50
|
|
|
|
4
|
next unless( @$aref ); |
|
3045
|
2
|
|
|
|
|
3
|
push( @{$ctx->{errors}}, @$aref ); |
|
|
2
|
|
|
|
|
4
|
|
|
3046
|
2
|
|
|
|
|
2
|
$ctx->{error_count} += scalar( @$aref ); |
|
3047
|
|
|
|
|
|
|
} |
|
3048
|
|
|
|
|
|
|
|
|
3049
|
1
|
|
|
|
|
3
|
return( _err_res( $ctx, $sp, "instance does not satisfy anyOf", 'anyOf' ) ); |
|
3050
|
|
|
|
|
|
|
} |
|
3051
|
|
|
|
|
|
|
|
|
3052
|
|
|
|
|
|
|
# oneOf: exactly one subschema must pass. |
|
3053
|
|
|
|
|
|
|
# Again, do not leak errors from non-selected branches. |
|
3054
|
13
|
100
|
|
|
|
29
|
if( $kw eq 'oneOf' ) |
|
3055
|
|
|
|
|
|
|
{ |
|
3056
|
11
|
|
|
|
|
16
|
my @ok_results; |
|
3057
|
|
|
|
|
|
|
my @branch_errs; |
|
3058
|
|
|
|
|
|
|
|
|
3059
|
11
|
|
|
|
|
16
|
for my $i ( 0 .. $#{$S->{oneOf}} ) |
|
|
11
|
|
|
|
|
34
|
|
|
3060
|
|
|
|
|
|
|
{ |
|
3061
|
22
|
|
|
|
|
261
|
my %shadow = %$ctx; |
|
3062
|
22
|
|
|
|
|
55
|
my @errs; |
|
3063
|
22
|
|
|
|
|
33
|
$shadow{errors} = \@errs; |
|
3064
|
22
|
|
|
|
|
31
|
$shadow{error_count} = 0; |
|
3065
|
|
|
|
|
|
|
|
|
3066
|
22
|
|
|
|
|
60
|
my $r = _v( \%shadow, _join_ptr( $sp, "oneOf/$i" ), $S->{oneOf}->[$i], $inst ); |
|
3067
|
|
|
|
|
|
|
|
|
3068
|
22
|
100
|
|
|
|
47
|
if( $r->{ok} ) |
|
3069
|
|
|
|
|
|
|
{ |
|
3070
|
6
|
|
|
|
|
31
|
push( @ok_results, $r ); |
|
3071
|
|
|
|
|
|
|
} |
|
3072
|
|
|
|
|
|
|
else |
|
3073
|
|
|
|
|
|
|
{ |
|
3074
|
16
|
|
|
|
|
88
|
push( @branch_errs, \@errs ); |
|
3075
|
|
|
|
|
|
|
} |
|
3076
|
|
|
|
|
|
|
} |
|
3077
|
|
|
|
|
|
|
|
|
3078
|
11
|
100
|
|
|
|
28
|
if( @ok_results == 1 ) |
|
3079
|
|
|
|
|
|
|
{ |
|
3080
|
|
|
|
|
|
|
# Exactly one branch matched: combinator satisfied. |
|
3081
|
|
|
|
|
|
|
# Do NOT bubble up branch props/items through oneOf. |
|
3082
|
4
|
|
|
|
|
38
|
return( { ok => 1, props => {}, items => {} } ); |
|
3083
|
|
|
|
|
|
|
} |
|
3084
|
|
|
|
|
|
|
|
|
3085
|
|
|
|
|
|
|
# Zero or >1 matched -> failure; merge branch errors |
|
3086
|
7
|
|
|
|
|
11
|
for my $aref ( @branch_errs ) |
|
3087
|
|
|
|
|
|
|
{ |
|
3088
|
12
|
50
|
|
|
|
22
|
next unless( @$aref ); |
|
3089
|
12
|
|
|
|
|
12
|
push( @{$ctx->{errors}}, @$aref ); |
|
|
12
|
|
|
|
|
37
|
|
|
3090
|
12
|
|
|
|
|
50
|
$ctx->{error_count} += scalar( @$aref ); |
|
3091
|
|
|
|
|
|
|
} |
|
3092
|
|
|
|
|
|
|
|
|
3093
|
|
|
|
|
|
|
return( |
|
3094
|
7
|
|
|
|
|
26
|
_err_res( |
|
3095
|
|
|
|
|
|
|
$ctx, |
|
3096
|
|
|
|
|
|
|
$sp, |
|
3097
|
|
|
|
|
|
|
"instance satisfies " . scalar( @ok_results ) . " schemas in oneOf (expected exactly 1)", |
|
3098
|
|
|
|
|
|
|
'oneOf' |
|
3099
|
|
|
|
|
|
|
) |
|
3100
|
|
|
|
|
|
|
); |
|
3101
|
|
|
|
|
|
|
} |
|
3102
|
|
|
|
|
|
|
|
|
3103
|
|
|
|
|
|
|
# not: subschema must NOT validate. |
|
3104
|
|
|
|
|
|
|
# Any errors from validating the inner schema are irrelevant on success. |
|
3105
|
2
|
50
|
|
|
|
5
|
if( $kw eq 'not' ) |
|
3106
|
|
|
|
|
|
|
{ |
|
3107
|
2
|
|
|
|
|
18
|
my %shadow = %$ctx; |
|
3108
|
2
|
|
|
|
|
5
|
my @errs; |
|
3109
|
2
|
|
|
|
|
4
|
$shadow{errors} = \@errs; |
|
3110
|
2
|
|
|
|
|
3
|
$shadow{error_count} = 0; |
|
3111
|
|
|
|
|
|
|
|
|
3112
|
2
|
|
|
|
|
5
|
my $r = _v( \%shadow, _join_ptr( $sp, "not" ), $S->{not}, $inst ); |
|
3113
|
|
|
|
|
|
|
|
|
3114
|
|
|
|
|
|
|
# If inner schema passes, then "not" fails |
|
3115
|
|
|
|
|
|
|
return( _err_res( $ctx, $sp, "instance matches forbidden not-schema", 'not' ) ) |
|
3116
|
2
|
100
|
|
|
|
15
|
if( $r->{ok} ); |
|
3117
|
|
|
|
|
|
|
|
|
3118
|
|
|
|
|
|
|
# Otherwise, "not" is satisfied; ignore inner errors entirely |
|
3119
|
1
|
|
|
|
|
8
|
return( { ok => 1, props => {}, items => {} } ); |
|
3120
|
|
|
|
|
|
|
} |
|
3121
|
|
|
|
|
|
|
|
|
3122
|
|
|
|
|
|
|
# Unknown / unsupported combinator (defensive default) |
|
3123
|
0
|
|
|
|
|
0
|
return( { ok => 1, props => {}, items => {} } ); |
|
3124
|
|
|
|
|
|
|
} |
|
3125
|
|
|
|
|
|
|
|
|
3126
|
|
|
|
|
|
|
sub _k_const |
|
3127
|
|
|
|
|
|
|
{ |
|
3128
|
6
|
|
|
6
|
|
11
|
my( $ctx, $v, $const, $ptr ) = @_; |
|
3129
|
6
|
50
|
|
|
|
15
|
return(1) if( _json_equal( $v, $const ) ); |
|
3130
|
0
|
|
|
|
|
0
|
return( _err( $ctx, $ptr, "const mismatch", 'const' ) ); |
|
3131
|
|
|
|
|
|
|
} |
|
3132
|
|
|
|
|
|
|
|
|
3133
|
|
|
|
|
|
|
sub _k_enum |
|
3134
|
|
|
|
|
|
|
{ |
|
3135
|
8
|
|
|
8
|
|
21
|
my( $ctx, $v, $arr, $ptr ) = @_; |
|
3136
|
8
|
|
|
|
|
9
|
for my $e ( @$arr ) |
|
3137
|
|
|
|
|
|
|
{ |
|
3138
|
15
|
100
|
|
|
|
21
|
return(1) if( _json_equal( $v, $e ) ); |
|
3139
|
|
|
|
|
|
|
} |
|
3140
|
4
|
|
|
|
|
15
|
return( _err( $ctx, $ptr, "value not in enum", 'enum' ) ); |
|
3141
|
|
|
|
|
|
|
} |
|
3142
|
|
|
|
|
|
|
|
|
3143
|
|
|
|
|
|
|
sub _k_format |
|
3144
|
|
|
|
|
|
|
{ |
|
3145
|
55
|
|
|
55
|
|
129
|
my( $ctx, $s, $fmt, $ptr ) = @_; |
|
3146
|
55
|
|
|
|
|
166
|
my $cb = $ctx->{formats}->{ $fmt }; |
|
3147
|
55
|
100
|
|
|
|
172
|
return(1) unless( $cb ); |
|
3148
|
52
|
|
|
|
|
73
|
local $@; |
|
3149
|
52
|
100
|
|
|
|
82
|
my $ok = eval{ $cb->( $s ) ? 1 : 0 }; |
|
|
52
|
|
|
|
|
159
|
|
|
3150
|
52
|
100
|
|
|
|
1261
|
return( $ok ? 1 : _err( $ctx, $ptr, "string fails format '$fmt'", 'format' ) ); |
|
3151
|
|
|
|
|
|
|
} |
|
3152
|
|
|
|
|
|
|
|
|
3153
|
|
|
|
|
|
|
sub _k_if_then_else |
|
3154
|
|
|
|
|
|
|
{ |
|
3155
|
16
|
|
|
16
|
|
23
|
my( $ctx, $sp, $S, $inst ) = @_; |
|
3156
|
|
|
|
|
|
|
|
|
3157
|
|
|
|
|
|
|
# Evaluate "if" in a shadow context so its errors do NOT leak |
|
3158
|
16
|
|
|
|
|
185
|
my %shadow = %$ctx; |
|
3159
|
16
|
|
|
|
|
34
|
my @errs; |
|
3160
|
16
|
|
|
|
|
19
|
$shadow{errors} = \@errs; |
|
3161
|
16
|
|
|
|
|
20
|
$shadow{error_count} = 0; |
|
3162
|
|
|
|
|
|
|
|
|
3163
|
16
|
|
|
|
|
24
|
my $cond = _v( \%shadow, _join_ptr( $sp, 'if' ), $S->{if}, $inst ); |
|
3164
|
|
|
|
|
|
|
|
|
3165
|
16
|
100
|
|
|
|
27
|
if( $cond->{ok} ) |
|
3166
|
|
|
|
|
|
|
{ |
|
3167
|
6
|
50
|
|
|
|
14
|
_t( $ctx, $sp, 'if', undef, 'pass', 'then' ) if( $ctx->{trace_on} ); |
|
3168
|
|
|
|
|
|
|
return( { ok => 1, props => {}, items => {} } ) |
|
3169
|
6
|
50
|
|
|
|
11
|
unless( exists( $S->{then} ) ); |
|
3170
|
|
|
|
|
|
|
|
|
3171
|
|
|
|
|
|
|
# Apply "then" against the REAL context |
|
3172
|
6
|
|
|
|
|
11
|
return( _v( $ctx, _join_ptr( $sp, 'then' ), $S->{then}, $inst ) ); |
|
3173
|
|
|
|
|
|
|
} |
|
3174
|
|
|
|
|
|
|
else |
|
3175
|
|
|
|
|
|
|
{ |
|
3176
|
10
|
50
|
|
|
|
21
|
_t( $ctx, $sp, 'if', undef, 'pass', 'else' ) if( $ctx->{trace_on} ); |
|
3177
|
|
|
|
|
|
|
return( { ok => 1, props => {}, items => {} } ) |
|
3178
|
10
|
50
|
|
|
|
99
|
unless( exists( $S->{else} ) ); |
|
3179
|
|
|
|
|
|
|
|
|
3180
|
|
|
|
|
|
|
# Apply "else" against the REAL context |
|
3181
|
0
|
|
|
|
|
0
|
return( _v( $ctx, _join_ptr( $sp, 'else' ), $S->{else}, $inst ) ); |
|
3182
|
|
|
|
|
|
|
} |
|
3183
|
|
|
|
|
|
|
} |
|
3184
|
|
|
|
|
|
|
|
|
3185
|
|
|
|
|
|
|
sub _k_number |
|
3186
|
|
|
|
|
|
|
{ |
|
3187
|
88
|
|
|
88
|
|
262
|
my( $ctx, $v, $kw, $arg, $ptr ) = @_; |
|
3188
|
88
|
100
|
|
|
|
327
|
if( $kw eq 'multipleOf' ) |
|
|
|
100
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
3189
|
|
|
|
|
|
|
{ |
|
3190
|
|
|
|
|
|
|
# Guard per spec: multipleOf must be > 0 |
|
3191
|
13
|
100
|
66
|
|
|
77
|
if( !defined( $arg ) || $arg <= 0 ) |
|
3192
|
|
|
|
|
|
|
{ |
|
3193
|
1
|
50
|
|
|
|
5
|
_t( $ctx, $ptr, 'multipleOf', undef, 'fail', 'arg<=0' ) if( $ctx->{trace_on} ); |
|
3194
|
1
|
|
|
|
|
4
|
return( _err( $ctx, $ptr, "multipleOf must be > 0", 'multipleOf' ) ); |
|
3195
|
|
|
|
|
|
|
} |
|
3196
|
|
|
|
|
|
|
# Float-tolerant multiple check |
|
3197
|
|
|
|
|
|
|
# my $ok = abs( ( $v / $arg ) - int( $v / $arg + 1e-10 ) ) < 1e-9; |
|
3198
|
12
|
|
|
|
|
57
|
my $ok = abs( ( $v / $arg ) - int( $v / $arg + 0.0000000001 ) ) < 1e-9; |
|
3199
|
12
|
0
|
|
|
|
28
|
_t( $ctx, $ptr, 'multipleOf', undef, $ok ? 'pass' : 'fail', "$v mod $arg" ) if( $ctx->{trace_on} ); |
|
|
|
50
|
|
|
|
|
|
|
3200
|
12
|
100
|
|
|
|
62
|
return( $ok ? 1 : _err( $ctx, $ptr, "number not multipleOf $arg" ) ); |
|
3201
|
|
|
|
|
|
|
} |
|
3202
|
|
|
|
|
|
|
elsif( $kw eq 'minimum' ) |
|
3203
|
|
|
|
|
|
|
{ |
|
3204
|
62
|
50
|
|
|
|
177
|
_t( $ctx, $ptr, 'minimum', undef, $v >= $arg ? 'pass' : 'fail', "$v >= $arg" ) if( $ctx->{trace_on} ); |
|
|
|
100
|
|
|
|
|
|
|
3205
|
62
|
100
|
|
|
|
307
|
return( $v >= $arg ? 1 : _err( $ctx, $ptr, "number less than minimum $arg", 'minimum' ) ); |
|
3206
|
|
|
|
|
|
|
} |
|
3207
|
|
|
|
|
|
|
elsif( $kw eq 'maximum' ) |
|
3208
|
|
|
|
|
|
|
{ |
|
3209
|
6
|
0
|
|
|
|
19
|
_t( $ctx, $ptr, 'maximum', undef, $v <= $arg ? 'pass' : 'fail', "$v <= $arg" ) if( $ctx->{trace_on} ); |
|
|
|
50
|
|
|
|
|
|
|
3210
|
6
|
100
|
|
|
|
31
|
return( $v <= $arg ? 1 : _err( $ctx, $ptr, "number greater than maximum $arg", 'maximum' ) ); |
|
3211
|
|
|
|
|
|
|
} |
|
3212
|
|
|
|
|
|
|
elsif( $kw eq 'exclusiveMinimum' ) |
|
3213
|
|
|
|
|
|
|
{ |
|
3214
|
5
|
0
|
|
|
|
13
|
_t( $ctx, $ptr, 'exclusiveMinimum', undef, $v > $arg ? 'pass' : 'fail', "$v > $arg" ) if( $ctx->{trace_on} ); |
|
|
|
50
|
|
|
|
|
|
|
3215
|
5
|
100
|
|
|
|
42
|
return( $v > $arg ? 1 : _err( $ctx, $ptr, "number not greater than exclusiveMinimum $arg", 'exclusiveMinimum' ) ); |
|
3216
|
|
|
|
|
|
|
} |
|
3217
|
|
|
|
|
|
|
elsif( $kw eq 'exclusiveMaximum' ) |
|
3218
|
|
|
|
|
|
|
{ |
|
3219
|
2
|
0
|
|
|
|
6
|
_t( $ctx, $ptr, 'exclusiveMaximum', undef, $v < $arg ? 'pass' : 'fail', "$v < $arg" ) if( $ctx->{trace_on} ); |
|
|
|
50
|
|
|
|
|
|
|
3220
|
2
|
100
|
|
|
|
14
|
return( $v < $arg ? 1 : _err( $ctx, $ptr, "number not less than exclusiveMaximum $arg", 'exclusiveMaximum' ) ); |
|
3221
|
|
|
|
|
|
|
} |
|
3222
|
0
|
|
|
|
|
0
|
return(1); |
|
3223
|
|
|
|
|
|
|
} |
|
3224
|
|
|
|
|
|
|
|
|
3225
|
|
|
|
|
|
|
sub _k_object_all |
|
3226
|
|
|
|
|
|
|
{ |
|
3227
|
287
|
|
|
287
|
|
541
|
my( $ctx, $sp, $S, $H ) = @_; |
|
3228
|
|
|
|
|
|
|
|
|
3229
|
287
|
|
|
|
|
318
|
my $ok = 1; |
|
3230
|
|
|
|
|
|
|
|
|
3231
|
|
|
|
|
|
|
my $bail_if_max = sub |
|
3232
|
|
|
|
|
|
|
{ |
|
3233
|
136
|
100
|
66
|
136
|
|
848
|
return( $ctx->{max_errors} && $ctx->{error_count} >= $ctx->{max_errors} ) ? 1 : 0; |
|
3234
|
287
|
|
|
|
|
1414
|
}; |
|
3235
|
|
|
|
|
|
|
|
|
3236
|
287
|
50
|
33
|
|
|
885
|
if( exists( $S->{minProperties} ) && ( scalar( keys( %$H ) ) ) < $S->{minProperties} ) |
|
3237
|
|
|
|
|
|
|
{ |
|
3238
|
0
|
|
|
|
|
0
|
_err_res( $ctx, $sp, "object has fewer than minProperties $S->{minProperties}", 'minProperties' ); |
|
3239
|
0
|
|
|
|
|
0
|
$ok = 0; |
|
3240
|
0
|
0
|
|
|
|
0
|
return( { ok => 0, props => {}, items => {} } ) if( $bail_if_max->() ); |
|
3241
|
|
|
|
|
|
|
} |
|
3242
|
287
|
50
|
33
|
|
|
687
|
if( exists( $S->{maxProperties} ) && ( scalar( keys( %$H ) ) ) > $S->{maxProperties} ) |
|
3243
|
|
|
|
|
|
|
{ |
|
3244
|
0
|
|
|
|
|
0
|
_err_res( $ctx, $sp, "object has more than maxProperties $S->{maxProperties}", 'maxProperties' ); |
|
3245
|
0
|
|
|
|
|
0
|
$ok = 0; |
|
3246
|
0
|
0
|
|
|
|
0
|
return( { ok => 0, props => {}, items => {} } ) if( $bail_if_max->() ); |
|
3247
|
|
|
|
|
|
|
} |
|
3248
|
|
|
|
|
|
|
|
|
3249
|
|
|
|
|
|
|
# Merge required from: |
|
3250
|
|
|
|
|
|
|
# - top-level "required" (array only) |
|
3251
|
|
|
|
|
|
|
# - property-level { required => 1 } or { optional => 0 } |
|
3252
|
287
|
|
|
|
|
377
|
my %required; |
|
3253
|
|
|
|
|
|
|
|
|
3254
|
287
|
100
|
100
|
|
|
973
|
if( exists( $S->{required} ) && ref( $S->{required} ) eq 'ARRAY' ) |
|
3255
|
|
|
|
|
|
|
{ |
|
3256
|
162
|
|
|
|
|
193
|
$required{ $_ } = 1 for( @{ $S->{required} } ); |
|
|
162
|
|
|
|
|
625
|
|
|
3257
|
|
|
|
|
|
|
} |
|
3258
|
|
|
|
|
|
|
|
|
3259
|
287
|
100
|
|
|
|
855
|
if( my $P = $S->{properties} ) |
|
3260
|
|
|
|
|
|
|
{ |
|
3261
|
251
|
|
|
|
|
608
|
for my $k ( keys( %$P ) ) |
|
3262
|
|
|
|
|
|
|
{ |
|
3263
|
393
|
|
|
|
|
588
|
my $pd = $P->{ $k }; |
|
3264
|
393
|
50
|
|
|
|
720
|
next unless( ref( $pd ) eq 'HASH' ); |
|
3265
|
|
|
|
|
|
|
|
|
3266
|
393
|
100
|
|
|
|
717
|
if( exists( $pd->{required} ) ) |
|
3267
|
|
|
|
|
|
|
{ |
|
3268
|
50
|
100
|
|
|
|
101
|
$required{ $k } = $pd->{required} ? 1 : 0; |
|
3269
|
|
|
|
|
|
|
} |
|
3270
|
393
|
100
|
|
|
|
831
|
if( exists( $pd->{optional} ) ) |
|
3271
|
|
|
|
|
|
|
{ |
|
3272
|
4
|
100
|
|
|
|
17
|
$required{ $k } = $pd->{optional} ? 0 : 1; # optional => 0 means required |
|
3273
|
|
|
|
|
|
|
} |
|
3274
|
|
|
|
|
|
|
} |
|
3275
|
|
|
|
|
|
|
} |
|
3276
|
|
|
|
|
|
|
|
|
3277
|
287
|
|
|
|
|
701
|
for my $rq ( grep{ $required{ $_ } } keys( %required ) ) |
|
|
263
|
|
|
|
|
595
|
|
|
3278
|
|
|
|
|
|
|
{ |
|
3279
|
257
|
100
|
|
|
|
539
|
next if( exists( $H->{ $rq } ) ); |
|
3280
|
39
|
100
|
|
|
|
80
|
_t( $ctx,$sp, 'required', undef, 'fail', $rq ) if( $ctx->{trace_on} ); |
|
3281
|
|
|
|
|
|
|
|
|
3282
|
39
|
|
|
|
|
64
|
my @need = sort grep { $required{ $_ } } keys %required; |
|
|
54
|
|
|
|
|
138
|
|
|
3283
|
39
|
|
|
|
|
98
|
my @have = sort keys %$H; |
|
3284
|
|
|
|
|
|
|
|
|
3285
|
39
|
50
|
|
|
|
123
|
my $need_str = @need ? join( ', ', @need ) : '(none)'; |
|
3286
|
39
|
100
|
|
|
|
86
|
my $have_str = @have ? join( ', ', @have ) : '(none)'; |
|
3287
|
|
|
|
|
|
|
|
|
3288
|
39
|
|
|
|
|
120
|
my $msg = "required property '$rq' is missing " |
|
3289
|
|
|
|
|
|
|
. "(required: $need_str; present: $have_str)"; |
|
3290
|
|
|
|
|
|
|
|
|
3291
|
39
|
|
|
|
|
70
|
_err_res( |
|
3292
|
|
|
|
|
|
|
$ctx, |
|
3293
|
|
|
|
|
|
|
_join_ptr( $sp, $rq ), |
|
3294
|
|
|
|
|
|
|
$msg, |
|
3295
|
|
|
|
|
|
|
'required' |
|
3296
|
|
|
|
|
|
|
); |
|
3297
|
|
|
|
|
|
|
|
|
3298
|
39
|
|
|
|
|
84
|
$ok = 0; |
|
3299
|
39
|
100
|
|
|
|
60
|
return( { ok => 0, props => {}, items => {} } ) if( $bail_if_max->() ); |
|
3300
|
|
|
|
|
|
|
} |
|
3301
|
|
|
|
|
|
|
|
|
3302
|
286
|
100
|
66
|
|
|
699
|
if( exists( $S->{propertyNames} ) && ref( $S->{propertyNames} ) eq 'HASH' ) |
|
3303
|
|
|
|
|
|
|
{ |
|
3304
|
6
|
|
|
|
|
10
|
for my $k ( keys( %$H ) ) |
|
3305
|
|
|
|
|
|
|
{ |
|
3306
|
12
|
|
|
|
|
27
|
my $r = _v( $ctx, _join_ptr( $sp, "propertyNames" ), $S->{propertyNames}, $k ); |
|
3307
|
12
|
100
|
|
|
|
34
|
if( !$r->{ok} ) |
|
3308
|
|
|
|
|
|
|
{ |
|
3309
|
2
|
|
|
|
|
3
|
$ok = 0; |
|
3310
|
2
|
50
|
|
|
|
5
|
return( { ok => 0, props => {}, items => {} } ) if( $bail_if_max->() ); |
|
3311
|
|
|
|
|
|
|
} |
|
3312
|
|
|
|
|
|
|
} |
|
3313
|
|
|
|
|
|
|
} |
|
3314
|
|
|
|
|
|
|
|
|
3315
|
286
|
|
100
|
|
|
650
|
my $props = $S->{properties} || {}; |
|
3316
|
286
|
|
100
|
|
|
913
|
my $patprops = $S->{patternProperties} || {}; |
|
3317
|
286
|
|
|
|
|
491
|
my $addl_set = exists( $S->{additionalProperties} ); |
|
3318
|
286
|
100
|
|
|
|
932
|
my $addl = $addl_set ? $S->{additionalProperties} : JSON::true; |
|
3319
|
|
|
|
|
|
|
|
|
3320
|
286
|
|
|
|
|
554
|
my %ann; |
|
3321
|
|
|
|
|
|
|
|
|
3322
|
286
|
|
|
|
|
817
|
for my $k ( sort( keys( %$H ) ) ) |
|
3323
|
|
|
|
|
|
|
{ |
|
3324
|
443
|
|
|
|
|
638
|
my $v = $H->{ $k }; |
|
3325
|
443
|
|
|
|
|
459
|
my $matched = 0; |
|
3326
|
|
|
|
|
|
|
|
|
3327
|
443
|
|
|
|
|
775
|
my $child_path = _join_ptr( $sp, $k ); |
|
3328
|
443
|
|
|
|
|
561
|
push( @{$ctx->{ptr_stack}}, $child_path ); |
|
|
443
|
|
|
|
|
931
|
|
|
3329
|
|
|
|
|
|
|
|
|
3330
|
443
|
100
|
|
|
|
793
|
if( exists( $props->{ $k } ) ) |
|
3331
|
|
|
|
|
|
|
{ |
|
3332
|
288
|
|
|
|
|
791
|
my $r = _v( $ctx, _join_ptr( $sp, "properties/$k" ), $props->{ $k }, $v ); |
|
3333
|
288
|
100
|
|
|
|
720
|
if( !$r->{ok} ) |
|
3334
|
|
|
|
|
|
|
{ |
|
3335
|
74
|
|
|
|
|
150
|
$ok = 0; |
|
3336
|
74
|
|
|
|
|
112
|
pop( @{$ctx->{ptr_stack}} ); |
|
|
74
|
|
|
|
|
145
|
|
|
3337
|
74
|
100
|
|
|
|
294
|
return( { ok => 0, props => \%ann, items => {} } ) if( $bail_if_max->() ); |
|
3338
|
73
|
|
|
|
|
294
|
next; |
|
3339
|
|
|
|
|
|
|
} |
|
3340
|
214
|
|
|
|
|
424
|
$ann{ $k } = 1; |
|
3341
|
214
|
|
|
|
|
601
|
$matched = 1; |
|
3342
|
|
|
|
|
|
|
} |
|
3343
|
|
|
|
|
|
|
|
|
3344
|
369
|
100
|
|
|
|
575
|
unless( $matched ) |
|
3345
|
|
|
|
|
|
|
{ |
|
3346
|
155
|
|
|
|
|
167
|
local $@; |
|
3347
|
155
|
|
|
|
|
235
|
for my $re ( keys( %$patprops ) ) |
|
3348
|
|
|
|
|
|
|
{ |
|
3349
|
4
|
|
|
|
|
28
|
my $re_ok = eval{ $k =~ /$re/ }; |
|
|
4
|
|
|
|
|
47
|
|
|
3350
|
4
|
100
|
|
|
|
14
|
next unless( $re_ok ); |
|
3351
|
|
|
|
|
|
|
|
|
3352
|
2
|
|
|
|
|
7
|
my $r = _v( $ctx, _join_ptr( $sp, "patternProperties/$re" ), $patprops->{ $re }, $v ); |
|
3353
|
2
|
50
|
|
|
|
7
|
if( !$r->{ok} ) |
|
3354
|
|
|
|
|
|
|
{ |
|
3355
|
0
|
|
|
|
|
0
|
$ok = 0; |
|
3356
|
0
|
|
|
|
|
0
|
pop( @{$ctx->{ptr_stack}} ); |
|
|
0
|
|
|
|
|
0
|
|
|
3357
|
0
|
0
|
|
|
|
0
|
return( { ok => 0, props => \%ann, items => {} } ) if( $bail_if_max->() ); |
|
3358
|
0
|
|
|
|
|
0
|
next; |
|
3359
|
|
|
|
|
|
|
} |
|
3360
|
2
|
|
|
|
|
5
|
$ann{ $k } = 1; |
|
3361
|
2
|
|
|
|
|
9
|
$matched = 1; |
|
3362
|
|
|
|
|
|
|
} |
|
3363
|
|
|
|
|
|
|
} |
|
3364
|
|
|
|
|
|
|
|
|
3365
|
369
|
100
|
|
|
|
629
|
unless( $matched ) |
|
3366
|
|
|
|
|
|
|
{ |
|
3367
|
153
|
100
|
66
|
|
|
437
|
if( $addl_set && !_is_true( $addl ) && !_is_hash( $addl ) ) |
|
|
|
100
|
100
|
|
|
|
|
|
3368
|
|
|
|
|
|
|
{ |
|
3369
|
16
|
|
|
|
|
30
|
_err_res( $ctx, _join_ptr( $sp, $k ), "additionalProperties not allowed: '$k'", 'additionalProperties' ); |
|
3370
|
16
|
|
|
|
|
29
|
$ok = 0; |
|
3371
|
16
|
|
|
|
|
18
|
pop( @{$ctx->{ptr_stack}} ); |
|
|
16
|
|
|
|
|
66
|
|
|
3372
|
16
|
50
|
|
|
|
50
|
return( { ok => 0, props => \%ann, items => {} } ) if( $bail_if_max->() ); |
|
3373
|
16
|
|
|
|
|
35
|
next; |
|
3374
|
|
|
|
|
|
|
} |
|
3375
|
|
|
|
|
|
|
elsif( ref( $addl ) eq 'HASH' ) |
|
3376
|
|
|
|
|
|
|
{ |
|
3377
|
5
|
|
|
|
|
47
|
my $r = _v( $ctx, _join_ptr( $sp, "additionalProperties" ), $addl, $v ); |
|
3378
|
5
|
50
|
|
|
|
18
|
if( !$r->{ok} ) |
|
3379
|
|
|
|
|
|
|
{ |
|
3380
|
0
|
|
|
|
|
0
|
$ok = 0; |
|
3381
|
0
|
|
|
|
|
0
|
pop( @{$ctx->{ptr_stack}} ); |
|
|
0
|
|
|
|
|
0
|
|
|
3382
|
0
|
0
|
|
|
|
0
|
return( { ok => 0, props => \%ann, items => {} } ) if( $bail_if_max->() ); |
|
3383
|
0
|
|
|
|
|
0
|
next; |
|
3384
|
|
|
|
|
|
|
} |
|
3385
|
5
|
|
|
|
|
22
|
$ann{ $k } = 1; |
|
3386
|
|
|
|
|
|
|
} |
|
3387
|
|
|
|
|
|
|
} |
|
3388
|
|
|
|
|
|
|
|
|
3389
|
353
|
|
|
|
|
358
|
pop( @{$ctx->{ptr_stack}} ); |
|
|
353
|
|
|
|
|
793
|
|
|
3390
|
|
|
|
|
|
|
} |
|
3391
|
|
|
|
|
|
|
|
|
3392
|
285
|
100
|
|
|
|
895
|
if( my $depR = $S->{dependentRequired} ) |
|
3393
|
|
|
|
|
|
|
{ |
|
3394
|
8
|
|
|
|
|
17
|
for my $k ( keys( %$depR ) ) |
|
3395
|
|
|
|
|
|
|
{ |
|
3396
|
8
|
100
|
|
|
|
19
|
next unless( exists( $H->{ $k } ) ); |
|
3397
|
6
|
50
|
|
|
|
7
|
for my $need ( @{$depR->{ $k } || []} ) |
|
|
6
|
|
|
|
|
16
|
|
|
3398
|
|
|
|
|
|
|
{ |
|
3399
|
6
|
100
|
|
|
|
16
|
next if( exists( $H->{ $need } ) ); |
|
3400
|
2
|
|
|
|
|
6
|
_err_res( $ctx, _join_ptr( $sp, $need ), "dependentRequired: '$need' required when '$k' is present", 'dependentRequired' ); |
|
3401
|
2
|
|
|
|
|
6
|
$ok = 0; |
|
3402
|
2
|
50
|
|
|
|
7
|
return( { ok => 0, props => \%ann, items => {} } ) if( $bail_if_max->() ); |
|
3403
|
|
|
|
|
|
|
} |
|
3404
|
|
|
|
|
|
|
} |
|
3405
|
|
|
|
|
|
|
} |
|
3406
|
|
|
|
|
|
|
|
|
3407
|
285
|
100
|
|
|
|
622
|
if( my $depS = $S->{dependentSchemas} ) |
|
3408
|
|
|
|
|
|
|
{ |
|
3409
|
8
|
|
|
|
|
17
|
for my $k ( keys( %$depS ) ) |
|
3410
|
|
|
|
|
|
|
{ |
|
3411
|
8
|
100
|
|
|
|
45
|
next unless( exists( $H->{ $k } ) ); |
|
3412
|
5
|
|
|
|
|
16
|
my $r = _v( $ctx, _join_ptr( $sp, "dependentSchemas/$k" ), $depS->{ $k }, $H ); |
|
3413
|
5
|
100
|
|
|
|
21
|
if( !$r->{ok} ) |
|
3414
|
|
|
|
|
|
|
{ |
|
3415
|
2
|
|
|
|
|
5
|
$ok = 0; |
|
3416
|
2
|
50
|
|
|
|
6
|
return( { ok => 0, props => \%ann, items => {} } ) if( $bail_if_max->() ); |
|
3417
|
|
|
|
|
|
|
} |
|
3418
|
|
|
|
|
|
|
} |
|
3419
|
|
|
|
|
|
|
} |
|
3420
|
|
|
|
|
|
|
|
|
3421
|
285
|
100
|
|
|
|
610
|
if( exists( $S->{unevaluatedProperties} ) ) |
|
3422
|
|
|
|
|
|
|
{ |
|
3423
|
4
|
|
|
|
|
10
|
my @unknown = grep { !$ann{ $_ } } keys( %$H ); |
|
|
9
|
|
|
|
|
19
|
|
|
3424
|
4
|
|
|
|
|
5
|
my $UE = $S->{unevaluatedProperties}; |
|
3425
|
|
|
|
|
|
|
|
|
3426
|
4
|
50
|
33
|
|
|
9
|
if( !_is_true( $UE ) && !_is_hash( $UE ) ) |
|
|
|
0
|
|
|
|
|
|
|
3427
|
|
|
|
|
|
|
{ |
|
3428
|
4
|
100
|
|
|
|
10
|
if( @unknown ) |
|
3429
|
|
|
|
|
|
|
{ |
|
3430
|
1
|
|
|
|
|
24
|
_err_res( $ctx, $sp, "unevaluatedProperties not allowed: " . join( ',', @unknown ), 'unevaluatedProperties' ); |
|
3431
|
1
|
|
|
|
|
2
|
$ok = 0; |
|
3432
|
1
|
50
|
|
|
|
2
|
return( { ok => 0, props => \%ann, items => {} } ) if( $bail_if_max->() ); |
|
3433
|
|
|
|
|
|
|
} |
|
3434
|
|
|
|
|
|
|
} |
|
3435
|
|
|
|
|
|
|
elsif( ref( $UE ) eq 'HASH' ) |
|
3436
|
|
|
|
|
|
|
{ |
|
3437
|
0
|
|
|
|
|
0
|
for my $k ( @unknown ) |
|
3438
|
|
|
|
|
|
|
{ |
|
3439
|
0
|
|
|
|
|
0
|
my $r = _v( $ctx, _join_ptr( $sp, "unevaluatedProperties" ), $UE, $H->{ $k } ); |
|
3440
|
0
|
0
|
|
|
|
0
|
if( !$r->{ok} ) |
|
3441
|
|
|
|
|
|
|
{ |
|
3442
|
0
|
|
|
|
|
0
|
$ok = 0; |
|
3443
|
0
|
0
|
|
|
|
0
|
return( { ok => 0, props => \%ann, items => {} } ) if( $bail_if_max->() ); |
|
3444
|
0
|
|
|
|
|
0
|
next; |
|
3445
|
|
|
|
|
|
|
} |
|
3446
|
0
|
|
|
|
|
0
|
$ann{ $k } = 1; |
|
3447
|
|
|
|
|
|
|
} |
|
3448
|
|
|
|
|
|
|
} |
|
3449
|
|
|
|
|
|
|
} |
|
3450
|
|
|
|
|
|
|
|
|
3451
|
285
|
100
|
|
|
|
2348
|
return( { ok => ( $ok ? 1 : 0 ), props => \%ann, items => {} } ); |
|
3452
|
|
|
|
|
|
|
} |
|
3453
|
|
|
|
|
|
|
|
|
3454
|
|
|
|
|
|
|
sub _k_string |
|
3455
|
|
|
|
|
|
|
{ |
|
3456
|
50
|
|
|
50
|
|
114
|
my( $ctx, $s, $S, $ptr ) = @_; |
|
3457
|
50
|
|
|
|
|
148
|
my $len = _strlen( $s ); |
|
3458
|
|
|
|
|
|
|
|
|
3459
|
50
|
100
|
100
|
|
|
209
|
if( exists( $S->{minLength} ) && $len < $S->{minLength} ) |
|
3460
|
|
|
|
|
|
|
{ |
|
3461
|
7
|
100
|
|
|
|
31
|
_t( $ctx, $ptr, 'minLength', undef, 'fail', "len=$len < $S->{minLength}" ) if( $ctx->{trace_on} ); |
|
3462
|
7
|
|
|
|
|
49
|
return( _err( $ctx, $ptr, "string shorter than minLength $S->{minLength}", 'minLength' ) ); |
|
3463
|
|
|
|
|
|
|
} |
|
3464
|
43
|
50
|
66
|
|
|
173
|
_t( $ctx, $ptr, 'minLength', undef, 'pass', "len=$len >= $S->{minLength}" ) if( exists( $S->{minLength} ) && $ctx->{trace_on} ); |
|
3465
|
|
|
|
|
|
|
|
|
3466
|
43
|
100
|
100
|
|
|
156
|
if( exists( $S->{maxLength} ) && $len > $S->{maxLength} ) |
|
3467
|
|
|
|
|
|
|
{ |
|
3468
|
1
|
50
|
|
|
|
4
|
_t( $ctx, $ptr, 'maxLength', undef, 'fail', "len=$len > $S->{maxLength}" ) if( $ctx->{trace_on} ); |
|
3469
|
1
|
|
|
|
|
5
|
return( _err( $ctx, $ptr, "string longer than maxLength $S->{maxLength}", 'maxLength' ) ); |
|
3470
|
|
|
|
|
|
|
} |
|
3471
|
42
|
50
|
66
|
|
|
89
|
_t( $ctx, $ptr, 'maxLength', undef, 'pass', "len=$len <= $S->{maxLength}" ) if( exists( $S->{maxLength} ) && $ctx->{trace_on} ); |
|
3472
|
|
|
|
|
|
|
|
|
3473
|
42
|
100
|
|
|
|
104
|
if( exists( $S->{pattern} ) ) |
|
3474
|
|
|
|
|
|
|
{ |
|
3475
|
18
|
|
|
|
|
21
|
my $re = $S->{pattern}; |
|
3476
|
18
|
|
|
|
|
19
|
local $@; |
|
3477
|
18
|
|
|
|
|
18
|
my $ok = eval{ $s =~ /$re/ }; |
|
|
18
|
|
|
|
|
233
|
|
|
3478
|
18
|
0
|
|
|
|
38
|
_t( $ctx, $ptr, 'pattern', undef, $ok ? 'pass' : 'fail', "/$re/" ) if( $ctx->{trace_on} ); |
|
|
|
50
|
|
|
|
|
|
|
3479
|
18
|
100
|
|
|
|
45
|
return( _err( $ctx, $ptr, "string does not match pattern /$re/", 'pattern' ) ) unless( $ok ); |
|
3480
|
|
|
|
|
|
|
} |
|
3481
|
40
|
|
|
|
|
106
|
return(1); |
|
3482
|
|
|
|
|
|
|
} |
|
3483
|
|
|
|
|
|
|
|
|
3484
|
|
|
|
|
|
|
# Primitive keyword helpers |
|
3485
|
|
|
|
|
|
|
sub _k_type |
|
3486
|
|
|
|
|
|
|
{ |
|
3487
|
582
|
|
|
582
|
|
1358
|
my( $ctx, $v, $type_kw, $ptr ) = @_; |
|
3488
|
|
|
|
|
|
|
|
|
3489
|
582
|
100
|
|
|
|
1555
|
my @alts = ref( $type_kw ) eq 'ARRAY' ? @$type_kw : ( $type_kw ); |
|
3490
|
|
|
|
|
|
|
|
|
3491
|
|
|
|
|
|
|
# First, allow inline schemas in the union |
|
3492
|
582
|
|
|
|
|
799
|
my $i = 0; |
|
3493
|
582
|
|
|
|
|
886
|
for my $alt ( @alts ) |
|
3494
|
|
|
|
|
|
|
{ |
|
3495
|
642
|
100
|
|
|
|
1159
|
if( ref( $alt ) eq 'HASH' ) |
|
3496
|
|
|
|
|
|
|
{ |
|
3497
|
20
|
|
|
|
|
57
|
my $r = _v( $ctx, _join_ptr( $ptr, "type/$i" ), $alt, $v ); |
|
3498
|
20
|
100
|
|
|
|
116
|
return(1) if( $r->{ok} ); |
|
3499
|
|
|
|
|
|
|
} |
|
3500
|
639
|
|
|
|
|
914
|
$i++; |
|
3501
|
|
|
|
|
|
|
} |
|
3502
|
|
|
|
|
|
|
|
|
3503
|
|
|
|
|
|
|
# Then, try string type names |
|
3504
|
579
|
|
|
|
|
837
|
for my $t ( @alts ) |
|
3505
|
|
|
|
|
|
|
{ |
|
3506
|
610
|
100
|
|
|
|
1061
|
next if( ref( $t ) ); |
|
3507
|
595
|
100
|
|
|
|
1174
|
return(1) if( _match_type( $v, $t ) ); |
|
3508
|
|
|
|
|
|
|
} |
|
3509
|
|
|
|
|
|
|
|
|
3510
|
36
|
100
|
|
|
|
62
|
my $exp = join( '|', map{ ref( $_ ) ? 'schema' : $_ } @alts ); |
|
|
49
|
|
|
|
|
225
|
|
|
3511
|
36
|
|
|
|
|
152
|
return( _err( $ctx, $ptr, "type mismatch: expected $exp", 'type' ) ); |
|
3512
|
|
|
|
|
|
|
} |
|
3513
|
|
|
|
|
|
|
|
|
3514
|
|
|
|
|
|
|
sub _k_unique_keys |
|
3515
|
|
|
|
|
|
|
{ |
|
3516
|
17
|
|
|
17
|
|
29
|
my( $ctx, $sp, $uk_def, $array ) = @_; |
|
3517
|
17
|
50
|
33
|
|
|
53
|
unless( ref( $uk_def ) eq 'ARRAY' && @$uk_def ) |
|
3518
|
|
|
|
|
|
|
{ |
|
3519
|
0
|
|
|
|
|
0
|
return( { ok => 1, props => {}, items => {} } ); |
|
3520
|
|
|
|
|
|
|
} |
|
3521
|
|
|
|
|
|
|
|
|
3522
|
17
|
|
|
|
|
27
|
for my $key_set ( @$uk_def ) |
|
3523
|
|
|
|
|
|
|
{ |
|
3524
|
19
|
50
|
33
|
|
|
54
|
next unless( ref( $key_set ) eq 'ARRAY' && @$key_set ); |
|
3525
|
|
|
|
|
|
|
|
|
3526
|
19
|
|
|
|
|
25
|
my %seen; |
|
3527
|
19
|
|
|
|
|
48
|
for my $i ( 0 .. $#$array ) |
|
3528
|
|
|
|
|
|
|
{ |
|
3529
|
42
|
|
|
|
|
67
|
my $item = $array->[$i]; |
|
3530
|
42
|
100
|
|
|
|
71
|
next unless( ref( $item ) eq 'HASH' ); |
|
3531
|
|
|
|
|
|
|
|
|
3532
|
40
|
|
|
|
|
46
|
my @key_vals; |
|
3533
|
40
|
|
|
|
|
39
|
my $all_present = 1; |
|
3534
|
40
|
|
|
|
|
67
|
for my $key ( @$key_set ) |
|
3535
|
|
|
|
|
|
|
{ |
|
3536
|
47
|
100
|
|
|
|
96
|
if( exists( $item->{ $key } ) ) |
|
3537
|
|
|
|
|
|
|
{ |
|
3538
|
44
|
|
|
|
|
79
|
push( @key_vals, _canon( $item->{ $key } ) ); |
|
3539
|
|
|
|
|
|
|
} |
|
3540
|
|
|
|
|
|
|
else |
|
3541
|
|
|
|
|
|
|
{ |
|
3542
|
3
|
|
|
|
|
4
|
$all_present = 0; |
|
3543
|
3
|
|
|
|
|
6
|
last; |
|
3544
|
|
|
|
|
|
|
} |
|
3545
|
|
|
|
|
|
|
} |
|
3546
|
|
|
|
|
|
|
|
|
3547
|
|
|
|
|
|
|
# Skip items that do not have *all* keys in this key set |
|
3548
|
40
|
100
|
|
|
|
92
|
next unless( $all_present ); |
|
3549
|
|
|
|
|
|
|
|
|
3550
|
37
|
|
|
|
|
65
|
my $composite = join( "\0", @key_vals ); |
|
3551
|
37
|
100
|
|
|
|
78
|
if( exists( $seen{ $composite } ) ) |
|
3552
|
|
|
|
|
|
|
{ |
|
3553
|
8
|
|
|
|
|
14
|
my $prev_i = $seen{ $composite }; |
|
3554
|
8
|
|
|
|
|
17
|
my $keys = join( ', ', map { "'$_'" } @$key_set ); |
|
|
9
|
|
|
|
|
31
|
|
|
3555
|
8
|
|
|
|
|
13
|
push( @{$ctx->{ptr_stack}}, _join_ptr( $sp, $i ) ); |
|
|
8
|
|
|
|
|
24
|
|
|
3556
|
8
|
|
|
|
|
36
|
my $res = _err_res( |
|
3557
|
|
|
|
|
|
|
$ctx, |
|
3558
|
|
|
|
|
|
|
$sp, |
|
3559
|
|
|
|
|
|
|
"uniqueKeys violation: items[$prev_i] and items[$i] have identical values for key(s) [$keys]", |
|
3560
|
|
|
|
|
|
|
'uniqueKeys', |
|
3561
|
|
|
|
|
|
|
); |
|
3562
|
8
|
|
|
|
|
11
|
pop( @{$ctx->{ptr_stack}} ); |
|
|
8
|
|
|
|
|
19
|
|
|
3563
|
|
|
|
|
|
|
|
|
3564
|
8
|
|
|
|
|
36
|
return( $res ); |
|
3565
|
|
|
|
|
|
|
} |
|
3566
|
29
|
|
|
|
|
104
|
$seen{ $composite } = $i; |
|
3567
|
|
|
|
|
|
|
} |
|
3568
|
|
|
|
|
|
|
} |
|
3569
|
|
|
|
|
|
|
|
|
3570
|
9
|
|
|
|
|
53
|
return( { ok => 1, props => {}, items => {} } ); |
|
3571
|
|
|
|
|
|
|
} |
|
3572
|
|
|
|
|
|
|
|
|
3573
|
|
|
|
|
|
|
sub _match_type |
|
3574
|
|
|
|
|
|
|
{ |
|
3575
|
595
|
|
|
595
|
|
1052
|
my( $v, $t ) = @_; |
|
3576
|
|
|
|
|
|
|
|
|
3577
|
595
|
100
|
66
|
|
|
1347
|
return(1) if( $t eq 'null' && !defined( $v ) ); |
|
3578
|
|
|
|
|
|
|
|
|
3579
|
594
|
100
|
|
|
|
1118
|
if( $t eq 'boolean' ) |
|
3580
|
|
|
|
|
|
|
{ |
|
3581
|
16
|
100
|
|
|
|
37
|
return(0) if( !defined( $v ) ); |
|
3582
|
14
|
100
|
66
|
|
|
79
|
if( blessed( $v ) && ( ref( $v ) =~ /Boolean/ ) ) |
|
3583
|
|
|
|
|
|
|
{ |
|
3584
|
8
|
|
|
|
|
88
|
my $s = "$v"; |
|
3585
|
8
|
50
|
66
|
|
|
95
|
return( ( $s eq '0' || $s eq '1' ) ? 1 : 0 ); |
|
3586
|
|
|
|
|
|
|
} |
|
3587
|
6
|
100
|
|
|
|
62
|
return( ( $v =~ /\A(?:0|1|true|false)\z/i ) ? 1 : 0 ); |
|
3588
|
|
|
|
|
|
|
} |
|
3589
|
|
|
|
|
|
|
|
|
3590
|
578
|
100
|
|
|
|
1102
|
if( $t eq 'integer' ) |
|
3591
|
|
|
|
|
|
|
{ |
|
3592
|
113
|
100
|
|
|
|
292
|
return(0) unless( _is_number( $v ) ); |
|
3593
|
99
|
100
|
|
|
|
1187
|
return( ( $v =~ /\A-?(?:0|[1-9][0-9]*)\z/ ) ? 1 : 0 ); |
|
3594
|
|
|
|
|
|
|
} |
|
3595
|
|
|
|
|
|
|
|
|
3596
|
465
|
100
|
|
|
|
883
|
if( $t eq 'number' ) |
|
3597
|
|
|
|
|
|
|
{ |
|
3598
|
33
|
|
|
|
|
66
|
return( _is_number( $v ) ); |
|
3599
|
|
|
|
|
|
|
} |
|
3600
|
|
|
|
|
|
|
|
|
3601
|
432
|
100
|
|
|
|
832
|
if( $t eq 'string' ) |
|
3602
|
|
|
|
|
|
|
{ |
|
3603
|
|
|
|
|
|
|
# Must be a scalar, defined, and NOT a numeric SV under strict typing. |
|
3604
|
140
|
100
|
100
|
|
|
707
|
return( (!ref( $v ) && defined( $v ) && !_is_number( $v )) ? 1 : 0 ); |
|
3605
|
|
|
|
|
|
|
} |
|
3606
|
|
|
|
|
|
|
|
|
3607
|
292
|
100
|
100
|
|
|
898
|
return(1) if( $t eq 'array' && ref( $v ) eq 'ARRAY' ); |
|
3608
|
255
|
100
|
100
|
|
|
1739
|
return(1) if( $t eq 'object' && ref( $v ) eq 'HASH' ); |
|
3609
|
|
|
|
|
|
|
|
|
3610
|
19
|
|
|
|
|
33
|
return(0); |
|
3611
|
|
|
|
|
|
|
} |
|
3612
|
|
|
|
|
|
|
|
|
3613
|
|
|
|
|
|
|
sub _normalize_uri |
|
3614
|
|
|
|
|
|
|
{ |
|
3615
|
119
|
|
|
119
|
|
285
|
my( $u ) = @_; |
|
3616
|
119
|
50
|
33
|
|
|
604
|
return( '#' ) unless( defined( $u ) && length( $u ) ); |
|
3617
|
119
|
|
|
|
|
282
|
return( $u ); |
|
3618
|
|
|
|
|
|
|
} |
|
3619
|
|
|
|
|
|
|
|
|
3620
|
|
|
|
|
|
|
sub _parse_media_type |
|
3621
|
|
|
|
|
|
|
{ |
|
3622
|
7
|
|
|
7
|
|
10
|
my( $s ) = @_; |
|
3623
|
7
|
|
|
|
|
28
|
my( $type, @rest ) = split( /;/, "$s" ); |
|
3624
|
7
|
|
50
|
|
|
13
|
$type ||= ''; |
|
3625
|
7
|
|
|
|
|
12
|
$type =~ s/\s+//g; |
|
3626
|
7
|
|
|
|
|
17
|
my %p; |
|
3627
|
7
|
|
|
|
|
11
|
for my $kv ( @rest ) |
|
3628
|
|
|
|
|
|
|
{ |
|
3629
|
7
|
|
|
|
|
16
|
my( $k, $v ) = split( /=/, $kv, 2 ); |
|
3630
|
7
|
50
|
|
|
|
14
|
next unless( defined( $k ) ); |
|
3631
|
7
|
|
|
|
|
34
|
$k =~ s/\s+//g; |
|
3632
|
7
|
50
|
|
|
|
11
|
$v = '' unless( defined( $v ) ); |
|
3633
|
7
|
|
|
|
|
22
|
$v =~ s/^\s+|\s+$//g; |
|
3634
|
|
|
|
|
|
|
# Allow single or double quote, but be consistent |
|
3635
|
7
|
|
|
|
|
16
|
$v =~ s/^(?<quote>["'])(.*)\g{quote}$/$2/; |
|
3636
|
7
|
|
|
|
|
30
|
$p{ lc( $k ) } = $v; |
|
3637
|
|
|
|
|
|
|
} |
|
3638
|
7
|
|
|
|
|
18
|
return( lc( $type ), \%p ); |
|
3639
|
|
|
|
|
|
|
} |
|
3640
|
|
|
|
|
|
|
|
|
3641
|
|
|
|
|
|
|
sub _prune_array_with_schema |
|
3642
|
|
|
|
|
|
|
{ |
|
3643
|
1
|
|
|
1
|
|
3
|
my( $self, $schema, $data ) = @_; |
|
3644
|
|
|
|
|
|
|
|
|
3645
|
1
|
|
|
|
|
2
|
my @out; |
|
3646
|
|
|
|
|
|
|
|
|
3647
|
1
|
|
|
|
|
4
|
my $shape = |
|
3648
|
|
|
|
|
|
|
{ |
|
3649
|
|
|
|
|
|
|
prefix_items => [], # index => subschema |
|
3650
|
|
|
|
|
|
|
items => undef # subschema for additional items |
|
3651
|
|
|
|
|
|
|
}; |
|
3652
|
|
|
|
|
|
|
|
|
3653
|
1
|
|
|
|
|
6
|
$self->_extract_array_shape( $schema, $shape ); |
|
3654
|
|
|
|
|
|
|
|
|
3655
|
1
|
|
|
|
|
3
|
for my $i ( 0 .. $#$data ) |
|
3656
|
|
|
|
|
|
|
{ |
|
3657
|
3
|
|
|
|
|
6
|
my $item = $data->[ $i ]; |
|
3658
|
3
|
|
|
|
|
5
|
my $item_schema; |
|
3659
|
|
|
|
|
|
|
|
|
3660
|
3
|
100
|
|
|
|
12
|
if( defined( $shape->{prefix_items}->[ $i ] ) ) |
|
|
|
50
|
|
|
|
|
|
|
3661
|
|
|
|
|
|
|
{ |
|
3662
|
1
|
|
|
|
|
3
|
$item_schema = $shape->{prefix_items}->[ $i ]; |
|
3663
|
|
|
|
|
|
|
} |
|
3664
|
|
|
|
|
|
|
elsif( $shape->{items} ) |
|
3665
|
|
|
|
|
|
|
{ |
|
3666
|
2
|
|
|
|
|
3
|
$item_schema = $shape->{items}; |
|
3667
|
|
|
|
|
|
|
} |
|
3668
|
|
|
|
|
|
|
|
|
3669
|
3
|
100
|
66
|
|
|
13
|
if( $item_schema && ref( $item ) ) |
|
3670
|
|
|
|
|
|
|
{ |
|
3671
|
2
|
|
|
|
|
6
|
$out[ $i ] = $self->_prune_with_schema( $item_schema, $item ); |
|
3672
|
|
|
|
|
|
|
} |
|
3673
|
|
|
|
|
|
|
else |
|
3674
|
|
|
|
|
|
|
{ |
|
3675
|
|
|
|
|
|
|
# No structural knowledge or scalar item: keep as-is |
|
3676
|
1
|
|
|
|
|
3
|
$out[ $i ] = $item; |
|
3677
|
|
|
|
|
|
|
} |
|
3678
|
|
|
|
|
|
|
} |
|
3679
|
|
|
|
|
|
|
|
|
3680
|
1
|
|
|
|
|
8
|
return( \@out ); |
|
3681
|
|
|
|
|
|
|
} |
|
3682
|
|
|
|
|
|
|
|
|
3683
|
|
|
|
|
|
|
sub _prune_object_with_schema |
|
3684
|
|
|
|
|
|
|
{ |
|
3685
|
6
|
|
|
6
|
|
12
|
my( $self, $schema, $data ) = @_; |
|
3686
|
|
|
|
|
|
|
|
|
3687
|
|
|
|
|
|
|
# Collect effective object shape from this schema and any allOf mixins. |
|
3688
|
6
|
|
|
|
|
39
|
my $shape = |
|
3689
|
|
|
|
|
|
|
{ |
|
3690
|
|
|
|
|
|
|
props => {}, # property name => subschema |
|
3691
|
|
|
|
|
|
|
patterns => [], # [ regex, subschema ] ... |
|
3692
|
|
|
|
|
|
|
allow_ap => 1, # additionalProperties allowed? |
|
3693
|
|
|
|
|
|
|
ap_schema => undef # subschema for additionalProperties, if any |
|
3694
|
|
|
|
|
|
|
}; |
|
3695
|
|
|
|
|
|
|
|
|
3696
|
6
|
|
|
|
|
24
|
$self->_extract_object_shape( $schema, $shape ); |
|
3697
|
|
|
|
|
|
|
|
|
3698
|
6
|
|
|
|
|
10
|
my %clean; |
|
3699
|
|
|
|
|
|
|
|
|
3700
|
|
|
|
|
|
|
KEY: |
|
3701
|
6
|
|
|
|
|
17
|
foreach my $key ( keys( %$data ) ) |
|
3702
|
|
|
|
|
|
|
{ |
|
3703
|
16
|
|
|
|
|
27
|
my $val = $data->{ $key }; |
|
3704
|
|
|
|
|
|
|
|
|
3705
|
|
|
|
|
|
|
# 1) Direct properties |
|
3706
|
16
|
100
|
|
|
|
35
|
if( exists( $shape->{props}->{ $key } ) ) |
|
3707
|
|
|
|
|
|
|
{ |
|
3708
|
7
|
|
|
|
|
12
|
my $sub = $shape->{props}->{ $key }; |
|
3709
|
7
|
|
|
|
|
21
|
$clean{ $key } = $self->_prune_with_schema( $sub, $val ); |
|
3710
|
7
|
|
|
|
|
20
|
next KEY; |
|
3711
|
|
|
|
|
|
|
} |
|
3712
|
|
|
|
|
|
|
|
|
3713
|
|
|
|
|
|
|
# 2) patternProperties |
|
3714
|
9
|
|
|
|
|
15
|
foreach my $pair ( @{$shape->{patterns}} ) |
|
|
9
|
|
|
|
|
19
|
|
|
3715
|
|
|
|
|
|
|
{ |
|
3716
|
3
|
|
|
|
|
8
|
my( $re, $pschema ) = @$pair; |
|
3717
|
3
|
|
|
|
|
4
|
my $ok; |
|
3718
|
|
|
|
|
|
|
|
|
3719
|
|
|
|
|
|
|
{ |
|
3720
|
3
|
|
|
|
|
5
|
local $@; |
|
|
3
|
|
|
|
|
4
|
|
|
3721
|
3
|
100
|
|
|
|
6
|
$ok = eval{ $key =~ /$re/ ? 1 : 0; }; |
|
|
3
|
|
|
|
|
34
|
|
|
3722
|
|
|
|
|
|
|
} |
|
3723
|
|
|
|
|
|
|
|
|
3724
|
3
|
100
|
|
|
|
10
|
next unless( $ok ); |
|
3725
|
|
|
|
|
|
|
|
|
3726
|
2
|
|
|
|
|
6
|
$clean{ $key } = $self->_prune_with_schema( $pschema, $val ); |
|
3727
|
2
|
|
|
|
|
6
|
next KEY; |
|
3728
|
|
|
|
|
|
|
} |
|
3729
|
|
|
|
|
|
|
|
|
3730
|
|
|
|
|
|
|
# 3) additionalProperties |
|
3731
|
7
|
50
|
|
|
|
17
|
if( $shape->{allow_ap} ) |
|
3732
|
|
|
|
|
|
|
{ |
|
3733
|
0
|
0
|
0
|
|
|
0
|
if( $shape->{ap_schema} && ref( $val ) ) |
|
3734
|
|
|
|
|
|
|
{ |
|
3735
|
0
|
|
|
|
|
0
|
$clean{ $key } = $self->_prune_with_schema( $shape->{ap_schema}, $val ); |
|
3736
|
|
|
|
|
|
|
} |
|
3737
|
|
|
|
|
|
|
else |
|
3738
|
|
|
|
|
|
|
{ |
|
3739
|
|
|
|
|
|
|
# allowed, but no further structure known |
|
3740
|
0
|
|
|
|
|
0
|
$clean{ $key } = $val; |
|
3741
|
|
|
|
|
|
|
} |
|
3742
|
|
|
|
|
|
|
} |
|
3743
|
|
|
|
|
|
|
else |
|
3744
|
|
|
|
|
|
|
{ |
|
3745
|
|
|
|
|
|
|
# additionalProperties is false (or equivalent) => drop unknown key |
|
3746
|
7
|
|
|
|
|
14
|
next KEY; |
|
3747
|
|
|
|
|
|
|
} |
|
3748
|
|
|
|
|
|
|
} |
|
3749
|
|
|
|
|
|
|
|
|
3750
|
6
|
|
|
|
|
44
|
return( \%clean ); |
|
3751
|
|
|
|
|
|
|
} |
|
3752
|
|
|
|
|
|
|
|
|
3753
|
|
|
|
|
|
|
sub _prune_with_schema |
|
3754
|
|
|
|
|
|
|
{ |
|
3755
|
17
|
|
|
17
|
|
40
|
my( $self, $schema, $data ) = @_; |
|
3756
|
|
|
|
|
|
|
|
|
3757
|
|
|
|
|
|
|
# Boolean schemas and non-hash schemas: do not attempt pruning. |
|
3758
|
17
|
50
|
|
|
|
42
|
return( $data ) unless( ref( $schema ) eq 'HASH' ); |
|
3759
|
|
|
|
|
|
|
|
|
3760
|
|
|
|
|
|
|
# Only prune structured values; scalars we leave untouched. |
|
3761
|
17
|
100
|
|
|
|
43
|
if( ref( $data ) eq 'HASH' ) |
|
|
|
100
|
|
|
|
|
|
|
3762
|
|
|
|
|
|
|
{ |
|
3763
|
6
|
|
|
|
|
17
|
return( $self->_prune_object_with_schema( $schema, $data ) ); |
|
3764
|
|
|
|
|
|
|
} |
|
3765
|
|
|
|
|
|
|
elsif( ref( $data ) eq 'ARRAY' ) |
|
3766
|
|
|
|
|
|
|
{ |
|
3767
|
1
|
|
|
|
|
7
|
return( $self->_prune_array_with_schema( $schema, $data ) ); |
|
3768
|
|
|
|
|
|
|
} |
|
3769
|
|
|
|
|
|
|
|
|
3770
|
10
|
|
|
|
|
60
|
return( $data ); |
|
3771
|
|
|
|
|
|
|
} |
|
3772
|
|
|
|
|
|
|
|
|
3773
|
|
|
|
|
|
|
sub _ptr_of_node |
|
3774
|
|
|
|
|
|
|
{ |
|
3775
|
8
|
|
|
8
|
|
15
|
my( $root, $target ) = @_; |
|
3776
|
8
|
|
|
|
|
11
|
for my $p ( keys( %{$root->{anchors}} ) ) |
|
|
8
|
|
|
|
|
39
|
|
|
3777
|
|
|
|
|
|
|
{ |
|
3778
|
74
|
|
|
|
|
225
|
my $n = $root->{anchors}->{ $p }; |
|
3779
|
74
|
100
|
|
|
|
319
|
return( $p ) if( $n eq $target ); |
|
3780
|
|
|
|
|
|
|
} |
|
3781
|
0
|
|
|
|
|
0
|
return( '#' ); |
|
3782
|
|
|
|
|
|
|
} |
|
3783
|
|
|
|
|
|
|
|
|
3784
|
|
|
|
|
|
|
my %SCRIPT_LIKE = map{ $_ => 1 } qw( |
|
3785
|
|
|
|
|
|
|
Hiragana Katakana Han Hangul Latin Cyrillic Greek |
|
3786
|
|
|
|
|
|
|
Hebrew Thai Armenian Georgian |
|
3787
|
|
|
|
|
|
|
Arabic Devanagari Bengali Gurmukhi Gujarati Oriya |
|
3788
|
|
|
|
|
|
|
Tamil Telugu Kannada Malayalam Sinhala |
|
3789
|
|
|
|
|
|
|
Lao Tibetan Myanmar Khmer |
|
3790
|
|
|
|
|
|
|
); |
|
3791
|
|
|
|
|
|
|
sub _re_to_js |
|
3792
|
|
|
|
|
|
|
{ |
|
3793
|
0
|
|
|
0
|
|
0
|
my $re = shift( @_ ); |
|
3794
|
0
|
|
|
|
|
0
|
my %opt = @_; |
|
3795
|
|
|
|
|
|
|
|
|
3796
|
|
|
|
|
|
|
# style: 'literal' => for use in /.../u |
|
3797
|
|
|
|
|
|
|
# 'string' => for use in new RegExp("...", "u") |
|
3798
|
0
|
|
0
|
|
|
0
|
my $style = $opt{style} || 'string'; |
|
3799
|
|
|
|
|
|
|
|
|
3800
|
0
|
0
|
0
|
|
|
0
|
return if( !defined( $re ) || !length( $re ) ); |
|
3801
|
|
|
|
|
|
|
|
|
3802
|
|
|
|
|
|
|
# |
|
3803
|
|
|
|
|
|
|
# 1) Convert \x{...} (Perl) to \u.... or \u{....} (JS) |
|
3804
|
|
|
|
|
|
|
# - 1–4 hex digits -> \uHHHH |
|
3805
|
|
|
|
|
|
|
# - 5–6 hex digits -> \u{HHHHH} (requires /u in JS) |
|
3806
|
|
|
|
|
|
|
# |
|
3807
|
0
|
|
|
|
|
0
|
$re =~ s{ |
|
3808
|
|
|
|
|
|
|
\\x\{([0-9A-Fa-f]{1,6})\} |
|
3809
|
|
|
|
|
|
|
}{ |
|
3810
|
0
|
|
|
|
|
0
|
my $hex = uc( $1 ); |
|
3811
|
0
|
0
|
|
|
|
0
|
if( length( $hex ) <= 4 ) |
|
3812
|
|
|
|
|
|
|
{ |
|
3813
|
0
|
|
|
|
|
0
|
"\\u$hex"; |
|
3814
|
|
|
|
|
|
|
} |
|
3815
|
|
|
|
|
|
|
else |
|
3816
|
|
|
|
|
|
|
{ |
|
3817
|
0
|
|
|
|
|
0
|
"\\u\{$hex\}"; |
|
3818
|
|
|
|
|
|
|
} |
|
3819
|
|
|
|
|
|
|
}egx; |
|
3820
|
|
|
|
|
|
|
|
|
3821
|
|
|
|
|
|
|
# Script eXtended |
|
3822
|
|
|
|
|
|
|
# |
|
3823
|
|
|
|
|
|
|
# 2) Convert \p{Katakana} / \P{Katakana} |
|
3824
|
|
|
|
|
|
|
# to \p{scx=Katakana} / \P{scx=Katakana} |
|
3825
|
|
|
|
|
|
|
# (Script_Extensions so it covers half-width too) |
|
3826
|
|
|
|
|
|
|
# |
|
3827
|
0
|
|
|
|
|
0
|
$re =~ s[ |
|
3828
|
|
|
|
|
|
|
\\([pP])\{([^}]+)\} |
|
3829
|
|
|
|
|
|
|
][ |
|
3830
|
0
|
|
|
|
|
0
|
my( $p, $name ) = ( $1, $2 ); |
|
3831
|
|
|
|
|
|
|
|
|
3832
|
0
|
|
|
|
|
0
|
my $norm = $name; |
|
3833
|
0
|
|
|
|
|
0
|
$norm =~ s/^\s+|\s+$//g; |
|
3834
|
0
|
|
|
|
|
0
|
$norm = ucfirst( lc( $norm ) ); |
|
3835
|
|
|
|
|
|
|
|
|
3836
|
0
|
0
|
|
|
|
0
|
if( exists( $SCRIPT_LIKE{ $norm } ) ) |
|
3837
|
|
|
|
|
|
|
{ |
|
3838
|
0
|
|
|
|
|
0
|
"\\$p\{scx=$norm\}"; |
|
3839
|
|
|
|
|
|
|
} |
|
3840
|
|
|
|
|
|
|
else |
|
3841
|
|
|
|
|
|
|
{ |
|
3842
|
|
|
|
|
|
|
# Pass other properties through unchanged: |
|
3843
|
|
|
|
|
|
|
# \p{L}, \p{Letter}, \p{Nd}, etc. |
|
3844
|
0
|
|
|
|
|
0
|
"\\$p\{$name\}"; |
|
3845
|
|
|
|
|
|
|
} |
|
3846
|
|
|
|
|
|
|
]egx; |
|
3847
|
|
|
|
|
|
|
|
|
3848
|
|
|
|
|
|
|
# |
|
3849
|
|
|
|
|
|
|
# 3) Optionally escape backslashes for JS string literal |
|
3850
|
|
|
|
|
|
|
# |
|
3851
|
0
|
0
|
|
|
|
0
|
if( $style eq 'string' ) |
|
3852
|
|
|
|
|
|
|
{ |
|
3853
|
0
|
|
|
|
|
0
|
$re =~ s{\\}{\\\\}g; # existing |
|
3854
|
0
|
|
|
|
|
0
|
$re =~ s{"}{\\"}g; # escape " |
|
3855
|
0
|
|
|
|
|
0
|
$re =~ s{\n}{\\n}g; |
|
3856
|
0
|
|
|
|
|
0
|
$re =~ s{\r}{\\r}g; |
|
3857
|
0
|
|
|
|
|
0
|
$re =~ s{/}{\/}g; |
|
3858
|
|
|
|
|
|
|
} |
|
3859
|
|
|
|
|
|
|
|
|
3860
|
|
|
|
|
|
|
# For 'literal', we leave backslashes as-is, for use in /.../u |
|
3861
|
|
|
|
|
|
|
|
|
3862
|
0
|
|
|
|
|
0
|
return( $re ); |
|
3863
|
|
|
|
|
|
|
} |
|
3864
|
|
|
|
|
|
|
|
|
3865
|
|
|
|
|
|
|
sub _register_builtin_media_validators |
|
3866
|
|
|
|
|
|
|
{ |
|
3867
|
4
|
|
|
4
|
|
13
|
my( $self ) = @_; |
|
3868
|
|
|
|
|
|
|
|
|
3869
|
|
|
|
|
|
|
# Example: application/json |
|
3870
|
|
|
|
|
|
|
$self->register_media_validator( 'application/json' => sub |
|
3871
|
|
|
|
|
|
|
{ |
|
3872
|
2
|
|
|
2
|
|
5
|
my( $bytes, $params ) = @_; |
|
3873
|
2
|
|
|
|
|
3
|
local $@; |
|
3874
|
2
|
|
|
|
|
2
|
my $v = eval{ JSON->new->allow_nonref(1)->decode( $bytes ) }; |
|
|
2
|
|
|
|
|
31
|
|
|
3875
|
2
|
50
|
|
|
|
5
|
return( 0, 'invalid JSON', undef ) if( $@ ); |
|
3876
|
|
|
|
|
|
|
# JSON value is valid even if it’s 0, "", or false |
|
3877
|
2
|
|
|
|
|
6
|
return( 1, undef, $v ); |
|
3878
|
4
|
|
|
|
|
42
|
} ); |
|
3879
|
|
|
|
|
|
|
|
|
3880
|
4
|
|
|
|
|
7
|
return( $self ); |
|
3881
|
|
|
|
|
|
|
} |
|
3882
|
|
|
|
|
|
|
|
|
3883
|
|
|
|
|
|
|
sub _resolve_uri |
|
3884
|
|
|
|
|
|
|
{ |
|
3885
|
37
|
|
|
37
|
|
67
|
my( $base, $ref ) = @_; |
|
3886
|
37
|
100
|
33
|
|
|
414
|
return( $ref ) if( !defined( $base ) || $base eq '' || $ref =~ /^[A-Za-z][A-Za-z0-9+\-.]*:/ ); |
|
|
|
|
66
|
|
|
|
|
|
3887
|
18
|
100
|
|
|
|
57
|
if( $ref =~ /^\#/ ) |
|
3888
|
|
|
|
|
|
|
{ |
|
3889
|
17
|
|
|
|
|
33
|
( my $no_frag = $base ) =~ s/\#.*$//; |
|
3890
|
17
|
|
|
|
|
51
|
return( $no_frag . $ref ); |
|
3891
|
|
|
|
|
|
|
} |
|
3892
|
1
|
50
|
|
|
|
4
|
return( $base . $ref ) if( $base =~ /\/$/ ); |
|
3893
|
1
|
|
|
|
|
15
|
( my $dir = $base ) =~ s{[^/]*$}{}; |
|
3894
|
1
|
|
|
|
|
4
|
return( $dir . $ref ); |
|
3895
|
|
|
|
|
|
|
} |
|
3896
|
|
|
|
|
|
|
|
|
3897
|
|
|
|
|
|
|
sub _safe_invoke |
|
3898
|
|
|
|
|
|
|
{ |
|
3899
|
8
|
|
|
8
|
|
18
|
my( $cb, @args ) = @_; |
|
3900
|
8
|
|
|
|
|
7
|
local $@; |
|
3901
|
|
|
|
|
|
|
# Force list context to preserve (ok, msg, out) style returns |
|
3902
|
8
|
|
|
|
|
14
|
my @ret = eval{ $cb->( @args ) }; |
|
|
8
|
|
|
|
|
17
|
|
|
3903
|
8
|
50
|
0
|
|
|
70
|
return( 0, ( $@ || 'callback failed' ), undef ) if( $@ ); |
|
3904
|
|
|
|
|
|
|
|
|
3905
|
|
|
|
|
|
|
# If callback returns (ok, msg, out) or (ok, msg) |
|
3906
|
8
|
50
|
|
|
|
13
|
if( @ret >= 2 ) |
|
3907
|
|
|
|
|
|
|
{ |
|
3908
|
8
|
100
|
|
|
|
19
|
my( $ok, $msg, $out ) = ( $ret[0] ? 1 : 0, $ret[1], $ret[2] ); |
|
3909
|
8
|
|
|
|
|
45
|
return( $ok, $msg, $out ); |
|
3910
|
|
|
|
|
|
|
} |
|
3911
|
|
|
|
|
|
|
|
|
3912
|
|
|
|
|
|
|
# If callback returns a single value |
|
3913
|
0
|
0
|
|
|
|
0
|
if( @ret == 1 ) |
|
3914
|
|
|
|
|
|
|
{ |
|
3915
|
0
|
|
|
|
|
0
|
my $v = $ret[0]; |
|
3916
|
|
|
|
|
|
|
# Reference => treat as decoded structure (success) |
|
3917
|
0
|
0
|
|
|
|
0
|
return( 1, undef, $v ) if( ref( $v ) ); |
|
3918
|
|
|
|
|
|
|
# Defined scalar => truthiness decides; scalar can be treated as decoded bytes |
|
3919
|
0
|
0
|
|
|
|
0
|
return( $v ? 1 : 0, undef, ( defined( $v ) ? $v : undef ) ); |
|
|
|
0
|
|
|
|
|
|
|
3920
|
|
|
|
|
|
|
} |
|
3921
|
|
|
|
|
|
|
|
|
3922
|
|
|
|
|
|
|
# Empty list => treat as failure (safer default) |
|
3923
|
0
|
|
|
|
|
0
|
return( 0, 'callback returned no value', undef ); |
|
3924
|
|
|
|
|
|
|
} |
|
3925
|
|
|
|
|
|
|
|
|
3926
|
|
|
|
|
|
|
sub _strlen |
|
3927
|
|
|
|
|
|
|
{ |
|
3928
|
50
|
|
|
50
|
|
87
|
my( $s ) = @_; |
|
3929
|
50
|
50
|
|
|
|
559
|
$s = Encode::decode( 'UTF-8', "$s", Encode::FB_DEFAULT ) unless( Encode::is_utf8( $s ) ); |
|
3930
|
50
|
|
|
|
|
2280
|
my @cp = unpack( 'U*', $s ); |
|
3931
|
50
|
|
|
|
|
124
|
return( scalar( @cp ) ); |
|
3932
|
|
|
|
|
|
|
} |
|
3933
|
|
|
|
|
|
|
|
|
3934
|
|
|
|
|
|
|
# Strict base64: validates alphabet, padding, length, and round-trips |
|
3935
|
|
|
|
|
|
|
sub _strict_base64_decode |
|
3936
|
|
|
|
|
|
|
{ |
|
3937
|
6
|
|
|
6
|
|
12
|
my( $s ) = @_; |
|
3938
|
6
|
50
|
|
|
|
13
|
return unless( defined( $s ) ); |
|
3939
|
|
|
|
|
|
|
|
|
3940
|
|
|
|
|
|
|
# strip ASCII whitespace per RFC 4648 §3.3 (tests commonly include raw) |
|
3941
|
6
|
|
|
|
|
99
|
( my $norm = "$s" ) =~ s/\s+//g; |
|
3942
|
|
|
|
|
|
|
|
|
3943
|
|
|
|
|
|
|
# valid alphabet + padding only |
|
3944
|
6
|
100
|
|
|
|
72
|
return unless( $norm =~ /\A[A-Za-z0-9+\/]*={0,2}\z/ ); |
|
3945
|
|
|
|
|
|
|
|
|
3946
|
|
|
|
|
|
|
# length must be a multiple of 4 |
|
3947
|
3
|
50
|
|
|
|
8
|
return unless( ( length( $norm ) % 4 ) == 0 ); |
|
3948
|
|
|
|
|
|
|
|
|
3949
|
3
|
|
|
|
|
38
|
local $@; |
|
3950
|
3
|
50
|
|
|
|
4
|
return unless( eval{ require MIME::Base64; 1 } ); |
|
|
3
|
|
|
|
|
25
|
|
|
|
3
|
|
|
|
|
6
|
|
|
3951
|
|
|
|
|
|
|
|
|
3952
|
3
|
|
|
|
|
14
|
my $out = MIME::Base64::decode_base64( $norm ); |
|
3953
|
|
|
|
|
|
|
|
|
3954
|
|
|
|
|
|
|
# re-encode and compare to ensure no silent salvage |
|
3955
|
3
|
|
|
|
|
14
|
my $re = MIME::Base64::encode_base64( $out, '' ); |
|
3956
|
|
|
|
|
|
|
# RFC allows omitting trailing '=' if not needed; normalize both |
|
3957
|
3
|
|
|
|
|
7
|
$re =~ s/\s+//g; |
|
3958
|
3
|
|
|
|
|
4
|
$norm =~ s/\s+//g; |
|
3959
|
3
|
50
|
|
|
|
8
|
return unless( $re eq $norm ); |
|
3960
|
3
|
|
|
|
|
6
|
return( $out ); |
|
3961
|
|
|
|
|
|
|
} |
|
3962
|
|
|
|
|
|
|
|
|
3963
|
|
|
|
|
|
|
sub _t |
|
3964
|
|
|
|
|
|
|
{ |
|
3965
|
44
|
|
|
44
|
|
113
|
my( $ctx, $schema_ptr, $keyword, $inst_path, $outcome, $note ) = @_; |
|
3966
|
44
|
50
|
|
|
|
98
|
return unless( $ctx->{trace_on} ); |
|
3967
|
|
|
|
|
|
|
|
|
3968
|
44
|
50
|
33
|
|
|
102
|
if( $ctx->{trace_limit} && @{$ctx->{trace}} >= $ctx->{trace_limit} ) |
|
|
44
|
|
|
|
|
174
|
|
|
3969
|
|
|
|
|
|
|
{ |
|
3970
|
0
|
|
|
|
|
0
|
return; |
|
3971
|
|
|
|
|
|
|
} |
|
3972
|
44
|
100
|
|
|
|
106
|
if( $ctx->{trace_sample} ) |
|
3973
|
|
|
|
|
|
|
{ |
|
3974
|
8
|
50
|
|
|
|
74
|
return if( int( rand(100) ) >= $ctx->{trace_sample} ); |
|
3975
|
|
|
|
|
|
|
} |
|
3976
|
|
|
|
|
|
|
|
|
3977
|
36
|
|
|
|
|
266
|
push( @{$ctx->{trace}}, |
|
3978
|
|
|
|
|
|
|
{ |
|
3979
|
|
|
|
|
|
|
schema_ptr => $schema_ptr, |
|
3980
|
|
|
|
|
|
|
keyword => $keyword, |
|
3981
|
36
|
|
50
|
|
|
55
|
inst_path => ( $ctx->{ptr_stack}->[-1] // '#' ), |
|
3982
|
|
|
|
|
|
|
outcome => $outcome, # 'pass' | 'fail' |
|
3983
|
|
|
|
|
|
|
note => $note, # short string |
|
3984
|
|
|
|
|
|
|
}); |
|
3985
|
|
|
|
|
|
|
} |
|
3986
|
|
|
|
|
|
|
|
|
3987
|
|
|
|
|
|
|
# Validation core with annotation + recursion |
|
3988
|
|
|
|
|
|
|
# _v returns { ok => 0|1, props => {k=>1,...}, items => {i=>1,...} } |
|
3989
|
|
|
|
|
|
|
sub _v |
|
3990
|
|
|
|
|
|
|
{ |
|
3991
|
715
|
|
|
715
|
|
1502
|
my( $ctx, $schema_ptr, $schema, $inst ) = @_; |
|
3992
|
|
|
|
|
|
|
|
|
3993
|
|
|
|
|
|
|
# Recursion guard only for reference types |
|
3994
|
715
|
100
|
|
|
|
1421
|
if( ref( $inst ) ) |
|
3995
|
|
|
|
|
|
|
{ |
|
3996
|
364
|
|
|
|
|
877
|
my $inst_addr = _inst_addr( $inst, $ctx->{ptr_stack}->[-1] ); |
|
3997
|
364
|
|
|
|
|
665
|
my $vkey = "$schema_ptr|$inst_addr"; |
|
3998
|
364
|
50
|
|
|
|
1388
|
return( { ok => 1, props => {}, items => {} } ) if( $ctx->{visited}->{ $vkey }++ ); |
|
3999
|
|
|
|
|
|
|
} |
|
4000
|
|
|
|
|
|
|
|
|
4001
|
|
|
|
|
|
|
# Enter dynamicAnchor scope if present |
|
4002
|
715
|
|
|
|
|
975
|
my $frame_added = 0; |
|
4003
|
715
|
50
|
66
|
|
|
2936
|
if( ref( $schema ) eq 'HASH' && |
|
|
|
|
66
|
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
4004
|
|
|
|
|
|
|
exists( $schema->{'$dynamicAnchor'} ) && |
|
4005
|
|
|
|
|
|
|
defined( $schema->{'$dynamicAnchor'} ) && |
|
4006
|
|
|
|
|
|
|
$schema->{'$dynamicAnchor'} ne '' ) |
|
4007
|
|
|
|
|
|
|
{ |
|
4008
|
11
|
|
|
|
|
15
|
my %frame = %{$ctx->{dyn_stack}->[-1]}; # inherit |
|
|
11
|
|
|
|
|
60
|
|
|
4009
|
11
|
|
|
|
|
26
|
$frame{ $schema->{'$dynamicAnchor'} } = $schema; |
|
4010
|
11
|
|
|
|
|
17
|
push( @{$ctx->{dyn_stack}}, \%frame ); |
|
|
11
|
|
|
|
|
27
|
|
|
4011
|
11
|
|
|
|
|
25
|
$frame_added = 1; |
|
4012
|
|
|
|
|
|
|
} |
|
4013
|
|
|
|
|
|
|
|
|
4014
|
715
|
|
|
|
|
2386
|
my $res = _v_node( $ctx, $schema_ptr, $schema, $inst ); |
|
4015
|
|
|
|
|
|
|
|
|
4016
|
715
|
100
|
|
|
|
1325
|
if( $frame_added ) |
|
4017
|
|
|
|
|
|
|
{ |
|
4018
|
11
|
|
|
|
|
14
|
pop( @{$ctx->{dyn_stack}} ); |
|
|
11
|
|
|
|
|
50
|
|
|
4019
|
|
|
|
|
|
|
} |
|
4020
|
|
|
|
|
|
|
|
|
4021
|
715
|
|
|
|
|
1311
|
return( $res ); |
|
4022
|
|
|
|
|
|
|
} |
|
4023
|
|
|
|
|
|
|
|
|
4024
|
|
|
|
|
|
|
sub _v_node |
|
4025
|
|
|
|
|
|
|
{ |
|
4026
|
715
|
|
|
715
|
|
1270
|
my( $ctx, $schema_ptr, $schema, $inst ) = @_; |
|
4027
|
|
|
|
|
|
|
|
|
4028
|
|
|
|
|
|
|
# $ref / $dynamicRef first |
|
4029
|
715
|
100
|
66
|
|
|
2370
|
if( ref( $schema ) eq 'HASH' && |
|
4030
|
|
|
|
|
|
|
exists( $schema->{'$ref'} ) ) |
|
4031
|
|
|
|
|
|
|
{ |
|
4032
|
22
|
|
|
|
|
75
|
return( _apply_ref( $ctx, $schema_ptr, $schema->{'$ref'}, $inst ) ); |
|
4033
|
|
|
|
|
|
|
} |
|
4034
|
693
|
100
|
66
|
|
|
9330
|
if( ref( $schema ) eq 'HASH' && |
|
4035
|
|
|
|
|
|
|
exists( $schema->{'$dynamicRef'} ) ) |
|
4036
|
|
|
|
|
|
|
{ |
|
4037
|
5
|
|
|
|
|
41
|
return( _apply_dynamic_ref( $ctx, $schema_ptr, $schema->{'$dynamicRef'}, $inst ) ); |
|
4038
|
|
|
|
|
|
|
} |
|
4039
|
688
|
100
|
66
|
|
|
2457
|
if( ref( $schema ) eq 'HASH' && |
|
|
|
|
66
|
|
|
|
|
|
4040
|
|
|
|
|
|
|
exists( $schema->{'$comment'} ) && |
|
4041
|
|
|
|
|
|
|
defined( $schema->{'$comment'} ) ) |
|
4042
|
|
|
|
|
|
|
{ |
|
4043
|
9
|
|
|
|
|
17
|
my $c = $schema->{'$comment'}; |
|
4044
|
9
|
100
|
|
|
|
27
|
if( my $cb = $ctx->{comment_handler} ) |
|
4045
|
|
|
|
|
|
|
{ |
|
4046
|
4
|
|
|
|
|
8
|
local $@; |
|
4047
|
4
|
|
|
|
|
10
|
eval{ $cb->( $schema_ptr, "$c" ) }; |
|
|
4
|
|
|
|
|
21
|
|
|
4048
|
|
|
|
|
|
|
# ignore callback errors to keep validation resilient |
|
4049
|
|
|
|
|
|
|
} |
|
4050
|
9
|
50
|
|
|
|
70
|
_t( $ctx, $schema_ptr, '$comment', undef, 'visit', "$c" ) if( $ctx->{trace_on} ); |
|
4051
|
|
|
|
|
|
|
} |
|
4052
|
|
|
|
|
|
|
|
|
4053
|
688
|
100
|
|
|
|
1458
|
_t( $ctx, $schema_ptr, 'node', undef, 'visit' ) if( $ctx->{trace_on} ); |
|
4054
|
|
|
|
|
|
|
|
|
4055
|
|
|
|
|
|
|
# Use compiled validator if enabled |
|
4056
|
688
|
100
|
|
|
|
2144
|
if( $ctx->{compile_on} ) |
|
4057
|
|
|
|
|
|
|
{ |
|
4058
|
35
|
|
|
|
|
73
|
my $fn = $ctx->{root}->{fn_index}->{ $schema_ptr }; |
|
4059
|
35
|
100
|
|
|
|
68
|
unless( $fn ) |
|
4060
|
|
|
|
|
|
|
{ |
|
4061
|
23
|
|
|
|
|
63
|
$fn = _compile_node( $ctx->{root}, $schema_ptr, $schema ); |
|
4062
|
23
|
|
|
|
|
97
|
$ctx->{root}->{fn_index}->{ $schema_ptr } = $fn; |
|
4063
|
|
|
|
|
|
|
} |
|
4064
|
35
|
|
|
|
|
95
|
return( $fn->( $ctx, $inst ) ); |
|
4065
|
|
|
|
|
|
|
} |
|
4066
|
|
|
|
|
|
|
|
|
4067
|
653
|
50
|
|
|
|
1337
|
return( { ok => 1, props => {}, items => {} } ) unless( ref( $schema ) eq 'HASH' ); |
|
4068
|
|
|
|
|
|
|
|
|
4069
|
653
|
|
|
|
|
971
|
my $ptr = $schema_ptr; |
|
4070
|
|
|
|
|
|
|
|
|
4071
|
|
|
|
|
|
|
# Types / const / enum |
|
4072
|
653
|
100
|
|
|
|
1312
|
if( exists( $schema->{type} ) ) |
|
4073
|
|
|
|
|
|
|
{ |
|
4074
|
547
|
100
|
|
|
|
1418
|
_k_type( $ctx, $inst, $schema->{type}, $ptr ) or return( _fail() ); |
|
4075
|
|
|
|
|
|
|
} |
|
4076
|
617
|
100
|
|
|
|
1392
|
if( exists( $schema->{const} ) ) |
|
4077
|
|
|
|
|
|
|
{ |
|
4078
|
6
|
50
|
|
|
|
15
|
_k_const( $ctx, $inst, $schema->{const}, $ptr ) or return( _fail() ); |
|
4079
|
|
|
|
|
|
|
} |
|
4080
|
617
|
100
|
|
|
|
1117
|
if( exists( $schema->{enum} ) ) |
|
4081
|
|
|
|
|
|
|
{ |
|
4082
|
8
|
100
|
|
|
|
18
|
_k_enum( $ctx, $inst, $schema->{enum}, $ptr ) or return( _fail() ); |
|
4083
|
|
|
|
|
|
|
} |
|
4084
|
613
|
50
|
|
|
|
1139
|
_t( $ctx, $schema_ptr, 'type/const/enum', undef, 'pass', '' ) if( $ctx->{trace_on} ); |
|
4085
|
|
|
|
|
|
|
|
|
4086
|
613
|
50
|
100
|
|
|
1322
|
if( $ctx->{unique_keys} && |
|
|
|
|
66
|
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
4087
|
|
|
|
|
|
|
exists( $schema->{uniqueKeys} ) && |
|
4088
|
|
|
|
|
|
|
ref( $schema->{uniqueKeys} ) eq 'ARRAY' && |
|
4089
|
|
|
|
|
|
|
ref( $inst ) eq 'ARRAY' ) |
|
4090
|
|
|
|
|
|
|
{ |
|
4091
|
17
|
|
|
|
|
32
|
my $r = _k_unique_keys( $ctx, $ptr, $schema->{uniqueKeys}, $inst ); |
|
4092
|
17
|
100
|
|
|
|
70
|
return( $r ) unless( $r->{ok} ); |
|
4093
|
|
|
|
|
|
|
} |
|
4094
|
|
|
|
|
|
|
|
|
4095
|
|
|
|
|
|
|
# Numbers |
|
4096
|
605
|
100
|
|
|
|
1173
|
if( _is_number( $inst ) ) |
|
4097
|
|
|
|
|
|
|
{ |
|
4098
|
139
|
|
|
|
|
352
|
for my $k ( qw( multipleOf minimum maximum exclusiveMinimum exclusiveMaximum ) ) |
|
4099
|
|
|
|
|
|
|
{ |
|
4100
|
631
|
100
|
|
|
|
1252
|
next unless( exists( $schema->{ $k } ) ); |
|
4101
|
86
|
100
|
|
|
|
251
|
_k_number( $ctx, $inst, $k, $schema->{ $k }, $ptr ) or return( _fail() ); |
|
4102
|
|
|
|
|
|
|
} |
|
4103
|
|
|
|
|
|
|
} |
|
4104
|
|
|
|
|
|
|
|
|
4105
|
|
|
|
|
|
|
# Strings |
|
4106
|
583
|
100
|
100
|
|
|
1710
|
if( !ref( $inst ) && defined( $inst ) ) |
|
4107
|
|
|
|
|
|
|
{ |
|
4108
|
265
|
100
|
66
|
|
|
2575
|
if( exists( $schema->{minLength} ) || exists( $schema->{maxLength} ) || exists( $schema->{pattern} ) ) |
|
|
|
|
66
|
|
|
|
|
|
4109
|
|
|
|
|
|
|
{ |
|
4110
|
48
|
100
|
|
|
|
107
|
_k_string( $ctx, $inst, $schema, $ptr ) or return( _fail() ); |
|
4111
|
|
|
|
|
|
|
} |
|
4112
|
256
|
100
|
|
|
|
545
|
if( exists( $schema->{format} ) ) |
|
4113
|
|
|
|
|
|
|
{ |
|
4114
|
53
|
100
|
|
|
|
169
|
_k_format( $ctx, $inst, $schema->{format}, $ptr ) or return( _fail() ); |
|
4115
|
|
|
|
|
|
|
} |
|
4116
|
|
|
|
|
|
|
|
|
4117
|
|
|
|
|
|
|
# contentEncoding / contentMediaType / contentSchema |
|
4118
|
239
|
50
|
66
|
|
|
1323
|
if( exists( $schema->{contentEncoding} ) || |
|
|
|
|
33
|
|
|
|
|
|
4119
|
|
|
|
|
|
|
exists( $schema->{contentMediaType} ) || |
|
4120
|
|
|
|
|
|
|
exists( $schema->{contentSchema} ) ) |
|
4121
|
|
|
|
|
|
|
{ |
|
4122
|
9
|
100
|
|
|
|
18
|
my $assert = $ctx->{content_assert} ? 1 : 0; |
|
4123
|
9
|
|
|
|
|
15
|
my $bytes = "$inst"; |
|
4124
|
9
|
|
|
|
|
13
|
my $decoded_ref; |
|
4125
|
|
|
|
|
|
|
|
|
4126
|
9
|
50
|
|
|
|
15
|
if( exists( $schema->{contentEncoding} ) ) |
|
4127
|
|
|
|
|
|
|
{ |
|
4128
|
9
|
|
|
|
|
32
|
my $dec = _content_decode( $ctx, $schema->{contentEncoding}, $bytes ); |
|
4129
|
9
|
100
|
|
|
|
23
|
if( !defined( $dec ) ) |
|
4130
|
|
|
|
|
|
|
{ |
|
4131
|
3
|
100
|
|
|
|
19
|
return( _err_res( $ctx, $ptr, "contentEncoding '$schema->{contentEncoding}' decode failed", 'contentEncoding' ) ) if( $assert ); |
|
4132
|
|
|
|
|
|
|
} |
|
4133
|
|
|
|
|
|
|
else |
|
4134
|
|
|
|
|
|
|
{ |
|
4135
|
6
|
|
|
|
|
8
|
$bytes = $dec; |
|
4136
|
|
|
|
|
|
|
} |
|
4137
|
|
|
|
|
|
|
} |
|
4138
|
|
|
|
|
|
|
|
|
4139
|
7
|
50
|
|
|
|
14
|
if( exists( $schema->{contentMediaType} ) ) |
|
4140
|
|
|
|
|
|
|
{ |
|
4141
|
7
|
|
|
|
|
21
|
my( $mt, $params ) = _parse_media_type( $schema->{contentMediaType} ); |
|
4142
|
7
|
100
|
|
|
|
22
|
if( my $cb = $ctx->{media_validators}->{ $mt } ) |
|
4143
|
|
|
|
|
|
|
{ |
|
4144
|
5
|
|
|
|
|
11
|
my( $ok, $msg, $maybe_decoded ) = _safe_invoke( $cb, $bytes, $params ); |
|
4145
|
5
|
100
|
|
|
|
13
|
if( !$ok ) |
|
4146
|
|
|
|
|
|
|
{ |
|
4147
|
2
|
100
|
33
|
|
|
13
|
return( _err_res( $ctx, $ptr, ( $msg || "contentMediaType '$mt' validation failed", 'contentMediaType' ) ) ) if( $assert ); |
|
4148
|
|
|
|
|
|
|
} |
|
4149
|
4
|
100
|
|
|
|
12
|
$decoded_ref = $maybe_decoded if( ref( $maybe_decoded ) ); |
|
4150
|
4
|
100
|
100
|
|
|
22
|
$bytes = $maybe_decoded if( defined( $maybe_decoded ) && !ref( $maybe_decoded ) ); |
|
4151
|
|
|
|
|
|
|
} |
|
4152
|
|
|
|
|
|
|
else |
|
4153
|
|
|
|
|
|
|
{ |
|
4154
|
2
|
50
|
0
|
|
|
7
|
if( $mt =~ m{\Atext/} && ( ( $params->{charset} || '' ) =~ /\Autf-?8\z/i ) ) |
|
|
|
|
33
|
|
|
|
|
|
4155
|
|
|
|
|
|
|
{ |
|
4156
|
0
|
|
|
|
|
0
|
local $@; |
|
4157
|
|
|
|
|
|
|
my $ok = eval |
|
4158
|
0
|
0
|
|
|
|
0
|
{ |
|
4159
|
0
|
|
|
|
|
0
|
require Encode; |
|
4160
|
0
|
|
|
|
|
0
|
Encode::decode( 'UTF-8', $bytes, Encode::FB_CROAK ); |
|
4161
|
0
|
|
|
|
|
0
|
1; |
|
4162
|
|
|
|
|
|
|
} ? 1 : 0; |
|
4163
|
0
|
0
|
0
|
|
|
0
|
if( !$ok && $assert ) |
|
4164
|
|
|
|
|
|
|
{ |
|
4165
|
0
|
|
|
|
|
0
|
return( _err_res( $ctx, $ptr, "contentMediaType '$mt' invalid UTF-8", 'contentMediaType' ) ); |
|
4166
|
|
|
|
|
|
|
} |
|
4167
|
|
|
|
|
|
|
} |
|
4168
|
|
|
|
|
|
|
} |
|
4169
|
|
|
|
|
|
|
} |
|
4170
|
|
|
|
|
|
|
|
|
4171
|
6
|
100
|
|
|
|
15
|
if( exists( $schema->{contentSchema} ) ) |
|
4172
|
|
|
|
|
|
|
{ |
|
4173
|
4
|
|
|
|
|
8
|
my $val; |
|
4174
|
4
|
100
|
|
|
|
24
|
if( ref( $decoded_ref ) ) |
|
4175
|
|
|
|
|
|
|
{ |
|
4176
|
2
|
|
|
|
|
3
|
$val = $decoded_ref; # already decoded by media validator |
|
4177
|
|
|
|
|
|
|
} |
|
4178
|
|
|
|
|
|
|
else |
|
4179
|
|
|
|
|
|
|
{ |
|
4180
|
2
|
|
|
|
|
3
|
local $@; |
|
4181
|
2
|
|
|
|
|
2
|
$val = eval{ JSON->new->allow_nonref(1)->utf8(1)->decode( $bytes ) }; |
|
|
2
|
|
|
|
|
59
|
|
|
4182
|
|
|
|
|
|
|
} |
|
4183
|
|
|
|
|
|
|
|
|
4184
|
4
|
100
|
|
|
|
12
|
if( !defined( $val ) ) |
|
4185
|
|
|
|
|
|
|
{ |
|
4186
|
1
|
50
|
|
|
|
3
|
return( _err_res( $ctx, $ptr, "contentSchema present but payload not JSON-decodable", 'contentSchema' ) ) if( $assert ); |
|
4187
|
|
|
|
|
|
|
} |
|
4188
|
|
|
|
|
|
|
else |
|
4189
|
|
|
|
|
|
|
{ |
|
4190
|
3
|
|
|
|
|
9
|
my $r = _v( $ctx, _join_ptr( $ptr, 'contentSchema' ), $schema->{contentSchema}, $val ); |
|
4191
|
3
|
100
|
|
|
|
12
|
return( $r ) unless( $r->{ok} ); |
|
4192
|
|
|
|
|
|
|
} |
|
4193
|
|
|
|
|
|
|
} |
|
4194
|
|
|
|
|
|
|
} |
|
4195
|
|
|
|
|
|
|
} |
|
4196
|
|
|
|
|
|
|
|
|
4197
|
553
|
|
|
|
|
762
|
my %ann_props; |
|
4198
|
|
|
|
|
|
|
my %ann_items; |
|
4199
|
|
|
|
|
|
|
|
|
4200
|
|
|
|
|
|
|
# Arrays |
|
4201
|
553
|
100
|
|
|
|
1700
|
if( ref( $inst ) eq 'ARRAY' ) |
|
4202
|
|
|
|
|
|
|
{ |
|
4203
|
32
|
|
|
|
|
119
|
my $r = _k_array_all( $ctx, $schema_ptr, $schema, $inst ); |
|
4204
|
32
|
100
|
|
|
|
107
|
return( $r ) unless( $r->{ok} ); |
|
4205
|
23
|
|
|
|
|
42
|
%ann_items = ( %ann_items, %{ $r->{items} } ); |
|
|
23
|
|
|
|
|
115
|
|
|
4206
|
|
|
|
|
|
|
} |
|
4207
|
544
|
50
|
66
|
|
|
1227
|
_t( $ctx, $schema_ptr, 'array', undef, 'pass', '' ) if( ref( $inst ) eq 'ARRAY' && $ctx->{trace_on} ); |
|
4208
|
|
|
|
|
|
|
|
|
4209
|
|
|
|
|
|
|
# Objects |
|
4210
|
544
|
100
|
|
|
|
975
|
if( ref( $inst ) eq 'HASH' ) |
|
4211
|
|
|
|
|
|
|
{ |
|
4212
|
270
|
|
|
|
|
887
|
my $r = _k_object_all( $ctx, $schema_ptr, $schema, $inst ); |
|
4213
|
270
|
100
|
|
|
|
760
|
return( $r ) unless( $r->{ok} ); |
|
4214
|
157
|
|
|
|
|
309
|
%ann_props = ( %ann_props, %{ $r->{props} } ); |
|
|
157
|
|
|
|
|
618
|
|
|
4215
|
|
|
|
|
|
|
} |
|
4216
|
431
|
50
|
66
|
|
|
1311
|
_t( $ctx, $schema_ptr, 'object', undef, 'pass', '' ) if( ref( $inst ) eq 'HASH' && $ctx->{trace_on} ); |
|
4217
|
|
|
|
|
|
|
|
|
4218
|
|
|
|
|
|
|
# Combinators |
|
4219
|
431
|
|
|
|
|
650
|
for my $comb ( qw( allOf anyOf oneOf not ) ) |
|
4220
|
|
|
|
|
|
|
{ |
|
4221
|
1706
|
100
|
|
|
|
2850
|
next unless( exists( $schema->{ $comb } ) ); |
|
4222
|
25
|
|
|
|
|
90
|
my $r = _k_combinator( $ctx, $schema_ptr, $schema, $inst, $comb ); |
|
4223
|
25
|
100
|
|
|
|
65
|
return( $r ) unless( $r->{ok} ); |
|
4224
|
13
|
|
|
|
|
30
|
%ann_props = ( %ann_props, %{ $r->{props} } ); |
|
|
13
|
|
|
|
|
27
|
|
|
4225
|
13
|
|
|
|
|
19
|
%ann_items = ( %ann_items, %{ $r->{items} } ); |
|
|
13
|
|
|
|
|
16
|
|
|
4226
|
13
|
50
|
|
|
|
38
|
_t( $ctx, $schema_ptr, $comb, undef, 'pass', '' ) if( $ctx->{trace_on} ); |
|
4227
|
|
|
|
|
|
|
} |
|
4228
|
|
|
|
|
|
|
|
|
4229
|
|
|
|
|
|
|
# Conditionals |
|
4230
|
419
|
100
|
|
|
|
860
|
if( exists( $schema->{if} ) ) |
|
4231
|
|
|
|
|
|
|
{ |
|
4232
|
16
|
|
|
|
|
29
|
my $r = _k_if_then_else( $ctx, $schema_ptr, $schema, $inst ); |
|
4233
|
16
|
100
|
|
|
|
30
|
return( $r ) unless( $r->{ok} ); |
|
4234
|
13
|
|
|
|
|
18
|
%ann_props = ( %ann_props, %{ $r->{props} } ); |
|
|
13
|
|
|
|
|
17
|
|
|
4235
|
13
|
|
|
|
|
19
|
%ann_items = ( %ann_items, %{ $r->{items} } ); |
|
|
13
|
|
|
|
|
48
|
|
|
4236
|
|
|
|
|
|
|
} |
|
4237
|
|
|
|
|
|
|
|
|
4238
|
416
|
50
|
|
|
|
884
|
_t( $ctx, $schema_ptr, 'node', undef, 'pass' ) if( $ctx->{trace_on} ); |
|
4239
|
416
|
|
|
|
|
1819
|
return( { ok => 1, props => \%ann_props, items => \%ann_items } ); |
|
4240
|
|
|
|
|
|
|
} |
|
4241
|
|
|
|
|
|
|
|
|
4242
|
|
|
|
|
|
|
# NOTE: JSON::Schema::Validate::Error |
|
4243
|
|
|
|
|
|
|
package JSON::Schema::Validate::Error; |
|
4244
|
|
|
|
|
|
|
BEGIN |
|
4245
|
|
|
|
|
|
|
{ |
|
4246
|
44
|
|
|
44
|
|
754
|
use strict; |
|
|
44
|
|
|
|
|
138
|
|
|
|
44
|
|
|
|
|
2287
|
|
|
4247
|
44
|
|
|
44
|
|
419
|
use warnings; |
|
|
44
|
|
|
|
|
204
|
|
|
|
44
|
|
|
|
|
3554
|
|
|
4248
|
44
|
|
|
44
|
|
320
|
use vars qw( $VERSION ); |
|
|
44
|
|
|
|
|
124
|
|
|
|
44
|
|
|
|
|
7412
|
|
|
4249
|
|
|
|
|
|
|
use overload ( |
|
4250
|
|
|
|
|
|
|
'""' => 'as_string', |
|
4251
|
2
|
|
|
2
|
|
47
|
'eq' => sub{ _obj_eq(@_) }, |
|
4252
|
3
|
|
|
3
|
|
11
|
'ne' => sub{ !_obj_eq(@_) }, |
|
4253
|
1
|
|
|
1
|
|
5
|
'==' => sub{ _obj_eq(@_) }, |
|
4254
|
0
|
|
|
0
|
|
0
|
'!=' => sub{ !_obj_eq(@_) }, |
|
4255
|
5
|
|
|
5
|
|
239
|
bool => sub{1}, |
|
4256
|
44
|
|
|
|
|
850
|
fallback => 1, |
|
4257
|
44
|
|
|
44
|
|
286
|
); |
|
|
44
|
|
|
|
|
111
|
|
|
4258
|
44
|
|
|
44
|
|
8546
|
our $VERSION = 'v0.1.0'; |
|
4259
|
|
|
|
|
|
|
}; |
|
4260
|
|
|
|
|
|
|
|
|
4261
|
44
|
|
|
44
|
|
220
|
use strict; |
|
|
44
|
|
|
|
|
73
|
|
|
|
44
|
|
|
|
|
1085
|
|
|
4262
|
44
|
|
|
44
|
|
204
|
use warnings; |
|
|
44
|
|
|
|
|
126
|
|
|
|
44
|
|
|
|
|
2269
|
|
|
4263
|
44
|
|
|
44
|
|
991
|
use utf8; |
|
|
44
|
|
|
|
|
426
|
|
|
|
44
|
|
|
|
|
398
|
|
|
4264
|
|
|
|
|
|
|
|
|
4265
|
|
|
|
|
|
|
sub new |
|
4266
|
|
|
|
|
|
|
{ |
|
4267
|
180
|
|
|
180
|
|
234524
|
my $that = shift( @_ ); |
|
4268
|
180
|
|
|
|
|
432
|
my $ref = {}; |
|
4269
|
|
|
|
|
|
|
# Legacy instantiation |
|
4270
|
|
|
|
|
|
|
# We make sure this is not one single option that was provided to us. |
|
4271
|
180
|
100
|
66
|
|
|
592
|
if( @_ == 2 && $_[0] !~ /^(?:path|message|keyword|schema_pointer)$/ ) |
|
4272
|
|
|
|
|
|
|
{ |
|
4273
|
4
|
|
|
|
|
15
|
@$ref{qw( path message )} = @_; |
|
4274
|
|
|
|
|
|
|
} |
|
4275
|
|
|
|
|
|
|
else |
|
4276
|
|
|
|
|
|
|
{ |
|
4277
|
176
|
|
|
|
|
957
|
my $args = { @_ }; |
|
4278
|
176
|
|
|
|
|
430
|
for( qw( path message keyword schema_pointer ) ) |
|
4279
|
|
|
|
|
|
|
{ |
|
4280
|
704
|
50
|
|
|
|
2028
|
$ref->{ $_ } = $args->{ $_ } if( exists( $args->{ $_ } ) ); |
|
4281
|
|
|
|
|
|
|
} |
|
4282
|
|
|
|
|
|
|
} |
|
4283
|
180
|
|
33
|
|
|
999
|
return( bless( $ref => ( ref( $that ) || $that ) ) ); |
|
4284
|
|
|
|
|
|
|
} |
|
4285
|
|
|
|
|
|
|
|
|
4286
|
|
|
|
|
|
|
sub as_hash |
|
4287
|
|
|
|
|
|
|
{ |
|
4288
|
0
|
|
|
0
|
|
0
|
my $self = shift( @_ ); |
|
4289
|
0
|
|
|
|
|
0
|
my $ref = {}; |
|
4290
|
0
|
|
|
|
|
0
|
my @keys = qw( keyword message path schema_pointer ); |
|
4291
|
0
|
|
|
|
|
0
|
@$ref{ @keys } = @$self{ @keys }; |
|
4292
|
0
|
|
|
|
|
0
|
return( $ref ); |
|
4293
|
|
|
|
|
|
|
} |
|
4294
|
|
|
|
|
|
|
|
|
4295
|
|
|
|
|
|
|
sub as_string |
|
4296
|
|
|
|
|
|
|
{ |
|
4297
|
9
|
|
|
9
|
|
26
|
my $self = shift( @_ ); |
|
4298
|
9
|
|
100
|
|
|
34
|
my $sp = $self->schema_pointer // ''; |
|
4299
|
9
|
|
50
|
|
|
38
|
my $path = $self->path // ''; |
|
4300
|
9
|
|
50
|
|
|
31
|
my $msg = $self->message // ''; |
|
4301
|
|
|
|
|
|
|
# Dual-path if avail |
|
4302
|
9
|
100
|
|
|
|
147
|
return( $sp ? "${sp} → ${path}: ${msg}" : "${path}: ${msg}" ); |
|
4303
|
|
|
|
|
|
|
} |
|
4304
|
|
|
|
|
|
|
|
|
4305
|
|
|
|
|
|
|
sub keyword |
|
4306
|
|
|
|
|
|
|
{ |
|
4307
|
8
|
|
|
8
|
|
3151
|
my $self = shift( @_ ); |
|
4308
|
8
|
50
|
|
|
|
29
|
$self->{keyword} = shift( @_ ) if( @_ ); |
|
4309
|
8
|
|
|
|
|
81
|
return( $self->{keyword} ); |
|
4310
|
|
|
|
|
|
|
} |
|
4311
|
|
|
|
|
|
|
|
|
4312
|
|
|
|
|
|
|
sub message |
|
4313
|
|
|
|
|
|
|
{ |
|
4314
|
29
|
|
|
29
|
|
73
|
my $self = shift( @_ ); |
|
4315
|
29
|
50
|
|
|
|
73
|
$self->{message} = shift( @_ ) if( @_ ); |
|
4316
|
29
|
|
|
|
|
206
|
return( $self->{message} ); |
|
4317
|
|
|
|
|
|
|
} |
|
4318
|
|
|
|
|
|
|
|
|
4319
|
|
|
|
|
|
|
sub path |
|
4320
|
|
|
|
|
|
|
{ |
|
4321
|
17
|
|
|
17
|
|
1103
|
my $self = shift( @_ ); |
|
4322
|
17
|
50
|
|
|
|
44
|
$self->{path} = shift( @_ ) if( @_ ); |
|
4323
|
17
|
|
|
|
|
67
|
return( $self->{path} ); |
|
4324
|
|
|
|
|
|
|
} |
|
4325
|
|
|
|
|
|
|
|
|
4326
|
|
|
|
|
|
|
sub schema_pointer |
|
4327
|
|
|
|
|
|
|
{ |
|
4328
|
10
|
|
|
10
|
|
22
|
my $self = shift( @_ ); |
|
4329
|
10
|
50
|
|
|
|
82
|
$self->{schema_pointer} = shift( @_ ) if( @_ ); |
|
4330
|
10
|
|
|
|
|
114
|
return( $self->{schema_pointer} ); |
|
4331
|
|
|
|
|
|
|
} |
|
4332
|
|
|
|
|
|
|
|
|
4333
|
|
|
|
|
|
|
sub _obj_eq |
|
4334
|
|
|
|
|
|
|
{ |
|
4335
|
44
|
|
|
44
|
|
26664
|
no overloading; |
|
|
44
|
|
|
|
|
120
|
|
|
|
44
|
|
|
|
|
10594
|
|
|
4336
|
6
|
|
|
6
|
|
13
|
my $self = shift( @_ ); |
|
4337
|
6
|
|
|
|
|
10
|
my $other = shift( @_ ); |
|
4338
|
6
|
|
|
|
|
10
|
my $me; |
|
4339
|
6
|
100
|
66
|
|
|
75
|
if( defined( $other ) && |
|
|
|
50
|
66
|
|
|
|
|
|
4340
|
|
|
|
|
|
|
Scalar::Util::blessed( $other ) && |
|
4341
|
|
|
|
|
|
|
$other->isa( 'JSON::Schema::Validate::Error' ) ) |
|
4342
|
|
|
|
|
|
|
{ |
|
4343
|
4
|
100
|
50
|
|
|
14
|
if( ( $self->message // '' ) eq ( $other->message // '' ) && |
|
|
|
|
50
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
4344
|
|
|
|
|
|
|
( $self->path // '' ) eq ( $other->path // '' ) ) |
|
4345
|
|
|
|
|
|
|
{ |
|
4346
|
2
|
|
|
|
|
26
|
return(1); |
|
4347
|
|
|
|
|
|
|
} |
|
4348
|
|
|
|
|
|
|
else |
|
4349
|
|
|
|
|
|
|
{ |
|
4350
|
2
|
|
|
|
|
13
|
return(0); |
|
4351
|
|
|
|
|
|
|
} |
|
4352
|
|
|
|
|
|
|
} |
|
4353
|
|
|
|
|
|
|
# Compare error message |
|
4354
|
|
|
|
|
|
|
elsif( !ref( $other ) ) |
|
4355
|
|
|
|
|
|
|
{ |
|
4356
|
2
|
|
50
|
|
|
6
|
my $me = $self->message // ''; |
|
4357
|
2
|
|
|
|
|
13
|
return( $me eq $other ); |
|
4358
|
|
|
|
|
|
|
} |
|
4359
|
|
|
|
|
|
|
# Otherwise some reference data to which we cannot compare |
|
4360
|
0
|
|
|
|
|
|
return(0) ; |
|
4361
|
|
|
|
|
|
|
} |
|
4362
|
|
|
|
|
|
|
|
|
4363
|
|
|
|
|
|
|
1; |
|
4364
|
|
|
|
|
|
|
# NOTE: POD |
|
4365
|
|
|
|
|
|
|
__END__ |
|
4366
|
|
|
|
|
|
|
|
|
4367
|
|
|
|
|
|
|
=encoding utf-8 |
|
4368
|
|
|
|
|
|
|
|
|
4369
|
|
|
|
|
|
|
=head1 NAME |
|
4370
|
|
|
|
|
|
|
|
|
4371
|
|
|
|
|
|
|
JSON::Schema::Validate - Lean, recursion-safe JSON Schema validator (Draft 2020-12) |
|
4372
|
|
|
|
|
|
|
|
|
4373
|
|
|
|
|
|
|
=head1 SYNOPSIS |
|
4374
|
|
|
|
|
|
|
|
|
4375
|
|
|
|
|
|
|
use JSON::Schema::Validate; |
|
4376
|
|
|
|
|
|
|
use JSON (); |
|
4377
|
|
|
|
|
|
|
|
|
4378
|
|
|
|
|
|
|
my $schema = { |
|
4379
|
|
|
|
|
|
|
'$schema' => 'https://json-schema.org/draft/2020-12/schema', |
|
4380
|
|
|
|
|
|
|
'$id' => 'https://example.org/s/root.json', |
|
4381
|
|
|
|
|
|
|
type => 'object', |
|
4382
|
|
|
|
|
|
|
required => [ 'name' ], |
|
4383
|
|
|
|
|
|
|
properties => { |
|
4384
|
|
|
|
|
|
|
name => { type => 'string', minLength => 1 }, |
|
4385
|
|
|
|
|
|
|
next => { '$dynamicRef' => '#Node' }, |
|
4386
|
|
|
|
|
|
|
}, |
|
4387
|
|
|
|
|
|
|
'$dynamicAnchor' => 'Node', |
|
4388
|
|
|
|
|
|
|
additionalProperties => JSON::false, |
|
4389
|
|
|
|
|
|
|
}; |
|
4390
|
|
|
|
|
|
|
|
|
4391
|
|
|
|
|
|
|
my $js = JSON::Schema::Validate->new( $schema ) |
|
4392
|
|
|
|
|
|
|
->compile |
|
4393
|
|
|
|
|
|
|
->content_checks |
|
4394
|
|
|
|
|
|
|
->ignore_unknown_required_vocab |
|
4395
|
|
|
|
|
|
|
->prune_unknown |
|
4396
|
|
|
|
|
|
|
->register_builtin_formats |
|
4397
|
|
|
|
|
|
|
->trace |
|
4398
|
|
|
|
|
|
|
->trace_limit(200) # 0 means unlimited |
|
4399
|
|
|
|
|
|
|
->unique_keys; # enable uniqueKeys |
|
4400
|
|
|
|
|
|
|
|
|
4401
|
|
|
|
|
|
|
You could also do: |
|
4402
|
|
|
|
|
|
|
|
|
4403
|
|
|
|
|
|
|
my $js = JSON::Schema::Validate->new( $schema, |
|
4404
|
|
|
|
|
|
|
compile => 1, |
|
4405
|
|
|
|
|
|
|
content_checks => 1, |
|
4406
|
|
|
|
|
|
|
ignore_req_vocab => 1, |
|
4407
|
|
|
|
|
|
|
prune_unknown => 1, |
|
4408
|
|
|
|
|
|
|
trace_on => 1, |
|
4409
|
|
|
|
|
|
|
trace_limit => 200, |
|
4410
|
|
|
|
|
|
|
unique_keys => 1, |
|
4411
|
|
|
|
|
|
|
)->register_builtin_formats; |
|
4412
|
|
|
|
|
|
|
|
|
4413
|
|
|
|
|
|
|
my $ok = $js->validate({ name => 'head', next => { name => 'tail' } }) |
|
4414
|
|
|
|
|
|
|
or die( $js->error ); |
|
4415
|
|
|
|
|
|
|
|
|
4416
|
|
|
|
|
|
|
print "ok\n"; |
|
4417
|
|
|
|
|
|
|
|
|
4418
|
|
|
|
|
|
|
# Override instance options for one call only (backward compatible) |
|
4419
|
|
|
|
|
|
|
$js->validate( $data, max_errors => 1 ) |
|
4420
|
|
|
|
|
|
|
or die( $js->error ); |
|
4421
|
|
|
|
|
|
|
|
|
4422
|
|
|
|
|
|
|
# Quick boolean check (records at most one error by default) |
|
4423
|
|
|
|
|
|
|
$js->is_valid({ name => 'head', next => { name => 'tail' } }) |
|
4424
|
|
|
|
|
|
|
or die( $js->error ); |
|
4425
|
|
|
|
|
|
|
|
|
4426
|
|
|
|
|
|
|
Generating a browser-side validator with L</compile_js>: |
|
4427
|
|
|
|
|
|
|
|
|
4428
|
|
|
|
|
|
|
use JSON::Schema::Validate; |
|
4429
|
|
|
|
|
|
|
use JSON (); |
|
4430
|
|
|
|
|
|
|
|
|
4431
|
|
|
|
|
|
|
my $schema = JSON->new->decode( do { |
|
4432
|
|
|
|
|
|
|
local $/; |
|
4433
|
|
|
|
|
|
|
<DATA>; |
|
4434
|
|
|
|
|
|
|
} ); |
|
4435
|
|
|
|
|
|
|
|
|
4436
|
|
|
|
|
|
|
my $js = JSON::Schema::Validate->new( $schema ) |
|
4437
|
|
|
|
|
|
|
->compile; |
|
4438
|
|
|
|
|
|
|
my $ok = $js->validate({ name => 'head', next => { name => 'tail' } }) |
|
4439
|
|
|
|
|
|
|
or die( $js->error ); |
|
4440
|
|
|
|
|
|
|
|
|
4441
|
|
|
|
|
|
|
# Generate a standalone JavaScript validator for use in a web page. |
|
4442
|
|
|
|
|
|
|
# ecma => 2018 enables Unicode regex features when available. |
|
4443
|
|
|
|
|
|
|
my $js_code = $validator->compile_js( ecma => 2018 ); |
|
4444
|
|
|
|
|
|
|
|
|
4445
|
|
|
|
|
|
|
open my $fh, '>:encoding(UTF-8)', 'htdocs/js/schema-validator.js' |
|
4446
|
|
|
|
|
|
|
or die( "Unable to write schema-validator.js: $!" ); |
|
4447
|
|
|
|
|
|
|
print {$fh} $js_code; |
|
4448
|
|
|
|
|
|
|
close $fh; |
|
4449
|
|
|
|
|
|
|
|
|
4450
|
|
|
|
|
|
|
In your HTML: |
|
4451
|
|
|
|
|
|
|
|
|
4452
|
|
|
|
|
|
|
<script src="/js/schema-validator.js"></script> |
|
4453
|
|
|
|
|
|
|
<script> |
|
4454
|
|
|
|
|
|
|
function validateForm() |
|
4455
|
|
|
|
|
|
|
{ |
|
4456
|
|
|
|
|
|
|
var src = document.getElementById('payload').value; |
|
4457
|
|
|
|
|
|
|
var out = document.getElementById('errors'); |
|
4458
|
|
|
|
|
|
|
var inst; |
|
4459
|
|
|
|
|
|
|
|
|
4460
|
|
|
|
|
|
|
try |
|
4461
|
|
|
|
|
|
|
{ |
|
4462
|
|
|
|
|
|
|
inst = JSON.parse( src ); |
|
4463
|
|
|
|
|
|
|
} |
|
4464
|
|
|
|
|
|
|
catch( e ) |
|
4465
|
|
|
|
|
|
|
{ |
|
4466
|
|
|
|
|
|
|
out.textContent = "Invalid JSON: " + e; |
|
4467
|
|
|
|
|
|
|
return; |
|
4468
|
|
|
|
|
|
|
} |
|
4469
|
|
|
|
|
|
|
|
|
4470
|
|
|
|
|
|
|
// The generated file defines a global function `validate(inst)` |
|
4471
|
|
|
|
|
|
|
// that returns an array of error objects. |
|
4472
|
|
|
|
|
|
|
var errors = validate( inst ); |
|
4473
|
|
|
|
|
|
|
|
|
4474
|
|
|
|
|
|
|
if( !errors || !errors.length ) |
|
4475
|
|
|
|
|
|
|
{ |
|
4476
|
|
|
|
|
|
|
out.textContent = "OK – no client-side schema errors."; |
|
4477
|
|
|
|
|
|
|
return; |
|
4478
|
|
|
|
|
|
|
} |
|
4479
|
|
|
|
|
|
|
|
|
4480
|
|
|
|
|
|
|
var lines = []; |
|
4481
|
|
|
|
|
|
|
for( var i = 0; i < errors.length; i++ ) |
|
4482
|
|
|
|
|
|
|
{ |
|
4483
|
|
|
|
|
|
|
var e = errors[i]; |
|
4484
|
|
|
|
|
|
|
lines.push( |
|
4485
|
|
|
|
|
|
|
e.path + " [" + e.keyword + "]: " + e.message |
|
4486
|
|
|
|
|
|
|
); |
|
4487
|
|
|
|
|
|
|
} |
|
4488
|
|
|
|
|
|
|
out.textContent = lines.join("\n"); |
|
4489
|
|
|
|
|
|
|
} |
|
4490
|
|
|
|
|
|
|
</script> |
|
4491
|
|
|
|
|
|
|
|
|
4492
|
|
|
|
|
|
|
See also the live demonstration on CodePen: L<https://codepen.io/jdeguest/pen/vEGjNYX> |
|
4493
|
|
|
|
|
|
|
|
|
4494
|
|
|
|
|
|
|
and the original announcement on Reddit: L<https://www.reddit.com/r/perl/comments/1p80dne/showcase_localised_json_schema_validation_in_perl/> |
|
4495
|
|
|
|
|
|
|
|
|
4496
|
|
|
|
|
|
|
=head1 VERSION |
|
4497
|
|
|
|
|
|
|
|
|
4498
|
|
|
|
|
|
|
v0.9.0 |
|
4499
|
|
|
|
|
|
|
|
|
4500
|
|
|
|
|
|
|
=head1 DESCRIPTION |
|
4501
|
|
|
|
|
|
|
|
|
4502
|
|
|
|
|
|
|
C<JSON::Schema::Validate> is a compact, dependency-light validator for L<JSON Schema|https://json-schema.org/> draft 2020-12. It focuses on: |
|
4503
|
|
|
|
|
|
|
|
|
4504
|
|
|
|
|
|
|
=over 4 |
|
4505
|
|
|
|
|
|
|
|
|
4506
|
|
|
|
|
|
|
=item * |
|
4507
|
|
|
|
|
|
|
|
|
4508
|
|
|
|
|
|
|
Correctness and recursion safety (supports C<$ref>, C<$dynamicRef>, C<$anchor>, C<$dynamicAnchor>). |
|
4509
|
|
|
|
|
|
|
|
|
4510
|
|
|
|
|
|
|
=item * |
|
4511
|
|
|
|
|
|
|
|
|
4512
|
|
|
|
|
|
|
Draft 2020-12 evaluation semantics, including C<unevaluatedItems> and C<unevaluatedProperties> with annotation tracking. |
|
4513
|
|
|
|
|
|
|
|
|
4514
|
|
|
|
|
|
|
=item * |
|
4515
|
|
|
|
|
|
|
|
|
4516
|
|
|
|
|
|
|
A practical Perl API (constructor takes the schema; call C<validate> with your data; inspect C<error> / C<errors> on failure). |
|
4517
|
|
|
|
|
|
|
|
|
4518
|
|
|
|
|
|
|
=item * |
|
4519
|
|
|
|
|
|
|
|
|
4520
|
|
|
|
|
|
|
Builtin validators for common C<format>s (date, time, email, hostname, ip, uri, uuid, JSON Pointer, etc.), with the option to register or override custom format handlers. |
|
4521
|
|
|
|
|
|
|
|
|
4522
|
|
|
|
|
|
|
=item * |
|
4523
|
|
|
|
|
|
|
|
|
4524
|
|
|
|
|
|
|
Optional code generation via L</compile_js> to run a subset of the schema client-side in JavaScript, using the same error structure as the Perl validator. |
|
4525
|
|
|
|
|
|
|
|
|
4526
|
|
|
|
|
|
|
=back |
|
4527
|
|
|
|
|
|
|
|
|
4528
|
|
|
|
|
|
|
This module is intentionally minimal compared to large reference implementations, but it implements the parts most people rely on in production. |
|
4529
|
|
|
|
|
|
|
|
|
4530
|
|
|
|
|
|
|
=head2 Supported Keywords (2020-12) |
|
4531
|
|
|
|
|
|
|
|
|
4532
|
|
|
|
|
|
|
=over 4 |
|
4533
|
|
|
|
|
|
|
|
|
4534
|
|
|
|
|
|
|
=item * Types |
|
4535
|
|
|
|
|
|
|
|
|
4536
|
|
|
|
|
|
|
C<type> (string or array of strings), including union types. Unions may also include inline schemas (e.g. C<< type => [ 'integer', { minimum => 0 } ] >>). |
|
4537
|
|
|
|
|
|
|
|
|
4538
|
|
|
|
|
|
|
=item * Constant / Enumerations |
|
4539
|
|
|
|
|
|
|
|
|
4540
|
|
|
|
|
|
|
C<const>, C<enum>. |
|
4541
|
|
|
|
|
|
|
|
|
4542
|
|
|
|
|
|
|
=item * Numbers |
|
4543
|
|
|
|
|
|
|
|
|
4544
|
|
|
|
|
|
|
C<multipleOf>, C<minimum>, C<maximum>, C<exclusiveMinimum>, C<exclusiveMaximum>. |
|
4545
|
|
|
|
|
|
|
|
|
4546
|
|
|
|
|
|
|
=item * Strings |
|
4547
|
|
|
|
|
|
|
|
|
4548
|
|
|
|
|
|
|
C<minLength>, C<maxLength>, C<pattern>, C<format>. |
|
4549
|
|
|
|
|
|
|
|
|
4550
|
|
|
|
|
|
|
=item * Arrays |
|
4551
|
|
|
|
|
|
|
|
|
4552
|
|
|
|
|
|
|
C<prefixItems>, C<items>, C<contains>, C<minContains>, C<maxContains>, C<uniqueItems>, C<unevaluatedItems>. |
|
4553
|
|
|
|
|
|
|
|
|
4554
|
|
|
|
|
|
|
=item * Objects |
|
4555
|
|
|
|
|
|
|
|
|
4556
|
|
|
|
|
|
|
C<properties>, C<patternProperties>, C<additionalProperties>, C<propertyNames>, C<required>, C<dependentRequired>, C<dependentSchemas>, C<unevaluatedProperties>. |
|
4557
|
|
|
|
|
|
|
|
|
4558
|
|
|
|
|
|
|
=item * Combinators |
|
4559
|
|
|
|
|
|
|
|
|
4560
|
|
|
|
|
|
|
C<allOf>, C<anyOf>, C<oneOf>, C<not>. |
|
4561
|
|
|
|
|
|
|
|
|
4562
|
|
|
|
|
|
|
=item * Conditionals |
|
4563
|
|
|
|
|
|
|
|
|
4564
|
|
|
|
|
|
|
C<if>, C<then>, C<else>. |
|
4565
|
|
|
|
|
|
|
|
|
4566
|
|
|
|
|
|
|
=item * Referencing |
|
4567
|
|
|
|
|
|
|
|
|
4568
|
|
|
|
|
|
|
C<$id>, C<$anchor>, C<$ref>, C<$dynamicAnchor>, C<$dynamicRef>. |
|
4569
|
|
|
|
|
|
|
|
|
4570
|
|
|
|
|
|
|
=back |
|
4571
|
|
|
|
|
|
|
|
|
4572
|
|
|
|
|
|
|
The Perl engine supports the full list above. The generated JavaScript currently implements a pragmatic subset; see L</compile_js> for details. |
|
4573
|
|
|
|
|
|
|
|
|
4574
|
|
|
|
|
|
|
=head2 Formats |
|
4575
|
|
|
|
|
|
|
|
|
4576
|
|
|
|
|
|
|
Call C<register_builtin_formats> to install default validators for the following C<format> names: |
|
4577
|
|
|
|
|
|
|
|
|
4578
|
|
|
|
|
|
|
=over 4 |
|
4579
|
|
|
|
|
|
|
|
|
4580
|
|
|
|
|
|
|
=item * C<date-time>, C<date>, C<time>, C<duration> |
|
4581
|
|
|
|
|
|
|
|
|
4582
|
|
|
|
|
|
|
For C<date-time> and C<date>, leverages L<DateTime::Format::ISO8601> when available, then falls back to L<Time::Piece> (core since perl 5.10), then to L<DateTime> if installed, and finally to strict regex checks. C<duration> is validated entirely by a strict ISO 8601 regex; no external module is required or used. |
|
4583
|
|
|
|
|
|
|
|
|
4584
|
|
|
|
|
|
|
=item * C<email>, C<idn-email> |
|
4585
|
|
|
|
|
|
|
|
|
4586
|
|
|
|
|
|
|
Imported and use the very complex and complete regular expression from L<Regexp::Common::Email::Address>, but without requiring this module. |
|
4587
|
|
|
|
|
|
|
|
|
4588
|
|
|
|
|
|
|
=item * C<hostname>, C<idn-hostname> |
|
4589
|
|
|
|
|
|
|
|
|
4590
|
|
|
|
|
|
|
C<idn-hostname> uses L<Net::IDN::Encode> if available; otherwise, applies a permissive Unicode label check and then C<hostname> rules. |
|
4591
|
|
|
|
|
|
|
|
|
4592
|
|
|
|
|
|
|
=item * C<ipv4>, C<ipv6> |
|
4593
|
|
|
|
|
|
|
|
|
4594
|
|
|
|
|
|
|
Strict regex-based validation. |
|
4595
|
|
|
|
|
|
|
|
|
4596
|
|
|
|
|
|
|
=item * C<uri>, C<uri-reference>, C<iri> |
|
4597
|
|
|
|
|
|
|
|
|
4598
|
|
|
|
|
|
|
Reasonable regex checks: C<uri> and C<iri> require a proper RFC-compliant scheme (C<ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )> followed by C<:>); C<iri> additionally permits Unicode characters in the path and query components. C<uri-reference> allows an optional scheme. None of these is a full RFC 2396/3987 parser. |
|
4599
|
|
|
|
|
|
|
|
|
4600
|
|
|
|
|
|
|
=item * C<uuid> |
|
4601
|
|
|
|
|
|
|
|
|
4602
|
|
|
|
|
|
|
Hyphenated 8-4-4-4-12 hex. |
|
4603
|
|
|
|
|
|
|
|
|
4604
|
|
|
|
|
|
|
=item * C<json-pointer>, C<relative-json-pointer> |
|
4605
|
|
|
|
|
|
|
|
|
4606
|
|
|
|
|
|
|
Conformant to RFC 6901 and the relative variant used by JSON Schema. |
|
4607
|
|
|
|
|
|
|
|
|
4608
|
|
|
|
|
|
|
=item * C<regex> |
|
4609
|
|
|
|
|
|
|
|
|
4610
|
|
|
|
|
|
|
Checks that the pattern compiles in Perl. |
|
4611
|
|
|
|
|
|
|
|
|
4612
|
|
|
|
|
|
|
=back |
|
4613
|
|
|
|
|
|
|
|
|
4614
|
|
|
|
|
|
|
Custom formats can be registered or override builtins via C<register_format> or the C<format =E<gt> { ... }> constructor option (see L</METHODS>). |
|
4615
|
|
|
|
|
|
|
|
|
4616
|
|
|
|
|
|
|
=head1 CONSTRUCTOR |
|
4617
|
|
|
|
|
|
|
|
|
4618
|
|
|
|
|
|
|
=head2 new |
|
4619
|
|
|
|
|
|
|
|
|
4620
|
|
|
|
|
|
|
my $js = JSON::Schema::Validate->new( $schema, %opts ); |
|
4621
|
|
|
|
|
|
|
|
|
4622
|
|
|
|
|
|
|
Build a validator from a decoded JSON Schema (Perl hash/array structure), and returns the newly instantiated object. |
|
4623
|
|
|
|
|
|
|
|
|
4624
|
|
|
|
|
|
|
Options (all optional): |
|
4625
|
|
|
|
|
|
|
|
|
4626
|
|
|
|
|
|
|
=over 4 |
|
4627
|
|
|
|
|
|
|
|
|
4628
|
|
|
|
|
|
|
=item C<compile =E<gt> 1|0> |
|
4629
|
|
|
|
|
|
|
|
|
4630
|
|
|
|
|
|
|
Defaults to C<0> |
|
4631
|
|
|
|
|
|
|
|
|
4632
|
|
|
|
|
|
|
Enable or disable the compiled-validator fast path. |
|
4633
|
|
|
|
|
|
|
|
|
4634
|
|
|
|
|
|
|
When enabled and the root has not been compiled yet, this triggers an initial compilation. |
|
4635
|
|
|
|
|
|
|
|
|
4636
|
|
|
|
|
|
|
=item C<content_assert =E<gt> 1|0> |
|
4637
|
|
|
|
|
|
|
|
|
4638
|
|
|
|
|
|
|
Defaults to C<0> |
|
4639
|
|
|
|
|
|
|
|
|
4640
|
|
|
|
|
|
|
Enable or disable the content assertions for the C<contentEncoding>, C<contentMediaType> and C<contentSchema> trio. |
|
4641
|
|
|
|
|
|
|
|
|
4642
|
|
|
|
|
|
|
When enabling, built-in media validators are registered (e.g. C<application/json>). |
|
4643
|
|
|
|
|
|
|
|
|
4644
|
|
|
|
|
|
|
=item C<extensions =E<gt> 1|0> |
|
4645
|
|
|
|
|
|
|
|
|
4646
|
|
|
|
|
|
|
Defaults to C<0> |
|
4647
|
|
|
|
|
|
|
|
|
4648
|
|
|
|
|
|
|
This enables or disables all non-core extensions currently implemented by the validator. |
|
4649
|
|
|
|
|
|
|
|
|
4650
|
|
|
|
|
|
|
When set to a true value, this enables the C<uniqueKeys> applicator. Future extensions (e.g. custom keywords, additional vocabularies) will also be controlled by this flag. |
|
4651
|
|
|
|
|
|
|
|
|
4652
|
|
|
|
|
|
|
When set to a true value, all known extensions are activated; setting it to false disables them all. |
|
4653
|
|
|
|
|
|
|
|
|
4654
|
|
|
|
|
|
|
If you set separately an extension boolean value, it will not be overriden by this. So for example: |
|
4655
|
|
|
|
|
|
|
|
|
4656
|
|
|
|
|
|
|
my $js = JSON::Schema::Validate->new( $schema, extension => 0, unique_keys => 1 ); |
|
4657
|
|
|
|
|
|
|
|
|
4658
|
|
|
|
|
|
|
Will globally disable extension, but will enable C<uniqueKeys> |
|
4659
|
|
|
|
|
|
|
|
|
4660
|
|
|
|
|
|
|
Enabling extensions does not affect core Draft 2020-12 compliance — unknown keywords are still ignored unless explicitly supported. |
|
4661
|
|
|
|
|
|
|
|
|
4662
|
|
|
|
|
|
|
=item C<format =E<gt> \%callbacks> |
|
4663
|
|
|
|
|
|
|
|
|
4664
|
|
|
|
|
|
|
Hash of C<format_name =E<gt> sub{ ... }> validators. Each sub receives the string to validate and must return true/false. Entries here take precedence when you later call C<register_builtin_formats> (i.e. your callbacks remain in place). |
|
4665
|
|
|
|
|
|
|
|
|
4666
|
|
|
|
|
|
|
=item C<ignore_unknown_required_vocab =E<gt> 1|0> |
|
4667
|
|
|
|
|
|
|
|
|
4668
|
|
|
|
|
|
|
Defaults to C<0> |
|
4669
|
|
|
|
|
|
|
|
|
4670
|
|
|
|
|
|
|
If enabled, required vocabularies declared in C<$vocabulary> that are not advertised as supported by the caller will be I<ignored> instead of causing the validator to C<die>. |
|
4671
|
|
|
|
|
|
|
|
|
4672
|
|
|
|
|
|
|
You can also use C<ignore_req_vocab> for short. |
|
4673
|
|
|
|
|
|
|
|
|
4674
|
|
|
|
|
|
|
=item C<max_errors> |
|
4675
|
|
|
|
|
|
|
|
|
4676
|
|
|
|
|
|
|
Defaults to C<200> |
|
4677
|
|
|
|
|
|
|
|
|
4678
|
|
|
|
|
|
|
Sets the maximum number of errors to be recorded. |
|
4679
|
|
|
|
|
|
|
|
|
4680
|
|
|
|
|
|
|
=item C<normalize_instance =E<gt> 1|0> |
|
4681
|
|
|
|
|
|
|
|
|
4682
|
|
|
|
|
|
|
Defaults to C<1> |
|
4683
|
|
|
|
|
|
|
|
|
4684
|
|
|
|
|
|
|
When true, the instance is round-tripped through L<JSON> before validation, which enforces strict JSON typing (strings remain strings; numbers remain numbers). This matches Python C<jsonschema>’s type behaviour. Set to C<0> if you prefer Perl’s permissive numeric/string duality. |
|
4685
|
|
|
|
|
|
|
|
|
4686
|
|
|
|
|
|
|
=item C<prune_unknown =E<gt> 1|0> |
|
4687
|
|
|
|
|
|
|
|
|
4688
|
|
|
|
|
|
|
Defaults to C<0> |
|
4689
|
|
|
|
|
|
|
|
|
4690
|
|
|
|
|
|
|
When set to a true value, unknown object properties in the instance are pruned (removed) prior to validation, based on the schema’s structural keywords. |
|
4691
|
|
|
|
|
|
|
|
|
4692
|
|
|
|
|
|
|
Pruning currently takes into account: |
|
4693
|
|
|
|
|
|
|
|
|
4694
|
|
|
|
|
|
|
=over 4 |
|
4695
|
|
|
|
|
|
|
|
|
4696
|
|
|
|
|
|
|
=item * C<properties> |
|
4697
|
|
|
|
|
|
|
|
|
4698
|
|
|
|
|
|
|
=item * C<patternProperties> |
|
4699
|
|
|
|
|
|
|
|
|
4700
|
|
|
|
|
|
|
=item * C<additionalProperties> |
|
4701
|
|
|
|
|
|
|
|
|
4702
|
|
|
|
|
|
|
(item value or subschema, including within C<allOf>) |
|
4703
|
|
|
|
|
|
|
|
|
4704
|
|
|
|
|
|
|
=item * C<allOf> (for merging additional object or array constraints) |
|
4705
|
|
|
|
|
|
|
|
|
4706
|
|
|
|
|
|
|
=back |
|
4707
|
|
|
|
|
|
|
|
|
4708
|
|
|
|
|
|
|
For objects: |
|
4709
|
|
|
|
|
|
|
|
|
4710
|
|
|
|
|
|
|
=over 4 |
|
4711
|
|
|
|
|
|
|
|
|
4712
|
|
|
|
|
|
|
=item * |
|
4713
|
|
|
|
|
|
|
|
|
4714
|
|
|
|
|
|
|
Any property explicitly declared under C<properties> is kept, and its value is recursively pruned according to its subschema (if it is itself an object or array). |
|
4715
|
|
|
|
|
|
|
|
|
4716
|
|
|
|
|
|
|
=item * |
|
4717
|
|
|
|
|
|
|
|
|
4718
|
|
|
|
|
|
|
Any property whose name matches one of the C<patternProperties> regular expressions is kept, and pruned recursively according to the associated subschema. |
|
4719
|
|
|
|
|
|
|
|
|
4720
|
|
|
|
|
|
|
=item * |
|
4721
|
|
|
|
|
|
|
|
|
4722
|
|
|
|
|
|
|
If C<additionalProperties> is C<false>, any object property not covered by C<properties> or C<patternProperties> is removed. |
|
4723
|
|
|
|
|
|
|
|
|
4724
|
|
|
|
|
|
|
=item * |
|
4725
|
|
|
|
|
|
|
|
|
4726
|
|
|
|
|
|
|
If C<additionalProperties> is a subschema, any such additional property is kept, and its value is pruned recursively following that subschema. |
|
4727
|
|
|
|
|
|
|
|
|
4728
|
|
|
|
|
|
|
=back |
|
4729
|
|
|
|
|
|
|
|
|
4730
|
|
|
|
|
|
|
For arrays: |
|
4731
|
|
|
|
|
|
|
|
|
4732
|
|
|
|
|
|
|
=over 4 |
|
4733
|
|
|
|
|
|
|
|
|
4734
|
|
|
|
|
|
|
=item * |
|
4735
|
|
|
|
|
|
|
|
|
4736
|
|
|
|
|
|
|
Items covered by C<prefixItems> (by index) or C<items> (for remaining elements) are kept, and if they are objects or arrays, they are pruned recursively. Existing positions are never removed; pruning only affects the nested contents. |
|
4737
|
|
|
|
|
|
|
|
|
4738
|
|
|
|
|
|
|
=back |
|
4739
|
|
|
|
|
|
|
|
|
4740
|
|
|
|
|
|
|
The pruner intentionally does B<not> interpret C<anyOf>, C<oneOf> or C<not> when deciding which properties to keep or drop, because doing so would require running full validation logic and could remove legitimate data incorrectly. In those cases, pruning errs on the side of keeping more data rather than over-pruning. |
|
4741
|
|
|
|
|
|
|
|
|
4742
|
|
|
|
|
|
|
When C<prune_unknown> is disabled (the default), the instance is not modified for validation purposes, and no pruning is performed. |
|
4743
|
|
|
|
|
|
|
|
|
4744
|
|
|
|
|
|
|
=item C<trace> |
|
4745
|
|
|
|
|
|
|
|
|
4746
|
|
|
|
|
|
|
Defaults to C<0> |
|
4747
|
|
|
|
|
|
|
|
|
4748
|
|
|
|
|
|
|
Enable or disable tracing. When enabled, the validator records lightweight, bounded trace events according to L</trace_limit> and L</trace_sample>. |
|
4749
|
|
|
|
|
|
|
|
|
4750
|
|
|
|
|
|
|
=item C<trace_limit> |
|
4751
|
|
|
|
|
|
|
|
|
4752
|
|
|
|
|
|
|
Defaults to C<0> |
|
4753
|
|
|
|
|
|
|
|
|
4754
|
|
|
|
|
|
|
Set a hard cap on the number of trace entries recorded during a single C<validate> call (C<0> = unlimited). |
|
4755
|
|
|
|
|
|
|
|
|
4756
|
|
|
|
|
|
|
=item C<trace_sample =E<gt> $percent> |
|
4757
|
|
|
|
|
|
|
|
|
4758
|
|
|
|
|
|
|
Enable probabilistic sampling of trace events. C<$percent> is an integer percentage in C<[0,100]>. C<0> disables sampling. Sampling occurs per-event, and still respects L</trace_limit>. |
|
4759
|
|
|
|
|
|
|
|
|
4760
|
|
|
|
|
|
|
=item C<unique_keys =E<gt> 1|0> |
|
4761
|
|
|
|
|
|
|
|
|
4762
|
|
|
|
|
|
|
Defaults to C<0> |
|
4763
|
|
|
|
|
|
|
|
|
4764
|
|
|
|
|
|
|
Explicitly enable or disable the C<uniqueKeys> applicator. |
|
4765
|
|
|
|
|
|
|
|
|
4766
|
|
|
|
|
|
|
C<uniqueKeys> is a non-standard extension (proposed for future drafts) that enforces uniqueness of one or more properties across all objects in an array. |
|
4767
|
|
|
|
|
|
|
|
|
4768
|
|
|
|
|
|
|
"uniqueKeys": [ ["id"] ] # 'id' must be unique |
|
4769
|
|
|
|
|
|
|
"uniqueKeys": [ ["id"], ["email"] ] # id AND email must each be unique |
|
4770
|
|
|
|
|
|
|
"uniqueKeys": [ ["category", "code"] ] # the pair (category,code) must be unique |
|
4771
|
|
|
|
|
|
|
|
|
4772
|
|
|
|
|
|
|
The applicator supports both single-property constraints and true composite keys. |
|
4773
|
|
|
|
|
|
|
|
|
4774
|
|
|
|
|
|
|
This option is useful when you need stronger guarantees than C<uniqueItems> provides, without resorting to complex C<contains>/C<not> patterns. |
|
4775
|
|
|
|
|
|
|
|
|
4776
|
|
|
|
|
|
|
When C<extensions> is enabled, C<unique_keys> is automatically turned on; the specific method allows finer-grained control. |
|
4777
|
|
|
|
|
|
|
|
|
4778
|
|
|
|
|
|
|
This works in B<both interpreted and compiled modes> and is fully integrated into the annotation system (plays nicely with C<unevaluatedProperties>, etc.). |
|
4779
|
|
|
|
|
|
|
|
|
4780
|
|
|
|
|
|
|
Disabled by default for maximum spec purity. |
|
4781
|
|
|
|
|
|
|
|
|
4782
|
|
|
|
|
|
|
=item C<vocab_support =E<gt> {}> |
|
4783
|
|
|
|
|
|
|
|
|
4784
|
|
|
|
|
|
|
A hash reference of support vocabularies. |
|
4785
|
|
|
|
|
|
|
|
|
4786
|
|
|
|
|
|
|
=back |
|
4787
|
|
|
|
|
|
|
|
|
4788
|
|
|
|
|
|
|
=head1 METHODS |
|
4789
|
|
|
|
|
|
|
|
|
4790
|
|
|
|
|
|
|
=head2 compile |
|
4791
|
|
|
|
|
|
|
|
|
4792
|
|
|
|
|
|
|
$js->compile; # enable compilation |
|
4793
|
|
|
|
|
|
|
$js->compile(1); # enable |
|
4794
|
|
|
|
|
|
|
$js->compile(0); # disable |
|
4795
|
|
|
|
|
|
|
|
|
4796
|
|
|
|
|
|
|
Enable or disable the compiled-validator fast path. |
|
4797
|
|
|
|
|
|
|
|
|
4798
|
|
|
|
|
|
|
When enabled and the root hasn’t been compiled yet, this triggers an initial compilation. |
|
4799
|
|
|
|
|
|
|
|
|
4800
|
|
|
|
|
|
|
Returns the current object to enable chaining. |
|
4801
|
|
|
|
|
|
|
|
|
4802
|
|
|
|
|
|
|
=head2 compile_js |
|
4803
|
|
|
|
|
|
|
|
|
4804
|
|
|
|
|
|
|
my $js_source = $js->compile_js; |
|
4805
|
|
|
|
|
|
|
my $js_source = $js->compile_js( ecma => 2018 ); |
|
4806
|
|
|
|
|
|
|
|
|
4807
|
|
|
|
|
|
|
Generate a standalone JavaScript validator for the current schema and return it as a UTF-8 string. |
|
4808
|
|
|
|
|
|
|
|
|
4809
|
|
|
|
|
|
|
You are responsible for writing this string to a C<.js> file and serving it to the browser (or embedding it in a page). |
|
4810
|
|
|
|
|
|
|
|
|
4811
|
|
|
|
|
|
|
The generated code: |
|
4812
|
|
|
|
|
|
|
|
|
4813
|
|
|
|
|
|
|
=over 4 |
|
4814
|
|
|
|
|
|
|
|
|
4815
|
|
|
|
|
|
|
=item * |
|
4816
|
|
|
|
|
|
|
|
|
4817
|
|
|
|
|
|
|
Wraps everything in a simple IIFE (Immediately Invoked Function Expression) C<(function(global){ ... })(this)>. |
|
4818
|
|
|
|
|
|
|
|
|
4819
|
|
|
|
|
|
|
=item * |
|
4820
|
|
|
|
|
|
|
|
|
4821
|
|
|
|
|
|
|
Defines a single public function: |
|
4822
|
|
|
|
|
|
|
|
|
4823
|
|
|
|
|
|
|
function validate(inst) { ... } |
|
4824
|
|
|
|
|
|
|
|
|
4825
|
|
|
|
|
|
|
exported on the global object (C<window.validate> in a browser). |
|
4826
|
|
|
|
|
|
|
|
|
4827
|
|
|
|
|
|
|
=item * |
|
4828
|
|
|
|
|
|
|
|
|
4829
|
|
|
|
|
|
|
Implements the same error reporting format as the Perl engine, but using plain JavaScript objects: |
|
4830
|
|
|
|
|
|
|
|
|
4831
|
|
|
|
|
|
|
{ |
|
4832
|
|
|
|
|
|
|
path: "#/path/in/instance", |
|
4833
|
|
|
|
|
|
|
keyword: "minimum", |
|
4834
|
|
|
|
|
|
|
message: "number is less than minimum 2", |
|
4835
|
|
|
|
|
|
|
schema_pointer: "#/definitions/.../minimum" |
|
4836
|
|
|
|
|
|
|
} |
|
4837
|
|
|
|
|
|
|
|
|
4838
|
|
|
|
|
|
|
=item * |
|
4839
|
|
|
|
|
|
|
|
|
4840
|
|
|
|
|
|
|
Returns an C<Array> of such error objects. If validation succeeds, the array is empty. |
|
4841
|
|
|
|
|
|
|
|
|
4842
|
|
|
|
|
|
|
=back |
|
4843
|
|
|
|
|
|
|
|
|
4844
|
|
|
|
|
|
|
Supported JavaScript options: |
|
4845
|
|
|
|
|
|
|
|
|
4846
|
|
|
|
|
|
|
=over 4 |
|
4847
|
|
|
|
|
|
|
|
|
4848
|
|
|
|
|
|
|
=item * C<ecma =E<gt> "auto" | YEAR> |
|
4849
|
|
|
|
|
|
|
|
|
4850
|
|
|
|
|
|
|
Controls which JavaScript regexp features the generated code will try to use. |
|
4851
|
|
|
|
|
|
|
|
|
4852
|
|
|
|
|
|
|
ecma => "auto" # default |
|
4853
|
|
|
|
|
|
|
ecma => 2018 # assume ES2018+ (Unicode property escapes, etc.) |
|
4854
|
|
|
|
|
|
|
|
|
4855
|
|
|
|
|
|
|
When C<ecma> is a number C<E<gt>= 2018>, patterns that use Unicode property escapes (e.g. C<\p{scx=Katakana}>) are compiled with the C</u> flag and will take advantage of Script / Script_Extensions support when the browser has it. |
|
4856
|
|
|
|
|
|
|
|
|
4857
|
|
|
|
|
|
|
In C<"auto"> mode the generator emits cautious compatibility shims: “advanced” patterns are wrapped in C<try/catch>; if the browser cannot compile them, those checks are silently skipped on the client (and are still enforced server-side by Perl). |
|
4858
|
|
|
|
|
|
|
|
|
4859
|
|
|
|
|
|
|
=item * C<max_errors =E<gt> 200> |
|
4860
|
|
|
|
|
|
|
|
|
4861
|
|
|
|
|
|
|
Defaults to 200. |
|
4862
|
|
|
|
|
|
|
|
|
4863
|
|
|
|
|
|
|
Set the maximum number of errors to be recorded. |
|
4864
|
|
|
|
|
|
|
|
|
4865
|
|
|
|
|
|
|
=item * C<name =E<gt> "myValidator"> |
|
4866
|
|
|
|
|
|
|
|
|
4867
|
|
|
|
|
|
|
Defaults to C<validate> |
|
4868
|
|
|
|
|
|
|
|
|
4869
|
|
|
|
|
|
|
Sets a custom name for the JavaScript validation function. |
|
4870
|
|
|
|
|
|
|
|
|
4871
|
|
|
|
|
|
|
=back |
|
4872
|
|
|
|
|
|
|
|
|
4873
|
|
|
|
|
|
|
=head3 JavaScript keyword coverage |
|
4874
|
|
|
|
|
|
|
|
|
4875
|
|
|
|
|
|
|
The generated JS implements a pragmatic subset of the Perl engine: |
|
4876
|
|
|
|
|
|
|
|
|
4877
|
|
|
|
|
|
|
=over 4 |
|
4878
|
|
|
|
|
|
|
|
|
4879
|
|
|
|
|
|
|
=item * Types |
|
4880
|
|
|
|
|
|
|
|
|
4881
|
|
|
|
|
|
|
C<type> (including unions). |
|
4882
|
|
|
|
|
|
|
|
|
4883
|
|
|
|
|
|
|
=item * Constants / enumerations |
|
4884
|
|
|
|
|
|
|
|
|
4885
|
|
|
|
|
|
|
C<const> (primitive values only) and C<enum>. |
|
4886
|
|
|
|
|
|
|
|
|
4887
|
|
|
|
|
|
|
Complex object/array C<const> values are currently ignored client-side and enforced server-side only. |
|
4888
|
|
|
|
|
|
|
|
|
4889
|
|
|
|
|
|
|
=item * Numbers |
|
4890
|
|
|
|
|
|
|
|
|
4891
|
|
|
|
|
|
|
C<minimum>, C<maximum>, C<exclusiveMinimum>, C<exclusiveMaximum>. |
|
4892
|
|
|
|
|
|
|
|
|
4893
|
|
|
|
|
|
|
For better UX, numeric-looking strings such as C<"10"> or C<"3.14"> are coerced to numbers before applying bounds. Non-numeric values: |
|
4894
|
|
|
|
|
|
|
|
|
4895
|
|
|
|
|
|
|
=over 4 |
|
4896
|
|
|
|
|
|
|
|
|
4897
|
|
|
|
|
|
|
=item * |
|
4898
|
|
|
|
|
|
|
|
|
4899
|
|
|
|
|
|
|
trigger a C<type> error (C<"expected number but found string">) when numeric keywords are present and no explicit C<type> is declared; |
|
4900
|
|
|
|
|
|
|
|
|
4901
|
|
|
|
|
|
|
=item * |
|
4902
|
|
|
|
|
|
|
|
|
4903
|
|
|
|
|
|
|
or are handled by the normal C<type> keyword if you explicitly declared C<< type => 'number' >> in the schema. |
|
4904
|
|
|
|
|
|
|
|
|
4905
|
|
|
|
|
|
|
=back |
|
4906
|
|
|
|
|
|
|
|
|
4907
|
|
|
|
|
|
|
=item * Strings |
|
4908
|
|
|
|
|
|
|
|
|
4909
|
|
|
|
|
|
|
C<minLength>, C<maxLength>, C<pattern>. |
|
4910
|
|
|
|
|
|
|
|
|
4911
|
|
|
|
|
|
|
Patterns are converted from Perl syntax to JavaScript using a conservative converter (e.g. C<\x{FF70}> to C<\uFF70>, C<\p{Katakana}> to C<\p{scx=Katakana}>). When the browser does not support the necessary Unicode features, such patterns are skipped client-side. |
|
4912
|
|
|
|
|
|
|
|
|
4913
|
|
|
|
|
|
|
=item * Arrays |
|
4914
|
|
|
|
|
|
|
|
|
4915
|
|
|
|
|
|
|
C<items> (single-schema form), C<minItems>, C<maxItems>, C<contains>, C<minContains>, C<maxContains>. |
|
4916
|
|
|
|
|
|
|
|
|
4917
|
|
|
|
|
|
|
=item * Objects |
|
4918
|
|
|
|
|
|
|
|
|
4919
|
|
|
|
|
|
|
C<properties>, C<required>. |
|
4920
|
|
|
|
|
|
|
|
|
4921
|
|
|
|
|
|
|
=item * Combinators |
|
4922
|
|
|
|
|
|
|
|
|
4923
|
|
|
|
|
|
|
C<allOf>, C<anyOf>, C<oneOf>, C<not>. |
|
4924
|
|
|
|
|
|
|
|
|
4925
|
|
|
|
|
|
|
“Negative required” patterns of the form C<< { "not": { "required": [...] } } >> are intentionally skipped on the client and enforced server-side only. |
|
4926
|
|
|
|
|
|
|
|
|
4927
|
|
|
|
|
|
|
=item * Conditionals |
|
4928
|
|
|
|
|
|
|
|
|
4929
|
|
|
|
|
|
|
C<if>, C<then>, C<else>, with the same semantics as the Perl engine: C<if> is evaluated in a “shadow” context and never produces errors directly; only C<then>/C<else> do. |
|
4930
|
|
|
|
|
|
|
|
|
4931
|
|
|
|
|
|
|
=item * Non-core extension |
|
4932
|
|
|
|
|
|
|
|
|
4933
|
|
|
|
|
|
|
C<uniqueKeys> when you enabled it via C<< unique_keys => 1 >> or C<< ->unique_keys >>. |
|
4934
|
|
|
|
|
|
|
|
|
4935
|
|
|
|
|
|
|
=back |
|
4936
|
|
|
|
|
|
|
|
|
4937
|
|
|
|
|
|
|
The following are intentionally B<not> implemented in JavaScript (but are fully supported in Perl): |
|
4938
|
|
|
|
|
|
|
|
|
4939
|
|
|
|
|
|
|
=over 4 |
|
4940
|
|
|
|
|
|
|
|
|
4941
|
|
|
|
|
|
|
=item * |
|
4942
|
|
|
|
|
|
|
|
|
4943
|
|
|
|
|
|
|
C<format> (client-side format checks are skipped). |
|
4944
|
|
|
|
|
|
|
|
|
4945
|
|
|
|
|
|
|
=item * |
|
4946
|
|
|
|
|
|
|
|
|
4947
|
|
|
|
|
|
|
C<prefixItems>, C<patternProperties>, C<unevaluatedItems>, C<unevaluatedProperties>, C<contentEncoding>, C<contentMediaType>, C<contentSchema>, external C<$ref> and C<$dynamicRef> targets. |
|
4948
|
|
|
|
|
|
|
|
|
4949
|
|
|
|
|
|
|
=back |
|
4950
|
|
|
|
|
|
|
|
|
4951
|
|
|
|
|
|
|
In other words: the JS validator is a fast, user-friendly I<pre-flight> check for web forms; the Perl validator remains the source of truth. |
|
4952
|
|
|
|
|
|
|
|
|
4953
|
|
|
|
|
|
|
=head3 Example: integrating the generated JS in a form |
|
4954
|
|
|
|
|
|
|
|
|
4955
|
|
|
|
|
|
|
Perl side: |
|
4956
|
|
|
|
|
|
|
|
|
4957
|
|
|
|
|
|
|
my $schema = ...; # your decoded schema |
|
4958
|
|
|
|
|
|
|
|
|
4959
|
|
|
|
|
|
|
my $validajstor = JSON::Schema::Validate->new( $schema ) |
|
4960
|
|
|
|
|
|
|
->compile; |
|
4961
|
|
|
|
|
|
|
|
|
4962
|
|
|
|
|
|
|
my $js_source = $validator->compile_js( ecma => 2018 ); |
|
4963
|
|
|
|
|
|
|
|
|
4964
|
|
|
|
|
|
|
open my $fh, '>:encoding(UTF-8)', 'htdocs/js/validator.js' |
|
4965
|
|
|
|
|
|
|
or die( "Cannot write JS: $!" ); |
|
4966
|
|
|
|
|
|
|
print {$fh} $js_source; |
|
4967
|
|
|
|
|
|
|
close $fh; |
|
4968
|
|
|
|
|
|
|
|
|
4969
|
|
|
|
|
|
|
HTML / JavaScript: |
|
4970
|
|
|
|
|
|
|
|
|
4971
|
|
|
|
|
|
|
<textarea id="company-data" rows="8" cols="80"> |
|
4972
|
|
|
|
|
|
|
{ "name_ja": "株式会社テスト", "capital": 1 } |
|
4973
|
|
|
|
|
|
|
</textarea> |
|
4974
|
|
|
|
|
|
|
|
|
4975
|
|
|
|
|
|
|
<button type="button" onclick="runValidation()">Validate</button> |
|
4976
|
|
|
|
|
|
|
|
|
4977
|
|
|
|
|
|
|
<pre id="validation-errors"></pre> |
|
4978
|
|
|
|
|
|
|
|
|
4979
|
|
|
|
|
|
|
<script src="/js/validator.js"></script> |
|
4980
|
|
|
|
|
|
|
<script> |
|
4981
|
|
|
|
|
|
|
function runValidation() |
|
4982
|
|
|
|
|
|
|
{ |
|
4983
|
|
|
|
|
|
|
var src = document.getElementById('company-data').value; |
|
4984
|
|
|
|
|
|
|
var out = document.getElementById('validation-errors'); |
|
4985
|
|
|
|
|
|
|
var inst; |
|
4986
|
|
|
|
|
|
|
|
|
4987
|
|
|
|
|
|
|
try |
|
4988
|
|
|
|
|
|
|
{ |
|
4989
|
|
|
|
|
|
|
inst = JSON.parse( src ); |
|
4990
|
|
|
|
|
|
|
} |
|
4991
|
|
|
|
|
|
|
catch( e ) |
|
4992
|
|
|
|
|
|
|
{ |
|
4993
|
|
|
|
|
|
|
out.textContent = "Invalid JSON: " + e; |
|
4994
|
|
|
|
|
|
|
return; |
|
4995
|
|
|
|
|
|
|
} |
|
4996
|
|
|
|
|
|
|
|
|
4997
|
|
|
|
|
|
|
var errors = validate( inst ); // defined by validator.js |
|
4998
|
|
|
|
|
|
|
|
|
4999
|
|
|
|
|
|
|
if( !errors || !errors.length ) |
|
5000
|
|
|
|
|
|
|
{ |
|
5001
|
|
|
|
|
|
|
out.textContent = "OK – no client-side schema errors."; |
|
5002
|
|
|
|
|
|
|
return; |
|
5003
|
|
|
|
|
|
|
} |
|
5004
|
|
|
|
|
|
|
|
|
5005
|
|
|
|
|
|
|
var lines = []; |
|
5006
|
|
|
|
|
|
|
for( var i = 0; i < errors.length; i++ ) |
|
5007
|
|
|
|
|
|
|
{ |
|
5008
|
|
|
|
|
|
|
var e = errors[i]; |
|
5009
|
|
|
|
|
|
|
lines.push( |
|
5010
|
|
|
|
|
|
|
"- " + e.path + |
|
5011
|
|
|
|
|
|
|
" [" + e.keyword + "]: " + |
|
5012
|
|
|
|
|
|
|
e.message |
|
5013
|
|
|
|
|
|
|
); |
|
5014
|
|
|
|
|
|
|
} |
|
5015
|
|
|
|
|
|
|
out.textContent = lines.join("\n"); |
|
5016
|
|
|
|
|
|
|
} |
|
5017
|
|
|
|
|
|
|
</script> |
|
5018
|
|
|
|
|
|
|
|
|
5019
|
|
|
|
|
|
|
You can then map each error back to specific fields, translate C<message> via your own localisation layer, or forward the C<errors> array to your logging pipeline. |
|
5020
|
|
|
|
|
|
|
|
|
5021
|
|
|
|
|
|
|
=head2 content_checks |
|
5022
|
|
|
|
|
|
|
|
|
5023
|
|
|
|
|
|
|
$js->content_checks; # enable |
|
5024
|
|
|
|
|
|
|
$js->content_checks(1); # enable |
|
5025
|
|
|
|
|
|
|
$js->content_checks(0); # disable |
|
5026
|
|
|
|
|
|
|
|
|
5027
|
|
|
|
|
|
|
Turn on/off content assertions for the C<contentEncoding>, C<contentMediaType> and C<contentSchema> trio. |
|
5028
|
|
|
|
|
|
|
|
|
5029
|
|
|
|
|
|
|
When enabling, built-in media validators are registered (e.g. C<application/json>). |
|
5030
|
|
|
|
|
|
|
|
|
5031
|
|
|
|
|
|
|
Returns the current object to enable chaining. |
|
5032
|
|
|
|
|
|
|
|
|
5033
|
|
|
|
|
|
|
=for Pod::Coverage enable_content_checks |
|
5034
|
|
|
|
|
|
|
|
|
5035
|
|
|
|
|
|
|
=head2 error |
|
5036
|
|
|
|
|
|
|
|
|
5037
|
|
|
|
|
|
|
my $msg = $js->error; |
|
5038
|
|
|
|
|
|
|
|
|
5039
|
|
|
|
|
|
|
Returns the first error L<JSON::Schema::Validate::Error> object out of all the possible errors found (see L</errors>), if any. |
|
5040
|
|
|
|
|
|
|
|
|
5041
|
|
|
|
|
|
|
When stringified, the object provides a short, human-oriented message for the first failure. |
|
5042
|
|
|
|
|
|
|
|
|
5043
|
|
|
|
|
|
|
=head2 errors |
|
5044
|
|
|
|
|
|
|
|
|
5045
|
|
|
|
|
|
|
my $array_ref = $js->errors; |
|
5046
|
|
|
|
|
|
|
|
|
5047
|
|
|
|
|
|
|
All collected L<error objects|JSON::Schema::Validate::Error> (up to the internal C<max_errors> cap). |
|
5048
|
|
|
|
|
|
|
|
|
5049
|
|
|
|
|
|
|
=head2 extensions |
|
5050
|
|
|
|
|
|
|
|
|
5051
|
|
|
|
|
|
|
$js->extensions; # enable all extensions |
|
5052
|
|
|
|
|
|
|
$js->extensions(1); # enable |
|
5053
|
|
|
|
|
|
|
$js->extensions(0); # disable |
|
5054
|
|
|
|
|
|
|
|
|
5055
|
|
|
|
|
|
|
Turn the extension framework on or off. |
|
5056
|
|
|
|
|
|
|
|
|
5057
|
|
|
|
|
|
|
Enabling extensions currently activates the C<uniqueKeys> applicator (and any future non-core features). Disabling it turns all extensions off, regardless of individual settings. |
|
5058
|
|
|
|
|
|
|
|
|
5059
|
|
|
|
|
|
|
Returns the object for method chaining. |
|
5060
|
|
|
|
|
|
|
|
|
5061
|
|
|
|
|
|
|
=head2 get_trace |
|
5062
|
|
|
|
|
|
|
|
|
5063
|
|
|
|
|
|
|
my $trace = $js->get_trace; # arrayref of trace entries (copy) |
|
5064
|
|
|
|
|
|
|
|
|
5065
|
|
|
|
|
|
|
Return a B<copy> of the last validation trace (array reference of hash references) so callers cannot mutate internal state. Each entry contains: |
|
5066
|
|
|
|
|
|
|
|
|
5067
|
|
|
|
|
|
|
{ |
|
5068
|
|
|
|
|
|
|
inst_path => '#/path/in/instance', |
|
5069
|
|
|
|
|
|
|
keyword => 'node' | 'minimum' | ..., |
|
5070
|
|
|
|
|
|
|
note => 'short string', |
|
5071
|
|
|
|
|
|
|
outcome => 'pass' | 'fail' | 'visit' | 'start', |
|
5072
|
|
|
|
|
|
|
schema_ptr => '#/path/in/schema', |
|
5073
|
|
|
|
|
|
|
} |
|
5074
|
|
|
|
|
|
|
|
|
5075
|
|
|
|
|
|
|
=head2 get_trace_limit |
|
5076
|
|
|
|
|
|
|
|
|
5077
|
|
|
|
|
|
|
my $n = $js->get_trace_limit; |
|
5078
|
|
|
|
|
|
|
|
|
5079
|
|
|
|
|
|
|
Accessor that returns the numeric trace limit currently in effect. See L</trace_limit> to set it. |
|
5080
|
|
|
|
|
|
|
|
|
5081
|
|
|
|
|
|
|
=head2 ignore_unknown_required_vocab |
|
5082
|
|
|
|
|
|
|
|
|
5083
|
|
|
|
|
|
|
$js->ignore_unknown_required_vocab; # enable |
|
5084
|
|
|
|
|
|
|
$js->ignore_unknown_required_vocab(1); # enable |
|
5085
|
|
|
|
|
|
|
$js->ignore_unknown_required_vocab(0); # disable |
|
5086
|
|
|
|
|
|
|
|
|
5087
|
|
|
|
|
|
|
If enabled, required vocabularies declared in C<$vocabulary> that are not advertised as supported by the caller will be I<ignored> instead of causing the validator to C<die>. |
|
5088
|
|
|
|
|
|
|
|
|
5089
|
|
|
|
|
|
|
Returns the current object to enable chaining. |
|
5090
|
|
|
|
|
|
|
|
|
5091
|
|
|
|
|
|
|
=head2 is_compile_enabled |
|
5092
|
|
|
|
|
|
|
|
|
5093
|
|
|
|
|
|
|
my $bool = $js->is_compile_enabled; |
|
5094
|
|
|
|
|
|
|
|
|
5095
|
|
|
|
|
|
|
Read-only accessor. |
|
5096
|
|
|
|
|
|
|
|
|
5097
|
|
|
|
|
|
|
Returns true if compilation mode is enabled, false otherwise. |
|
5098
|
|
|
|
|
|
|
|
|
5099
|
|
|
|
|
|
|
=head2 is_content_checks_enabled |
|
5100
|
|
|
|
|
|
|
|
|
5101
|
|
|
|
|
|
|
my $bool = $js->is_content_checks_enabled; |
|
5102
|
|
|
|
|
|
|
|
|
5103
|
|
|
|
|
|
|
Read-only accessor. |
|
5104
|
|
|
|
|
|
|
|
|
5105
|
|
|
|
|
|
|
Returns true if content assertions are enabled, false otherwise. |
|
5106
|
|
|
|
|
|
|
|
|
5107
|
|
|
|
|
|
|
=head2 is_trace_on |
|
5108
|
|
|
|
|
|
|
|
|
5109
|
|
|
|
|
|
|
my $bool = $js->is_trace_on; |
|
5110
|
|
|
|
|
|
|
|
|
5111
|
|
|
|
|
|
|
Read-only accessor. |
|
5112
|
|
|
|
|
|
|
|
|
5113
|
|
|
|
|
|
|
Returns true if tracing is enabled, false otherwise. |
|
5114
|
|
|
|
|
|
|
|
|
5115
|
|
|
|
|
|
|
=head2 is_unique_keys_enabled |
|
5116
|
|
|
|
|
|
|
|
|
5117
|
|
|
|
|
|
|
my $bool = $js->is_unique_keys_enabled; |
|
5118
|
|
|
|
|
|
|
|
|
5119
|
|
|
|
|
|
|
Read-only accessor. |
|
5120
|
|
|
|
|
|
|
|
|
5121
|
|
|
|
|
|
|
Returns true if the C<uniqueKeys> applicator is currently active, false otherwise. |
|
5122
|
|
|
|
|
|
|
|
|
5123
|
|
|
|
|
|
|
=head2 is_unknown_required_vocab_ignored |
|
5124
|
|
|
|
|
|
|
|
|
5125
|
|
|
|
|
|
|
my $bool = $js->is_unknown_required_vocab_ignored; |
|
5126
|
|
|
|
|
|
|
|
|
5127
|
|
|
|
|
|
|
Read-only accessor. |
|
5128
|
|
|
|
|
|
|
|
|
5129
|
|
|
|
|
|
|
Returns true if unknown required vocabularies are being ignored, false otherwise. |
|
5130
|
|
|
|
|
|
|
|
|
5131
|
|
|
|
|
|
|
=head2 is_valid |
|
5132
|
|
|
|
|
|
|
|
|
5133
|
|
|
|
|
|
|
my $ok = $js->is_valid( $data ); |
|
5134
|
|
|
|
|
|
|
|
|
5135
|
|
|
|
|
|
|
my $ok = $js->is_valid( |
|
5136
|
|
|
|
|
|
|
$data, |
|
5137
|
|
|
|
|
|
|
max_errors => 1, # default for is_valid |
|
5138
|
|
|
|
|
|
|
trace_on => 0, |
|
5139
|
|
|
|
|
|
|
trace_limit => 0, |
|
5140
|
|
|
|
|
|
|
compile_on => 0, |
|
5141
|
|
|
|
|
|
|
content_assert => 0, |
|
5142
|
|
|
|
|
|
|
); |
|
5143
|
|
|
|
|
|
|
|
|
5144
|
|
|
|
|
|
|
Validate C<$data> against the compiled schema and return a boolean. |
|
5145
|
|
|
|
|
|
|
|
|
5146
|
|
|
|
|
|
|
This is a convenience method intended for “yes/no” checks. It behaves like L</validate> but defaults to C<< max_errors => 1 >> so that, on failure, only one error is recorded. |
|
5147
|
|
|
|
|
|
|
|
|
5148
|
|
|
|
|
|
|
On failure, the single recorded error can be retrieved with L</error>: |
|
5149
|
|
|
|
|
|
|
|
|
5150
|
|
|
|
|
|
|
$js->is_valid( $data ) |
|
5151
|
|
|
|
|
|
|
or die( $js->error ); |
|
5152
|
|
|
|
|
|
|
|
|
5153
|
|
|
|
|
|
|
Per-call options are passed through to L</validate> and may override the instance configuration for this call only (e.g. C<max_errors>, C<trace_on>, C<trace_limit>, C<compile_on>, C<content_assert>). |
|
5154
|
|
|
|
|
|
|
|
|
5155
|
|
|
|
|
|
|
Returns 1 on success, 0 on failure. |
|
5156
|
|
|
|
|
|
|
|
|
5157
|
|
|
|
|
|
|
=head2 prune_instance |
|
5158
|
|
|
|
|
|
|
|
|
5159
|
|
|
|
|
|
|
my $pruned = $jsv->prune_instance( $instance ); |
|
5160
|
|
|
|
|
|
|
|
|
5161
|
|
|
|
|
|
|
Returns a pruned copy of C<$instance> according to the schema that was passed to C<new>. The original data structure is B<not> modified. |
|
5162
|
|
|
|
|
|
|
|
|
5163
|
|
|
|
|
|
|
The pruning rules are the same as those used when the constructor option C<prune_unknown> is enabled (see L</prune_unknown>), namely: |
|
5164
|
|
|
|
|
|
|
|
|
5165
|
|
|
|
|
|
|
=over 4 |
|
5166
|
|
|
|
|
|
|
|
|
5167
|
|
|
|
|
|
|
=item * |
|
5168
|
|
|
|
|
|
|
|
|
5169
|
|
|
|
|
|
|
For objects, only properties allowed by C<properties>, C<patternProperties> and C<additionalProperties> (including those brought in via C<allOf>) are kept. Their values are recursively pruned when they are objects or arrays. |
|
5170
|
|
|
|
|
|
|
|
|
5171
|
|
|
|
|
|
|
=item * |
|
5172
|
|
|
|
|
|
|
|
|
5173
|
|
|
|
|
|
|
If C<additionalProperties> is C<false>, properties not matched by C<properties> or C<patternProperties> are removed. |
|
5174
|
|
|
|
|
|
|
|
|
5175
|
|
|
|
|
|
|
=item * |
|
5176
|
|
|
|
|
|
|
|
|
5177
|
|
|
|
|
|
|
If C<additionalProperties> is a subschema, additional properties are kept and pruned recursively according to that subschema. |
|
5178
|
|
|
|
|
|
|
|
|
5179
|
|
|
|
|
|
|
=item * |
|
5180
|
|
|
|
|
|
|
|
|
5181
|
|
|
|
|
|
|
For arrays, items are never removed by index. However, for elements covered by C<prefixItems> or C<items>, their nested content is pruned recursively when it is an object or array. |
|
5182
|
|
|
|
|
|
|
|
|
5183
|
|
|
|
|
|
|
=item * |
|
5184
|
|
|
|
|
|
|
|
|
5185
|
|
|
|
|
|
|
C<anyOf>, C<oneOf> and C<not> are B<not> used to decide which properties to drop, to avoid over-pruning valid data without performing full validation. |
|
5186
|
|
|
|
|
|
|
|
|
5187
|
|
|
|
|
|
|
=back |
|
5188
|
|
|
|
|
|
|
|
|
5189
|
|
|
|
|
|
|
This method is useful when you want to clean incoming data structures before further processing, without necessarily performing a full schema validation at the same time. |
|
5190
|
|
|
|
|
|
|
|
|
5191
|
|
|
|
|
|
|
=head2 register_builtin_formats |
|
5192
|
|
|
|
|
|
|
|
|
5193
|
|
|
|
|
|
|
$js->register_builtin_formats; |
|
5194
|
|
|
|
|
|
|
|
|
5195
|
|
|
|
|
|
|
Registers the built-in validators listed in L</Formats>. Existing user-supplied format callbacks are preserved if they already exist under the same name. |
|
5196
|
|
|
|
|
|
|
|
|
5197
|
|
|
|
|
|
|
User-supplied callbacks passed via C<< format => { ... } >> are preserved and take precedence. |
|
5198
|
|
|
|
|
|
|
|
|
5199
|
|
|
|
|
|
|
=head2 register_content_decoder |
|
5200
|
|
|
|
|
|
|
|
|
5201
|
|
|
|
|
|
|
$js->register_content_decoder( $name => sub{ ... } ); |
|
5202
|
|
|
|
|
|
|
|
|
5203
|
|
|
|
|
|
|
or |
|
5204
|
|
|
|
|
|
|
|
|
5205
|
|
|
|
|
|
|
$js->register_content_decoder(rot13 => sub |
|
5206
|
|
|
|
|
|
|
{ |
|
5207
|
|
|
|
|
|
|
$bytes =~ tr/A-Za-z/N-ZA-Mn-za-m/; |
|
5208
|
|
|
|
|
|
|
return( $bytes ); # now treated as (1, undef, $decoded) |
|
5209
|
|
|
|
|
|
|
}); |
|
5210
|
|
|
|
|
|
|
|
|
5211
|
|
|
|
|
|
|
Register a content B<decoder> for C<contentEncoding>. The callback receives a single argument: the raw data, and should return one of: |
|
5212
|
|
|
|
|
|
|
|
|
5213
|
|
|
|
|
|
|
=over 4 |
|
5214
|
|
|
|
|
|
|
|
|
5215
|
|
|
|
|
|
|
=item * a decoded scalar (success); |
|
5216
|
|
|
|
|
|
|
|
|
5217
|
|
|
|
|
|
|
=item * C<undef> (failure); |
|
5218
|
|
|
|
|
|
|
|
|
5219
|
|
|
|
|
|
|
=item * or the triplet C<( $ok, $msg, $out )> where C<$ok> is truthy on success, C<$msg> is an optional error string, and C<$out> is the decoded value. |
|
5220
|
|
|
|
|
|
|
|
|
5221
|
|
|
|
|
|
|
=back |
|
5222
|
|
|
|
|
|
|
|
|
5223
|
|
|
|
|
|
|
The C<$name> is lower-cased internally. Returns the current object. |
|
5224
|
|
|
|
|
|
|
|
|
5225
|
|
|
|
|
|
|
Throws an exception if the second argument is not a code reference. |
|
5226
|
|
|
|
|
|
|
|
|
5227
|
|
|
|
|
|
|
=head2 register_format |
|
5228
|
|
|
|
|
|
|
|
|
5229
|
|
|
|
|
|
|
$js->register_format( $name, sub { ... } ); |
|
5230
|
|
|
|
|
|
|
|
|
5231
|
|
|
|
|
|
|
Register or override a C<format> validator at runtime. The sub receives a single scalar (the candidate string) and must return true/false. |
|
5232
|
|
|
|
|
|
|
|
|
5233
|
|
|
|
|
|
|
=head2 register_media_validator |
|
5234
|
|
|
|
|
|
|
|
|
5235
|
|
|
|
|
|
|
$js->register_media_validator( 'application/json' => sub{ ... } ); |
|
5236
|
|
|
|
|
|
|
|
|
5237
|
|
|
|
|
|
|
Register a media B<validator/decoder> for C<contentMediaType>. The callback receives 2 arguments: |
|
5238
|
|
|
|
|
|
|
|
|
5239
|
|
|
|
|
|
|
=over 4 |
|
5240
|
|
|
|
|
|
|
|
|
5241
|
|
|
|
|
|
|
=item * C<$bytes> |
|
5242
|
|
|
|
|
|
|
|
|
5243
|
|
|
|
|
|
|
The data to validate |
|
5244
|
|
|
|
|
|
|
|
|
5245
|
|
|
|
|
|
|
=item * C<\%params> |
|
5246
|
|
|
|
|
|
|
|
|
5247
|
|
|
|
|
|
|
A hash reference of media-type parameters (e.g. C<charset>). |
|
5248
|
|
|
|
|
|
|
|
|
5249
|
|
|
|
|
|
|
=back |
|
5250
|
|
|
|
|
|
|
|
|
5251
|
|
|
|
|
|
|
It may return one of: |
|
5252
|
|
|
|
|
|
|
|
|
5253
|
|
|
|
|
|
|
=over 4 |
|
5254
|
|
|
|
|
|
|
|
|
5255
|
|
|
|
|
|
|
=item * C<( $ok, $msg, $decoded )> — canonical form. On success C<$ok> is true, C<$msg> is optional, and C<$decoded> can be either a Perl structure or a new octet/string value. |
|
5256
|
|
|
|
|
|
|
|
|
5257
|
|
|
|
|
|
|
=item * a reference — treated as success with that reference as C<$decoded>. |
|
5258
|
|
|
|
|
|
|
|
|
5259
|
|
|
|
|
|
|
=item * a defined scalar — treated as success with that scalar as C<$decoded>. |
|
5260
|
|
|
|
|
|
|
|
|
5261
|
|
|
|
|
|
|
=item * C<undef> or empty list — treated as failure. |
|
5262
|
|
|
|
|
|
|
|
|
5263
|
|
|
|
|
|
|
=back |
|
5264
|
|
|
|
|
|
|
|
|
5265
|
|
|
|
|
|
|
The media type key is lower-cased internally. |
|
5266
|
|
|
|
|
|
|
|
|
5267
|
|
|
|
|
|
|
It returns the current object. |
|
5268
|
|
|
|
|
|
|
|
|
5269
|
|
|
|
|
|
|
It throws an exception if the second argument is not a code reference. |
|
5270
|
|
|
|
|
|
|
|
|
5271
|
|
|
|
|
|
|
=head2 set_comment_handler |
|
5272
|
|
|
|
|
|
|
|
|
5273
|
|
|
|
|
|
|
$js->set_comment_handler(sub |
|
5274
|
|
|
|
|
|
|
{ |
|
5275
|
|
|
|
|
|
|
my( $schema_ptr, $text ) = @_; |
|
5276
|
|
|
|
|
|
|
warn "Comment at $schema_ptr: $text\n"; |
|
5277
|
|
|
|
|
|
|
}); |
|
5278
|
|
|
|
|
|
|
|
|
5279
|
|
|
|
|
|
|
Install an optional callback for the Draft 2020-12 C<$comment> keyword. |
|
5280
|
|
|
|
|
|
|
|
|
5281
|
|
|
|
|
|
|
C<$comment> is annotation-only (never affects validation). When provided, the callback is invoked once per encountered C<$comment> string with the schema pointer and the comment text. Callback errors are ignored. |
|
5282
|
|
|
|
|
|
|
|
|
5283
|
|
|
|
|
|
|
If a value is provided, and is not a code reference, a warning will be emitted. |
|
5284
|
|
|
|
|
|
|
|
|
5285
|
|
|
|
|
|
|
This returns the current object. |
|
5286
|
|
|
|
|
|
|
|
|
5287
|
|
|
|
|
|
|
=head2 set_resolver |
|
5288
|
|
|
|
|
|
|
|
|
5289
|
|
|
|
|
|
|
$js->set_resolver( sub{ my( $absolute_uri ) = @_; ...; return $schema_hashref } ); |
|
5290
|
|
|
|
|
|
|
|
|
5291
|
|
|
|
|
|
|
Install a resolver for external documents. It is called with an absolute URI (formed from the current base C<$id> and the C<$ref>) and must return a Perl hash reference representation of a JSON Schema. If the returned hash contains C<'$id'>, it will become the new base for that document; otherwise, the absolute URI is used as its base. |
|
5292
|
|
|
|
|
|
|
|
|
5293
|
|
|
|
|
|
|
=head2 set_vocabulary_support |
|
5294
|
|
|
|
|
|
|
|
|
5295
|
|
|
|
|
|
|
$js->set_vocabulary_support( \%support ); |
|
5296
|
|
|
|
|
|
|
|
|
5297
|
|
|
|
|
|
|
Declare which vocabularies the host supports, as a hash reference: |
|
5298
|
|
|
|
|
|
|
|
|
5299
|
|
|
|
|
|
|
{ |
|
5300
|
|
|
|
|
|
|
'https://example/vocab/core' => 1, |
|
5301
|
|
|
|
|
|
|
... |
|
5302
|
|
|
|
|
|
|
} |
|
5303
|
|
|
|
|
|
|
|
|
5304
|
|
|
|
|
|
|
Resets internal vocabulary-checked state so the declaration is enforced on next C<validate>. |
|
5305
|
|
|
|
|
|
|
|
|
5306
|
|
|
|
|
|
|
By default, this module supports all vocabularies required by 2020-12. |
|
5307
|
|
|
|
|
|
|
|
|
5308
|
|
|
|
|
|
|
However, you can restrict support: |
|
5309
|
|
|
|
|
|
|
|
|
5310
|
|
|
|
|
|
|
$js->set_vocabulary_support({ |
|
5311
|
|
|
|
|
|
|
'https://json-schema.org/draft/2020-12/vocab/core' => 1, |
|
5312
|
|
|
|
|
|
|
'https://json-schema.org/draft/2020-12/vocab/applicator' => 1, |
|
5313
|
|
|
|
|
|
|
'https://json-schema.org/draft/2020-12/vocab/format' => 0, |
|
5314
|
|
|
|
|
|
|
'https://mycorp/vocab/internal' => 1, |
|
5315
|
|
|
|
|
|
|
}); |
|
5316
|
|
|
|
|
|
|
|
|
5317
|
|
|
|
|
|
|
It returns the current object. |
|
5318
|
|
|
|
|
|
|
|
|
5319
|
|
|
|
|
|
|
=head2 trace |
|
5320
|
|
|
|
|
|
|
|
|
5321
|
|
|
|
|
|
|
$js->trace; # enable |
|
5322
|
|
|
|
|
|
|
$js->trace(1); # enable |
|
5323
|
|
|
|
|
|
|
$js->trace(0); # disable |
|
5324
|
|
|
|
|
|
|
|
|
5325
|
|
|
|
|
|
|
Enable or disable tracing. When enabled, the validator records lightweight, bounded trace events according to L</trace_limit> and L</trace_sample>. |
|
5326
|
|
|
|
|
|
|
|
|
5327
|
|
|
|
|
|
|
It returns the current object for chaining. |
|
5328
|
|
|
|
|
|
|
|
|
5329
|
|
|
|
|
|
|
=head2 trace_limit |
|
5330
|
|
|
|
|
|
|
|
|
5331
|
|
|
|
|
|
|
$js->trace_limit( $n ); |
|
5332
|
|
|
|
|
|
|
|
|
5333
|
|
|
|
|
|
|
Set a hard cap on the number of trace entries recorded during a single C<validate> call (C<0> = unlimited). |
|
5334
|
|
|
|
|
|
|
|
|
5335
|
|
|
|
|
|
|
It returns the current object for chaining. |
|
5336
|
|
|
|
|
|
|
|
|
5337
|
|
|
|
|
|
|
=head2 trace_sample |
|
5338
|
|
|
|
|
|
|
|
|
5339
|
|
|
|
|
|
|
$js->trace_sample( $percent ); |
|
5340
|
|
|
|
|
|
|
|
|
5341
|
|
|
|
|
|
|
Enable probabilistic sampling of trace events. C<$percent> is an integer percentage in C<[0,100]>. C<0> disables sampling. Sampling occurs per-event, and still respects L</trace_limit>. |
|
5342
|
|
|
|
|
|
|
|
|
5343
|
|
|
|
|
|
|
It returns the current object for chaining. |
|
5344
|
|
|
|
|
|
|
|
|
5345
|
|
|
|
|
|
|
=head2 unique_keys |
|
5346
|
|
|
|
|
|
|
|
|
5347
|
|
|
|
|
|
|
$js->unique_keys; # enable uniqueKeys |
|
5348
|
|
|
|
|
|
|
$js->unique_keys(1); # enable |
|
5349
|
|
|
|
|
|
|
$js->unique_keys(0); # disable |
|
5350
|
|
|
|
|
|
|
|
|
5351
|
|
|
|
|
|
|
Enable or disable the C<uniqueKeys> applicator independently of the C<extensions> option. |
|
5352
|
|
|
|
|
|
|
|
|
5353
|
|
|
|
|
|
|
When disabled (the default), schemas containing the C<uniqueKeys> keyword are ignored. |
|
5354
|
|
|
|
|
|
|
|
|
5355
|
|
|
|
|
|
|
Returns the object for method chaining. |
|
5356
|
|
|
|
|
|
|
|
|
5357
|
|
|
|
|
|
|
=head2 validate |
|
5358
|
|
|
|
|
|
|
|
|
5359
|
|
|
|
|
|
|
my $ok = $js->validate( $data ); |
|
5360
|
|
|
|
|
|
|
|
|
5361
|
|
|
|
|
|
|
my $ok = $js->validate( |
|
5362
|
|
|
|
|
|
|
$data, |
|
5363
|
|
|
|
|
|
|
max_errors => 5, |
|
5364
|
|
|
|
|
|
|
trace_on => 1, |
|
5365
|
|
|
|
|
|
|
trace_limit => 200, |
|
5366
|
|
|
|
|
|
|
compile_on => 0, |
|
5367
|
|
|
|
|
|
|
content_assert => 1, |
|
5368
|
|
|
|
|
|
|
); |
|
5369
|
|
|
|
|
|
|
|
|
5370
|
|
|
|
|
|
|
Validate a decoded JSON instance against the compiled schema and return a boolean. |
|
5371
|
|
|
|
|
|
|
|
|
5372
|
|
|
|
|
|
|
On failure, inspect C<< $js->error >> to retrieve the L<error object|JSON::Schema::Validate::Error> that stringifies for a concise message (first error), or C<< $js->errors >> for an array reference of L<error objects|JSON::Schema::Validate::Error>. |
|
5373
|
|
|
|
|
|
|
|
|
5374
|
|
|
|
|
|
|
Example: |
|
5375
|
|
|
|
|
|
|
|
|
5376
|
|
|
|
|
|
|
my $ok = $js->validate( $data ) or die( $js->error ); |
|
5377
|
|
|
|
|
|
|
|
|
5378
|
|
|
|
|
|
|
Each error is a L<JSON::Schema::Validate::Error> object: |
|
5379
|
|
|
|
|
|
|
|
|
5380
|
|
|
|
|
|
|
my $err = $js->error; |
|
5381
|
|
|
|
|
|
|
say $err->path; # #/properties~1name |
|
5382
|
|
|
|
|
|
|
say $err->schema_pointer; # #/properties/name |
|
5383
|
|
|
|
|
|
|
say $err->keyword; # minLength |
|
5384
|
|
|
|
|
|
|
say $err->message; # string shorter than minLength 1 |
|
5385
|
|
|
|
|
|
|
say "$err"; # stringifies to a concise message |
|
5386
|
|
|
|
|
|
|
|
|
5387
|
|
|
|
|
|
|
=head3 Per-call option overrides |
|
5388
|
|
|
|
|
|
|
|
|
5389
|
|
|
|
|
|
|
C<validate> accepts optional named parameters (hash or hash reference) that override the validator’s instance configuration for this call only. |
|
5390
|
|
|
|
|
|
|
|
|
5391
|
|
|
|
|
|
|
Currently supported overrides: |
|
5392
|
|
|
|
|
|
|
|
|
5393
|
|
|
|
|
|
|
=over 4 |
|
5394
|
|
|
|
|
|
|
|
|
5395
|
|
|
|
|
|
|
=item * C<max_errors> |
|
5396
|
|
|
|
|
|
|
|
|
5397
|
|
|
|
|
|
|
Maximum number of errors to collect before stopping validation. |
|
5398
|
|
|
|
|
|
|
|
|
5399
|
|
|
|
|
|
|
=item * C<trace_on>, C<trace_limit> |
|
5400
|
|
|
|
|
|
|
|
|
5401
|
|
|
|
|
|
|
Enable tracing and limit the number of trace entries. |
|
5402
|
|
|
|
|
|
|
|
|
5403
|
|
|
|
|
|
|
=item * C<compile_on> |
|
5404
|
|
|
|
|
|
|
|
|
5405
|
|
|
|
|
|
|
Enable on-the-fly compilation during validation. |
|
5406
|
|
|
|
|
|
|
|
|
5407
|
|
|
|
|
|
|
=item * C<content_assert> |
|
5408
|
|
|
|
|
|
|
|
|
5409
|
|
|
|
|
|
|
Enable media-type / content assertions. |
|
5410
|
|
|
|
|
|
|
|
|
5411
|
|
|
|
|
|
|
=back |
|
5412
|
|
|
|
|
|
|
|
|
5413
|
|
|
|
|
|
|
All options are optional and backward compatible. If omitted, the instance configuration is used. |
|
5414
|
|
|
|
|
|
|
|
|
5415
|
|
|
|
|
|
|
=head3 Relationship to C<is_valid> |
|
5416
|
|
|
|
|
|
|
|
|
5417
|
|
|
|
|
|
|
L</is_valid> is a convenience wrapper around C<validate> that defaults C<< max_errors => 1 >> and is intended for fast boolean checks: |
|
5418
|
|
|
|
|
|
|
|
|
5419
|
|
|
|
|
|
|
$js->is_valid( $data ) or die( $js->error ); |
|
5420
|
|
|
|
|
|
|
|
|
5421
|
|
|
|
|
|
|
=head1 BEHAVIOUR NOTES |
|
5422
|
|
|
|
|
|
|
|
|
5423
|
|
|
|
|
|
|
=over 4 |
|
5424
|
|
|
|
|
|
|
|
|
5425
|
|
|
|
|
|
|
=item * Recursion & Cycles |
|
5426
|
|
|
|
|
|
|
|
|
5427
|
|
|
|
|
|
|
The validator guards on the pair C<(schema_pointer, instance_address)>, so self-referential schemas and cyclic instance graphs will not infinite-loop. |
|
5428
|
|
|
|
|
|
|
|
|
5429
|
|
|
|
|
|
|
=item * Union Types with Inline Schemas |
|
5430
|
|
|
|
|
|
|
|
|
5431
|
|
|
|
|
|
|
C<type> may be an array mixing string type names and inline schemas. Any inline schema that validates the instance makes the C<type> check succeed. |
|
5432
|
|
|
|
|
|
|
|
|
5433
|
|
|
|
|
|
|
=item * Booleans |
|
5434
|
|
|
|
|
|
|
|
|
5435
|
|
|
|
|
|
|
For practicality in Perl, C<< type => 'boolean' >> accepts JSON-like booleans (e.g. true/false, 1/0 as strings) as well as Perl boolean objects (if you use a boolean class). If you need stricter behaviour, you can adapt C<_match_type> or introduce a constructor flag and branch there. |
|
5436
|
|
|
|
|
|
|
|
|
5437
|
|
|
|
|
|
|
=item * Combinators C<allOf>, C<anyOf>, C<oneOf>, C<not> |
|
5438
|
|
|
|
|
|
|
|
|
5439
|
|
|
|
|
|
|
C<allOf> validates all subschemas and merges their annotations (e.g. evaluated properties/items) into the parent schema’s annotation. If any subschema fails, C<allOf> fails. |
|
5440
|
|
|
|
|
|
|
|
|
5441
|
|
|
|
|
|
|
C<anyOf> and C<oneOf> always validate their branches in a “shadow” context. |
|
5442
|
|
|
|
|
|
|
Errors produced by non-selected branches do not leak into the main context when the combinator as a whole succeeds. When C<anyOf> fails (no branch matched) or C<oneOf> fails (zero or more than one branch matched), the validator merges the collected branch errors into the main context to make debugging easier. |
|
5443
|
|
|
|
|
|
|
|
|
5444
|
|
|
|
|
|
|
C<not> is also evaluated in a shadow context. If the inner subschema validates, C<not> fails with a single “forbidden not-schema” error; otherwise C<not> succeeds and any inner errors are discarded. |
|
5445
|
|
|
|
|
|
|
|
|
5446
|
|
|
|
|
|
|
=item * Conditionals C<if> / C<then> / C<else> |
|
5447
|
|
|
|
|
|
|
|
|
5448
|
|
|
|
|
|
|
The subschema under C<if> is treated purely as a condition: |
|
5449
|
|
|
|
|
|
|
|
|
5450
|
|
|
|
|
|
|
=over 4 |
|
5451
|
|
|
|
|
|
|
|
|
5452
|
|
|
|
|
|
|
=item * |
|
5453
|
|
|
|
|
|
|
|
|
5454
|
|
|
|
|
|
|
C<if> is always evaluated in an isolated “shadow” context. Any errors it produces (for example from C<required>) are never reported directly. |
|
5455
|
|
|
|
|
|
|
|
|
5456
|
|
|
|
|
|
|
=item * |
|
5457
|
|
|
|
|
|
|
|
|
5458
|
|
|
|
|
|
|
If C<if> succeeds and C<then> is present, C<then> is evaluated against the real context and may produce errors. |
|
5459
|
|
|
|
|
|
|
|
|
5460
|
|
|
|
|
|
|
=item * |
|
5461
|
|
|
|
|
|
|
|
|
5462
|
|
|
|
|
|
|
If C<if> fails and C<else> is present, C<else> is evaluated against the real context and may produce errors. |
|
5463
|
|
|
|
|
|
|
|
|
5464
|
|
|
|
|
|
|
=back |
|
5465
|
|
|
|
|
|
|
|
|
5466
|
|
|
|
|
|
|
This matches the JSON Schema 2020-12 intent: only C<then>/C<else> affect validity, C<if> itself never does. |
|
5467
|
|
|
|
|
|
|
|
|
5468
|
|
|
|
|
|
|
=item * C<unevaluatedItems> / C<unevaluatedProperties> |
|
5469
|
|
|
|
|
|
|
|
|
5470
|
|
|
|
|
|
|
Both C<unevaluatedItems> and C<unevaluatedProperties> are enforced using annotation produced by earlier keyword evaluations within the same schema object, matching draft 2020-12 semantics. |
|
5471
|
|
|
|
|
|
|
|
|
5472
|
|
|
|
|
|
|
=item * Error reporting and pointers |
|
5473
|
|
|
|
|
|
|
|
|
5474
|
|
|
|
|
|
|
Each error object contains both: |
|
5475
|
|
|
|
|
|
|
|
|
5476
|
|
|
|
|
|
|
=over 4 |
|
5477
|
|
|
|
|
|
|
|
|
5478
|
|
|
|
|
|
|
=item * |
|
5479
|
|
|
|
|
|
|
|
|
5480
|
|
|
|
|
|
|
C<path> – a JSON Pointer-like path to the failing location in the instance (e.g. C<#/properties~1s/oneOf~11/properties~1classes/0>). |
|
5481
|
|
|
|
|
|
|
|
|
5482
|
|
|
|
|
|
|
=item * |
|
5483
|
|
|
|
|
|
|
|
|
5484
|
|
|
|
|
|
|
C<schema_pointer> – a JSON Pointer into the root schema that identifies the keyword which emitted the error (e.g. |
|
5485
|
|
|
|
|
|
|
C<#/properties~1s/oneOf~11/properties~1classes/items/allOf~10/then/voting_right>). |
|
5486
|
|
|
|
|
|
|
|
|
5487
|
|
|
|
|
|
|
=back |
|
5488
|
|
|
|
|
|
|
|
|
5489
|
|
|
|
|
|
|
Messages for C<required> errors also list the full required set and the keys actually present at that location to help debug combinators such as C<anyOf>/C<oneOf>/C<if>/C<then>/C<else>. |
|
5490
|
|
|
|
|
|
|
|
|
5491
|
|
|
|
|
|
|
=item * RFC rigor and media types |
|
5492
|
|
|
|
|
|
|
|
|
5493
|
|
|
|
|
|
|
L<URI>/C<IRI> and media‐type parsing is intentionally pragmatic rather than fully RFC-complete. For example, C<uri>, C<iri>, and C<uri-reference> use strict but heuristic regexes; C<contentMediaType> validates UTF-8 for C<text/*; charset=utf-8> and supports pluggable validators/decoders, but is not a general MIME toolkit. |
|
5494
|
|
|
|
|
|
|
|
|
5495
|
|
|
|
|
|
|
=item * Compilation vs. Interpretation |
|
5496
|
|
|
|
|
|
|
|
|
5497
|
|
|
|
|
|
|
Both code paths are correct by design. The interpreter is simpler and great while developing a schema; toggle C<< ->compile >> when moving to production or after the schema stabilises. You may enable compilation lazily (call C<compile> any time) or eagerly via the constructor (C<< compile => 1 >>). |
|
5498
|
|
|
|
|
|
|
|
|
5499
|
|
|
|
|
|
|
=back |
|
5500
|
|
|
|
|
|
|
|
|
5501
|
|
|
|
|
|
|
=head1 WHY ENABLE C<COMPILE>? |
|
5502
|
|
|
|
|
|
|
|
|
5503
|
|
|
|
|
|
|
When C<compile> is ON, the validator precompiles a tiny Perl closure for each schema node. At runtime, those closures: |
|
5504
|
|
|
|
|
|
|
|
|
5505
|
|
|
|
|
|
|
=over 4 |
|
5506
|
|
|
|
|
|
|
|
|
5507
|
|
|
|
|
|
|
=item * avoid repeated hash lookups for keyword presence/values; |
|
5508
|
|
|
|
|
|
|
|
|
5509
|
|
|
|
|
|
|
=item * skip dispatch on absent keywords (branchless fast paths); |
|
5510
|
|
|
|
|
|
|
|
|
5511
|
|
|
|
|
|
|
=item * reuse precompiled child validators (arrays/objects/combinators); |
|
5512
|
|
|
|
|
|
|
|
|
5513
|
|
|
|
|
|
|
=item * reduce allocator churn by returning small, fixed-shape result hashes. |
|
5514
|
|
|
|
|
|
|
|
|
5515
|
|
|
|
|
|
|
=back |
|
5516
|
|
|
|
|
|
|
|
|
5517
|
|
|
|
|
|
|
In practice this improves steady-state throughput (especially for large/branchy schemas, or hot validation loops) and lowers tail latency by minimising per-instance work. The trade-offs are: |
|
5518
|
|
|
|
|
|
|
|
|
5519
|
|
|
|
|
|
|
=over 4 |
|
5520
|
|
|
|
|
|
|
|
|
5521
|
|
|
|
|
|
|
=item * a one-time compile cost per node (usually amortised quickly); |
|
5522
|
|
|
|
|
|
|
|
|
5523
|
|
|
|
|
|
|
=item * a small memory footprint for closures (one per visited node). |
|
5524
|
|
|
|
|
|
|
|
|
5525
|
|
|
|
|
|
|
=back |
|
5526
|
|
|
|
|
|
|
|
|
5527
|
|
|
|
|
|
|
If you only validate once or twice against a tiny schema, compilation will not matter; for services, batch jobs, or streaming pipelines it typically yields a noticeable speedup. Always benchmark with your own schema+data. |
|
5528
|
|
|
|
|
|
|
|
|
5529
|
|
|
|
|
|
|
=head1 AUTHOR |
|
5530
|
|
|
|
|
|
|
|
|
5531
|
|
|
|
|
|
|
Jacques Deguest E<lt>F<jack@deguest.jp>E<gt> |
|
5532
|
|
|
|
|
|
|
|
|
5533
|
|
|
|
|
|
|
=head1 SEE ALSO |
|
5534
|
|
|
|
|
|
|
|
|
5535
|
|
|
|
|
|
|
L<perl>, L<Time::Piece>, L<DateTime>, L<DateTime::Format::ISO8601>, L<Regexp::Common>, L<Net::IDN::Encode>, L<JSON::PP> |
|
5536
|
|
|
|
|
|
|
|
|
5537
|
|
|
|
|
|
|
L<JSON::Schema>, L<JSON::Validator> |
|
5538
|
|
|
|
|
|
|
|
|
5539
|
|
|
|
|
|
|
L<python-jsonschema|https://github.com/python-jsonschema/jsonschema>, |
|
5540
|
|
|
|
|
|
|
L<fastjsonschema|https://github.com/horejsek/python-fastjsonschema>, |
|
5541
|
|
|
|
|
|
|
L<Pydantic|https://docs.pydantic.dev>, |
|
5542
|
|
|
|
|
|
|
L<RapidJSON Schema|https://rapidjson.org/md_doc_schema.html> |
|
5543
|
|
|
|
|
|
|
|
|
5544
|
|
|
|
|
|
|
L<https://json-schema.org/specification> |
|
5545
|
|
|
|
|
|
|
|
|
5546
|
|
|
|
|
|
|
=head1 COPYRIGHT & LICENSE |
|
5547
|
|
|
|
|
|
|
|
|
5548
|
|
|
|
|
|
|
Copyright(c) 2025 DEGUEST Pte. Ltd. |
|
5549
|
|
|
|
|
|
|
|
|
5550
|
|
|
|
|
|
|
All rights reserved. |
|
5551
|
|
|
|
|
|
|
|
|
5552
|
|
|
|
|
|
|
This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. |
|
5553
|
|
|
|
|
|
|
|
|
5554
|
|
|
|
|
|
|
=cut |