line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
36
|
|
|
36
|
|
272271
|
use strict; |
|
36
|
|
|
|
|
133
|
|
|
36
|
|
|
|
|
1277
|
|
2
|
36
|
|
|
36
|
|
293
|
use warnings; |
|
36
|
|
|
|
|
174
|
|
|
36
|
|
|
|
|
1996
|
|
3
|
|
|
|
|
|
|
package JSON::Schema::Modern::Utilities; |
4
|
|
|
|
|
|
|
# vim: set ts=8 sts=2 sw=2 tw=100 et : |
5
|
|
|
|
|
|
|
# ABSTRACT: Internal utilities for JSON::Schema::Modern |
6
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
our $VERSION = '0.571'; |
8
|
|
|
|
|
|
|
|
9
|
36
|
|
|
36
|
|
862
|
use 5.020; |
|
36
|
|
|
|
|
157
|
|
10
|
36
|
|
|
36
|
|
263
|
use strictures 2; |
|
36
|
|
|
|
|
386
|
|
|
36
|
|
|
|
|
1579
|
|
11
|
36
|
|
|
36
|
|
7899
|
use stable 0.031 'postderef'; |
|
36
|
|
|
|
|
716
|
|
|
36
|
|
|
|
|
314
|
|
12
|
36
|
|
|
36
|
|
7045
|
use experimental 'signatures'; |
|
36
|
|
|
|
|
148
|
|
|
36
|
|
|
|
|
220
|
|
13
|
36
|
|
|
36
|
|
3301
|
use if "$]" >= 5.022, experimental => 're_strict'; |
|
36
|
|
|
|
|
125
|
|
|
36
|
|
|
|
|
487
|
|
14
|
36
|
|
|
36
|
|
3768
|
no if "$]" >= 5.031009, feature => 'indirect'; |
|
36
|
|
|
|
|
168
|
|
|
36
|
|
|
|
|
367
|
|
15
|
36
|
|
|
36
|
|
2096
|
no if "$]" >= 5.033001, feature => 'multidimensional'; |
|
36
|
|
|
|
|
119
|
|
|
36
|
|
|
|
|
268
|
|
16
|
36
|
|
|
36
|
|
1918
|
no if "$]" >= 5.033006, feature => 'bareword_filehandles'; |
|
36
|
|
|
|
|
136
|
|
|
36
|
|
|
|
|
290
|
|
17
|
36
|
|
|
36
|
|
1521
|
use B; |
|
36
|
|
|
|
|
118
|
|
|
36
|
|
|
|
|
2222
|
|
18
|
36
|
|
|
36
|
|
291
|
use Carp 'croak'; |
|
36
|
|
|
|
|
110
|
|
|
36
|
|
|
|
|
2747
|
|
19
|
36
|
|
|
36
|
|
1338
|
use JSON::MaybeXS 1.004004 'is_bool'; |
|
36
|
|
|
|
|
17093
|
|
|
36
|
|
|
|
|
3008
|
|
20
|
36
|
|
|
36
|
|
1300
|
use Ref::Util 0.100 qw(is_ref is_plain_arrayref is_plain_hashref); |
|
36
|
|
|
|
|
3940
|
|
|
36
|
|
|
|
|
2393
|
|
21
|
36
|
|
|
36
|
|
310
|
use Scalar::Util 'blessed'; |
|
36
|
|
|
|
|
102
|
|
|
36
|
|
|
|
|
2077
|
|
22
|
36
|
|
|
36
|
|
1596
|
use Storable 'dclone'; |
|
36
|
|
|
|
|
6974
|
|
|
36
|
|
|
|
|
2313
|
|
23
|
36
|
|
|
36
|
|
1275
|
use Feature::Compat::Try; |
|
36
|
|
|
|
|
762
|
|
|
36
|
|
|
|
|
540
|
|
24
|
36
|
|
|
36
|
|
11917
|
use JSON::Schema::Modern::Error; |
|
36
|
|
|
|
|
135
|
|
|
36
|
|
|
|
|
1300
|
|
25
|
36
|
|
|
36
|
|
1303
|
use JSON::Schema::Modern::Annotation; |
|
36
|
|
|
|
|
88
|
|
|
36
|
|
|
|
|
1226
|
|
26
|
36
|
|
|
36
|
|
254
|
use namespace::clean; |
|
36
|
|
|
|
|
115
|
|
|
36
|
|
|
|
|
437
|
|
27
|
|
|
|
|
|
|
|
28
|
36
|
|
|
36
|
|
12384
|
use Exporter 'import'; |
|
36
|
|
|
|
|
112
|
|
|
36
|
|
|
|
|
3258
|
|
29
|
|
|
|
|
|
|
|
30
|
|
|
|
|
|
|
our @EXPORT_OK = qw( |
31
|
|
|
|
|
|
|
is_type |
32
|
|
|
|
|
|
|
get_type |
33
|
|
|
|
|
|
|
is_equal |
34
|
|
|
|
|
|
|
is_elements_unique |
35
|
|
|
|
|
|
|
jsonp |
36
|
|
|
|
|
|
|
unjsonp |
37
|
|
|
|
|
|
|
local_annotations |
38
|
|
|
|
|
|
|
canonical_uri |
39
|
|
|
|
|
|
|
E |
40
|
|
|
|
|
|
|
A |
41
|
|
|
|
|
|
|
abort |
42
|
|
|
|
|
|
|
assert_keyword_exists |
43
|
|
|
|
|
|
|
assert_keyword_type |
44
|
|
|
|
|
|
|
assert_pattern |
45
|
|
|
|
|
|
|
assert_uri_reference |
46
|
|
|
|
|
|
|
assert_uri |
47
|
|
|
|
|
|
|
annotate_self |
48
|
|
|
|
|
|
|
sprintf_num |
49
|
|
|
|
|
|
|
true |
50
|
|
|
|
|
|
|
false |
51
|
|
|
|
|
|
|
); |
52
|
|
|
|
|
|
|
|
53
|
36
|
|
|
36
|
|
325
|
use JSON::PP (); |
|
36
|
|
|
|
|
134
|
|
|
36
|
|
|
|
|
1842
|
|
54
|
36
|
|
|
36
|
|
729
|
use constant { true => JSON::PP::true, false => JSON::PP::false }; |
|
36
|
|
|
|
|
115
|
|
|
36
|
|
|
|
|
1619
|
|
55
|
|
|
|
|
|
|
|
56
|
72373
|
|
|
72373
|
0
|
482993
|
sub is_type ($type, $value) { |
|
72373
|
|
|
|
|
111380
|
|
|
72373
|
|
|
|
|
109013
|
|
|
72373
|
|
|
|
|
98901
|
|
57
|
72373
|
100
|
|
|
|
146265
|
if ($type eq 'null') { |
58
|
71
|
|
|
|
|
340
|
return !(defined $value); |
59
|
|
|
|
|
|
|
} |
60
|
72302
|
100
|
|
|
|
141238
|
if ($type eq 'boolean') { |
61
|
8201
|
|
|
|
|
29046
|
return is_bool($value); |
62
|
|
|
|
|
|
|
} |
63
|
64101
|
100
|
|
|
|
125328
|
if ($type eq 'object') { |
64
|
18243
|
|
|
|
|
86797
|
return is_plain_hashref($value); |
65
|
|
|
|
|
|
|
} |
66
|
45858
|
100
|
|
|
|
92872
|
if ($type eq 'array') { |
67
|
11148
|
|
|
|
|
53268
|
return is_plain_arrayref($value); |
68
|
|
|
|
|
|
|
} |
69
|
|
|
|
|
|
|
|
70
|
34710
|
100
|
100
|
|
|
106644
|
if ($type eq 'string' or $type eq 'number' or $type eq 'integer') { |
|
|
|
100
|
|
|
|
|
71
|
34700
|
100
|
|
|
|
69418
|
return 0 if not defined $value; |
72
|
34686
|
|
|
|
|
159175
|
my $flags = B::svref_2object(\$value)->FLAGS; |
73
|
|
|
|
|
|
|
|
74
|
34686
|
100
|
|
|
|
92536
|
if ($type eq 'string') { |
75
|
24306
|
|
66
|
|
|
219934
|
return !is_ref($value) && $flags & B::SVf_POK && !($flags & (B::SVf_IOK | B::SVf_NOK)); |
76
|
|
|
|
|
|
|
} |
77
|
|
|
|
|
|
|
|
78
|
10380
|
100
|
|
|
|
24675
|
if ($type eq 'number') { |
79
|
6892
|
|
100
|
|
|
59531
|
return ref($value) =~ /^Math::Big(?:Int|Float)$/ |
80
|
|
|
|
|
|
|
|| !($flags & B::SVf_POK) && ($flags & (B::SVf_IOK | B::SVf_NOK)); |
81
|
|
|
|
|
|
|
} |
82
|
|
|
|
|
|
|
|
83
|
3488
|
50
|
|
|
|
7760
|
if ($type eq 'integer') { |
84
|
3488
|
|
100
|
|
|
35853
|
return ref($value) =~ /^Math::Big(?:Int|Float)$/ && $value->is_int |
85
|
|
|
|
|
|
|
|| !($flags & B::SVf_POK) && ($flags & (B::SVf_IOK | B::SVf_NOK)) && int($value) == $value; |
86
|
|
|
|
|
|
|
} |
87
|
|
|
|
|
|
|
} |
88
|
|
|
|
|
|
|
|
89
|
10
|
100
|
|
|
|
52
|
if ($type =~ /^reference to (.+)$/) { |
90
|
6
|
|
33
|
|
|
75
|
return !blessed($value) && ref($value) eq $1; |
91
|
|
|
|
|
|
|
} |
92
|
|
|
|
|
|
|
|
93
|
4
|
|
|
|
|
26
|
return ref($value) eq $type; |
94
|
|
|
|
|
|
|
} |
95
|
|
|
|
|
|
|
|
96
|
77807
|
|
|
77807
|
0
|
182891
|
sub get_type ($value) { |
|
77807
|
|
|
|
|
123466
|
|
|
77807
|
|
|
|
|
110201
|
|
97
|
77807
|
100
|
|
|
|
246856
|
return 'object' if is_plain_hashref($value); |
98
|
21689
|
100
|
|
|
|
64187
|
return 'boolean' if is_bool($value); |
99
|
12886
|
100
|
|
|
|
93027
|
return 'null' if not defined $value; |
100
|
12573
|
100
|
|
|
|
27611
|
return 'array' if is_plain_arrayref($value); |
101
|
|
|
|
|
|
|
|
102
|
11321
|
100
|
|
|
|
26781
|
return ref($value) =~ /^Math::Big(?:Int|Float)$/ ? ($value->is_int ? 'integer' : 'number') |
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
103
|
|
|
|
|
|
|
: (blessed($value) ? '' : 'reference to ').ref($value) |
104
|
|
|
|
|
|
|
if is_ref($value); |
105
|
|
|
|
|
|
|
|
106
|
10868
|
|
|
|
|
40957
|
my $flags = B::svref_2object(\$value)->FLAGS; |
107
|
10868
|
100
|
100
|
|
|
47673
|
return 'string' if $flags & B::SVf_POK && !($flags & (B::SVf_IOK | B::SVf_NOK)); |
108
|
4748
|
100
|
66
|
|
|
30476
|
return int($value) == $value ? 'integer' : 'number' |
|
|
100
|
|
|
|
|
|
109
|
|
|
|
|
|
|
if !($flags & B::SVf_POK) && ($flags & (B::SVf_IOK | B::SVf_NOK)); |
110
|
|
|
|
|
|
|
|
111
|
2
|
|
|
|
|
10
|
return 'ambiguous type'; |
112
|
|
|
|
|
|
|
} |
113
|
|
|
|
|
|
|
|
114
|
|
|
|
|
|
|
# compares two arbitrary data payloads for equality, as per |
115
|
|
|
|
|
|
|
# https://json-schema.org/draft/2020-12/json-schema-core.html#rfc.section.4.2.2 |
116
|
|
|
|
|
|
|
# if provided with a state hashref with a 'path' key, any differences are recorded within |
117
|
4453
|
|
|
4453
|
0
|
7320
|
sub is_equal ($x, $y, $state = undef) { |
|
4453
|
|
|
|
|
6975
|
|
|
4453
|
|
|
|
|
6758
|
|
|
4453
|
|
|
|
|
6928
|
|
|
4453
|
|
|
|
|
6320
|
|
118
|
4453
|
|
100
|
|
|
18390
|
$state->{path} //= ''; |
119
|
|
|
|
|
|
|
|
120
|
4453
|
|
|
|
|
10383
|
my @types = map get_type($_), $x, $y; |
121
|
|
|
|
|
|
|
|
122
|
4453
|
100
|
|
|
|
15643
|
if ($state->{scalarref_booleans}) { |
123
|
1582
|
100
|
|
|
|
3819
|
($x, $types[0]) = (0+!!$$x, 'boolean') if $types[0] eq 'reference to SCALAR'; |
124
|
1582
|
100
|
|
|
|
3460
|
($y, $types[1]) = (0+!!$$y, 'boolean') if $types[1] eq 'reference to SCALAR'; |
125
|
|
|
|
|
|
|
} |
126
|
|
|
|
|
|
|
|
127
|
4453
|
100
|
|
|
|
12226
|
return 0 if $types[0] ne $types[1]; |
128
|
3560
|
100
|
|
|
|
7390
|
return 1 if $types[0] eq 'null'; |
129
|
3546
|
100
|
|
|
|
14136
|
return $x eq $y if $types[0] eq 'string'; |
130
|
1625
|
100
|
|
|
|
8699
|
return $x == $y if grep $types[0] eq $_, qw(boolean number integer); |
131
|
|
|
|
|
|
|
|
132
|
542
|
|
|
|
|
1010
|
my $path = $state->{path}; |
133
|
542
|
100
|
|
|
|
1204
|
if ($types[0] eq 'object') { |
134
|
214
|
100
|
|
|
|
648
|
return 0 if keys %$x != keys %$y; |
135
|
198
|
100
|
|
|
|
1053
|
return 0 if not is_equal([ sort keys %$x ], [ sort keys %$y ]); |
136
|
192
|
|
|
|
|
776
|
foreach my $property (sort keys %$x) { |
137
|
230
|
|
|
|
|
595
|
$state->{path} = jsonp($path, $property); |
138
|
230
|
100
|
|
|
|
656
|
return 0 if not is_equal($x->{$property}, $y->{$property}, $state); |
139
|
|
|
|
|
|
|
} |
140
|
106
|
|
|
|
|
740
|
return 1; |
141
|
|
|
|
|
|
|
} |
142
|
|
|
|
|
|
|
|
143
|
328
|
50
|
|
|
|
808
|
if ($types[0] eq 'array') { |
144
|
328
|
100
|
|
|
|
850
|
return 0 if @$x != @$y; |
145
|
320
|
|
|
|
|
881
|
foreach my $idx (0 .. $x->$#*) { |
146
|
364
|
|
|
|
|
1066
|
$state->{path} = $path.'/'.$idx; |
147
|
364
|
100
|
|
|
|
960
|
return 0 if not is_equal($x->[$idx], $y->[$idx], $state); |
148
|
|
|
|
|
|
|
} |
149
|
232
|
|
|
|
|
1140
|
return 1; |
150
|
|
|
|
|
|
|
} |
151
|
|
|
|
|
|
|
|
152
|
0
|
|
|
|
|
0
|
return 0; # should never get here |
153
|
|
|
|
|
|
|
} |
154
|
|
|
|
|
|
|
|
155
|
|
|
|
|
|
|
# checks array elements for uniqueness. short-circuits on first pair of matching elements |
156
|
|
|
|
|
|
|
# if second arrayref is provided, it is populated with the indices of identical items |
157
|
2849
|
|
|
2849
|
0
|
5049
|
sub is_elements_unique ($array, $equal_indices = undef) { |
|
2849
|
|
|
|
|
4866
|
|
|
2849
|
|
|
|
|
4948
|
|
|
2849
|
|
|
|
|
4277
|
|
158
|
2849
|
|
|
|
|
9071
|
foreach my $idx0 (0 .. $array->$#*-1) { |
159
|
985
|
|
|
|
|
2773
|
foreach my $idx1 ($idx0+1 .. $array->$#*) { |
160
|
1390
|
100
|
|
|
|
4822
|
if (is_equal($array->[$idx0], $array->[$idx1], { scalarref_booleans => 1 })) { |
161
|
211
|
50
|
|
|
|
2386
|
push @$equal_indices, $idx0, $idx1 if defined $equal_indices; |
162
|
211
|
|
|
|
|
970
|
return 0; |
163
|
|
|
|
|
|
|
} |
164
|
|
|
|
|
|
|
} |
165
|
|
|
|
|
|
|
} |
166
|
2638
|
|
|
|
|
9188
|
return 1; |
167
|
|
|
|
|
|
|
} |
168
|
|
|
|
|
|
|
|
169
|
|
|
|
|
|
|
# shorthand for creating and appending json pointers |
170
|
|
|
|
|
|
|
# the first argument is a a json pointer; remaining arguments are path segments to be encoded and |
171
|
|
|
|
|
|
|
# appended |
172
|
|
|
|
|
|
|
sub jsonp { |
173
|
45526
|
100
|
|
45526
|
0
|
581472
|
return join('/', shift, map s/~/~0/gr =~ s!/!~1!gr, map +(is_plain_arrayref($_) ? @$_ : $_), grep defined, @_); |
174
|
|
|
|
|
|
|
} |
175
|
|
|
|
|
|
|
|
176
|
|
|
|
|
|
|
# splits a json pointer apart into its path segments |
177
|
0
|
|
|
0
|
0
|
0
|
sub unjsonp ($path) { |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
178
|
0
|
|
|
|
|
0
|
return map s!~0!~!gr =~ s!~1!/!gr, split m!/!, $path; |
179
|
|
|
|
|
|
|
} |
180
|
|
|
|
|
|
|
|
181
|
|
|
|
|
|
|
# get all annotations produced for the current instance data location (that are visible to this |
182
|
|
|
|
|
|
|
# schema location) - remember these are hashrefs, not Annotation objects |
183
|
1200
|
|
|
1200
|
0
|
2022
|
sub local_annotations ($state) { |
|
1200
|
|
|
|
|
2147
|
|
|
1200
|
|
|
|
|
1923
|
|
184
|
1200
|
|
|
|
|
4914
|
grep $_->{instance_location} eq $state->{data_path}, $state->{annotations}->@*; |
185
|
|
|
|
|
|
|
} |
186
|
|
|
|
|
|
|
|
187
|
|
|
|
|
|
|
# shorthand for finding the canonical uri of the present schema location |
188
|
|
|
|
|
|
|
# last argument can be an arrayref, usually coming from $state->{_schema_path_suffix} |
189
|
41279
|
|
|
41279
|
0
|
66337
|
sub canonical_uri ($state, @extra_path) { |
|
41279
|
|
|
|
|
61758
|
|
|
41279
|
|
|
|
|
77390
|
|
|
41279
|
|
|
|
|
59892
|
|
190
|
41279
|
100
|
100
|
|
|
173299
|
return $state->{initial_schema_uri} if not @extra_path and not length($state->{schema_path}); |
191
|
23870
|
100
|
100
|
|
|
86009
|
splice(@extra_path, -1, 1, $extra_path[-1]->@*) if @extra_path and is_plain_arrayref($extra_path[-1]); |
192
|
23870
|
|
|
|
|
84602
|
my $uri = $state->{initial_schema_uri}->clone; |
193
|
23870
|
100
|
100
|
|
|
1861990
|
my $fragment = ($uri->fragment//'').(@extra_path ? jsonp($state->{schema_path}, @extra_path) : $state->{schema_path}); |
194
|
23870
|
100
|
|
|
|
132554
|
undef $fragment if not length($fragment); |
195
|
23870
|
|
|
|
|
70299
|
$uri->fragment($fragment); |
196
|
23870
|
|
|
|
|
179963
|
$uri; |
197
|
|
|
|
|
|
|
} |
198
|
|
|
|
|
|
|
|
199
|
|
|
|
|
|
|
# shorthand for creating error objects |
200
|
|
|
|
|
|
|
# uses these keys from $state: |
201
|
|
|
|
|
|
|
# - initial_schema_uri |
202
|
|
|
|
|
|
|
# - keyword |
203
|
|
|
|
|
|
|
# - data_path |
204
|
|
|
|
|
|
|
# - traversed_schema_path |
205
|
|
|
|
|
|
|
# - schema_path |
206
|
|
|
|
|
|
|
# - _schema_path_suffix |
207
|
|
|
|
|
|
|
# - errors |
208
|
11206
|
|
|
11206
|
0
|
45630
|
sub E ($state, $error_string, @args) { |
|
11206
|
|
|
|
|
17898
|
|
|
11206
|
|
|
|
|
19843
|
|
|
11206
|
|
|
|
|
20716
|
|
|
11206
|
|
|
|
|
16935
|
|
209
|
11206
|
50
|
|
|
|
27801
|
croak 'E called in void context' if not defined wantarray; |
210
|
|
|
|
|
|
|
|
211
|
|
|
|
|
|
|
# sometimes the keyword shouldn't be at the very end of the schema path |
212
|
|
|
|
|
|
|
my $uri = canonical_uri($state, $state->{keyword}, $state->{_schema_path_suffix}) |
213
|
11206
|
|
|
|
|
40182
|
->to_abs($state->{effective_base_uri}); |
214
|
|
|
|
|
|
|
|
215
|
|
|
|
|
|
|
my $keyword_location = $state->{traversed_schema_path} |
216
|
11206
|
|
|
|
|
2770579
|
.jsonp($state->{schema_path}, $state->{keyword}, delete $state->{_schema_path_suffix}); |
217
|
|
|
|
|
|
|
|
218
|
11206
|
100
|
100
|
|
|
41809
|
undef $uri if $uri eq '' and $keyword_location eq '' |
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
219
|
|
|
|
|
|
|
or ($uri->fragment // '') eq $keyword_location and $uri->clone->fragment(undef) eq ''; |
220
|
|
|
|
|
|
|
|
221
|
|
|
|
|
|
|
push $state->{errors}->@*, JSON::Schema::Modern::Error->new( |
222
|
|
|
|
|
|
|
keyword => $state->{keyword}, |
223
|
|
|
|
|
|
|
instance_location => $state->{data_path}, |
224
|
|
|
|
|
|
|
keyword_location => $keyword_location, |
225
|
|
|
|
|
|
|
defined $uri ? ( absolute_keyword_location => $uri ) : (), |
226
|
|
|
|
|
|
|
error => @args ? sprintf($error_string, @args) : $error_string, |
227
|
11206
|
100
|
|
|
|
4577182
|
$state->{exception} ? ( exception => $state->{exception} ) : (), |
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
228
|
|
|
|
|
|
|
); |
229
|
|
|
|
|
|
|
|
230
|
11206
|
|
|
|
|
794694
|
return 0; |
231
|
|
|
|
|
|
|
} |
232
|
|
|
|
|
|
|
|
233
|
|
|
|
|
|
|
# shorthand for creating annotations |
234
|
|
|
|
|
|
|
# uses these keys from $state: |
235
|
|
|
|
|
|
|
# - initial_schema_uri |
236
|
|
|
|
|
|
|
# - keyword |
237
|
|
|
|
|
|
|
# - data_path |
238
|
|
|
|
|
|
|
# - traversed_schema_path |
239
|
|
|
|
|
|
|
# - schema_path |
240
|
|
|
|
|
|
|
# - _schema_path_suffix |
241
|
|
|
|
|
|
|
# - annotations |
242
|
|
|
|
|
|
|
# - collect_annotations |
243
|
10764
|
|
|
10764
|
0
|
17131
|
sub A ($state, $annotation) { |
|
10764
|
|
|
|
|
16880
|
|
|
10764
|
|
|
|
|
17562
|
|
|
10764
|
|
|
|
|
16488
|
|
244
|
10764
|
100
|
100
|
|
|
42115
|
return 1 if not $state->{collect_annotations} or $state->{spec_version} eq 'draft7'; |
245
|
|
|
|
|
|
|
|
246
|
|
|
|
|
|
|
# we store the absolute uri in unresolved form until needed, |
247
|
|
|
|
|
|
|
# and perform the rest of the calculations later. |
248
|
|
|
|
|
|
|
|
249
|
|
|
|
|
|
|
my $uri = [ canonical_uri($state, $state->{keyword}, $state->{_schema_path_suffix}), |
250
|
2466
|
|
|
|
|
8982
|
$state->{effective_base_uri} ]; |
251
|
|
|
|
|
|
|
|
252
|
|
|
|
|
|
|
my $keyword_location = $state->{traversed_schema_path} |
253
|
2466
|
|
|
|
|
8732
|
.jsonp($state->{schema_path}, $state->{keyword}, delete $state->{_schema_path_suffix}); |
254
|
|
|
|
|
|
|
|
255
|
|
|
|
|
|
|
push $state->{annotations}->@*, { |
256
|
|
|
|
|
|
|
keyword => $state->{keyword}, |
257
|
|
|
|
|
|
|
instance_location => $state->{data_path}, |
258
|
|
|
|
|
|
|
keyword_location => $keyword_location, |
259
|
|
|
|
|
|
|
# we calculate absolute_keyword_location when instantiating the Annotation object for Result |
260
|
|
|
|
|
|
|
_uri => $uri, |
261
|
|
|
|
|
|
|
annotation => $annotation, |
262
|
2466
|
100
|
|
|
|
15299
|
$state->{_unknown} ? ( unknown => 1 ) : (), |
263
|
|
|
|
|
|
|
}; |
264
|
|
|
|
|
|
|
|
265
|
2466
|
|
|
|
|
7257
|
return 1; |
266
|
|
|
|
|
|
|
} |
267
|
|
|
|
|
|
|
|
268
|
|
|
|
|
|
|
# creates an error object, but also aborts evaluation immediately |
269
|
|
|
|
|
|
|
# only this error is returned, because other errors on the stack might not actually be "real" |
270
|
|
|
|
|
|
|
# errors (consider if we were in the middle of evaluating a "not" or "if"). |
271
|
|
|
|
|
|
|
# Therefore this is only appropriate during the evaluation phase, not the traverse phase. |
272
|
55
|
|
|
55
|
0
|
297
|
sub abort ($state, $error_string, @args) { |
|
55
|
|
|
|
|
109
|
|
|
55
|
|
|
|
|
120
|
|
|
55
|
|
|
|
|
123
|
|
|
55
|
|
|
|
|
92
|
|
273
|
55
|
|
|
|
|
783
|
()= E({ %$state, exception => 1 }, $error_string, @args); |
274
|
55
|
50
|
|
|
|
416
|
croak 'abort() called during traverse' if $state->{traverse}; |
275
|
55
|
|
|
|
|
719
|
die pop $state->{errors}->@*; |
276
|
|
|
|
|
|
|
} |
277
|
|
|
|
|
|
|
|
278
|
0
|
|
|
0
|
0
|
0
|
sub assert_keyword_exists ($state, $schema) { |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
279
|
0
|
0
|
|
|
|
0
|
croak 'assert_keyword_exists called in void context' if not defined wantarray; |
280
|
0
|
0
|
|
|
|
0
|
return E($state, '%s keyword is required', $state->{keyword}) if not exists $schema->{$state->{keyword}}; |
281
|
0
|
|
|
|
|
0
|
return 1; |
282
|
|
|
|
|
|
|
} |
283
|
|
|
|
|
|
|
|
284
|
39919
|
|
|
39919
|
0
|
60223
|
sub assert_keyword_type ($state, $schema, $type) { |
|
39919
|
|
|
|
|
60484
|
|
|
39919
|
|
|
|
|
56461
|
|
|
39919
|
|
|
|
|
62358
|
|
|
39919
|
|
|
|
|
55024
|
|
285
|
39919
|
50
|
|
|
|
87667
|
croak 'assert_keyword_type called in void context' if not defined wantarray; |
286
|
39919
|
|
|
|
|
84132
|
my $value = $schema->{$state->{keyword}}; |
287
|
39919
|
|
|
|
|
60714
|
my $thing = 'value'; |
288
|
|
|
|
|
|
|
($value, $thing) = is_plain_hashref($value) ? ($value->{$state->{_schema_path_suffix}}, 'value at "'.$state->{_schema_path_suffix}.'"') |
289
|
|
|
|
|
|
|
: is_plain_arrayref($value) ? ($value->[$state->{_schema_path_suffix}], 'item '.$state->{_schema_path_suffix}) |
290
|
|
|
|
|
|
|
: die 'unknown type' |
291
|
39919
|
0
|
|
|
|
87633
|
if exists $state->{_schema_path_suffix}; |
|
|
50
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
292
|
39919
|
100
|
|
|
|
85692
|
return 1 if is_type($type, $value); |
293
|
18
|
100
|
|
|
|
143
|
E($state, '%s %s is not a%s %s', $state->{keyword}, $thing, ($type =~ /^[aeiou]/ ? 'n' : ''), $type); |
294
|
|
|
|
|
|
|
} |
295
|
|
|
|
|
|
|
|
296
|
2289
|
|
|
2289
|
0
|
4390
|
sub assert_pattern ($state, $pattern) { |
|
2289
|
|
|
|
|
3789
|
|
|
2289
|
|
|
|
|
3823
|
|
|
2289
|
|
|
|
|
3528
|
|
297
|
2289
|
50
|
|
|
|
5614
|
croak 'assert_pattern called in void context' if not defined wantarray; |
298
|
|
|
|
|
|
|
try { |
299
|
0
|
|
|
0
|
|
0
|
local $SIG{__WARN__} = sub { die @_ }; |
300
|
|
|
|
|
|
|
qr/$pattern/; |
301
|
|
|
|
|
|
|
} |
302
|
2289
|
|
|
|
|
5230
|
catch ($e) { return E($state, $e); }; |
303
|
2286
|
|
|
|
|
21433
|
return 1; |
304
|
|
|
|
|
|
|
} |
305
|
|
|
|
|
|
|
|
306
|
|
|
|
|
|
|
# this is only suitable for checking URIs within schemas themselves |
307
|
4596
|
|
|
4596
|
0
|
8066
|
sub assert_uri_reference ($state, $schema) { |
|
4596
|
|
|
|
|
6954
|
|
|
4596
|
|
|
|
|
6810
|
|
|
4596
|
|
|
|
|
6812
|
|
308
|
4596
|
50
|
|
|
|
9898
|
croak 'assert_uri_reference called in void context' if not defined wantarray; |
309
|
|
|
|
|
|
|
|
310
|
4596
|
|
|
|
|
10107
|
my $string = $schema->{$state->{keyword}}; |
311
|
|
|
|
|
|
|
return E($state, '%s value is not a valid URI reference', $state->{keyword}) |
312
|
|
|
|
|
|
|
# see also uri-reference format sub |
313
|
4596
|
100
|
66
|
|
|
16450
|
if fc(Mojo::URL->new($string)->to_unsafe_string) ne fc($string) |
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
314
|
|
|
|
|
|
|
or $string =~ /[^[:ascii:]]/ |
315
|
|
|
|
|
|
|
or $string =~ /#/ |
316
|
|
|
|
|
|
|
and $string !~ m{#$} # empty fragment |
317
|
|
|
|
|
|
|
and $string !~ m{#[A-Za-z][A-Za-z0-9_:.-]*$} # plain-name fragment |
318
|
|
|
|
|
|
|
and $string !~ m{#/(?:[^~]|~[01])*$}; # json pointer fragment |
319
|
|
|
|
|
|
|
|
320
|
4566
|
|
|
|
|
1637998
|
return 1; |
321
|
|
|
|
|
|
|
} |
322
|
|
|
|
|
|
|
|
323
|
|
|
|
|
|
|
# this is only suitable for checking URIs within schemas themselves |
324
|
5823
|
|
|
5823
|
0
|
10956
|
sub assert_uri ($state, $schema, $override = undef) { |
|
5823
|
|
|
|
|
9897
|
|
|
5823
|
|
|
|
|
8862
|
|
|
5823
|
|
|
|
|
10213
|
|
|
5823
|
|
|
|
|
8537
|
|
325
|
5823
|
50
|
|
|
|
12249
|
croak 'assert_uri called in void context' if not defined wantarray; |
326
|
|
|
|
|
|
|
|
327
|
5823
|
|
66
|
|
|
20409
|
my $string = $override // $schema->{$state->{keyword}}; |
328
|
5823
|
|
|
|
|
19428
|
my $uri = Mojo::URL->new($string); |
329
|
|
|
|
|
|
|
|
330
|
5823
|
50
|
66
|
|
|
510028
|
return E($state, '"%s" is not a valid URI', $string) |
|
|
|
100
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
|
66
|
|
|
|
|
331
|
|
|
|
|
|
|
# see also uri format sub |
332
|
|
|
|
|
|
|
if fc($uri->to_unsafe_string) ne fc($string) |
333
|
|
|
|
|
|
|
or $string =~ /[^[:ascii:]]/ |
334
|
|
|
|
|
|
|
or not $uri->is_abs |
335
|
|
|
|
|
|
|
or $string =~ /#/ |
336
|
|
|
|
|
|
|
and $string !~ m{#$} # empty fragment |
337
|
|
|
|
|
|
|
and $string !~ m{#[A-Za-z][A-Za-z0-9_:.-]*$} # plain-name fragment |
338
|
|
|
|
|
|
|
and $string !~ m{#/(?:[^~]|~[01])*$}; # json pointer fragment |
339
|
|
|
|
|
|
|
|
340
|
5815
|
|
|
|
|
1203659
|
return 1; |
341
|
|
|
|
|
|
|
} |
342
|
|
|
|
|
|
|
|
343
|
|
|
|
|
|
|
# produces an annotation whose value is the same as that of the current keyword |
344
|
1595
|
|
|
1595
|
0
|
2467
|
sub annotate_self ($state, $schema) { |
|
1595
|
|
|
|
|
2446
|
|
|
1595
|
|
|
|
|
2460
|
|
|
1595
|
|
|
|
|
2499
|
|
345
|
|
|
|
|
|
|
A($state, is_ref($schema->{$state->{keyword}}) ? dclone($schema->{$state->{keyword}}) |
346
|
1595
|
100
|
|
|
|
11107
|
: $schema->{$state->{keyword}}); |
347
|
|
|
|
|
|
|
} |
348
|
|
|
|
|
|
|
|
349
|
1101
|
|
|
1101
|
0
|
2164
|
sub sprintf_num ($value) { |
|
1101
|
|
|
|
|
2055
|
|
|
1101
|
|
|
|
|
1820
|
|
350
|
|
|
|
|
|
|
# use original value as stored in the NV, without losing precision |
351
|
1101
|
100
|
|
|
|
6922
|
ref($value) =~ /^Math::Big(?:Int|Float)$/ ? $value->bstr : sprintf('%s', $value); |
352
|
|
|
|
|
|
|
} |
353
|
|
|
|
|
|
|
|
354
|
|
|
|
|
|
|
1; |
355
|
|
|
|
|
|
|
|
356
|
|
|
|
|
|
|
__END__ |
357
|
|
|
|
|
|
|
|
358
|
|
|
|
|
|
|
=pod |
359
|
|
|
|
|
|
|
|
360
|
|
|
|
|
|
|
=encoding UTF-8 |
361
|
|
|
|
|
|
|
|
362
|
|
|
|
|
|
|
=head1 NAME |
363
|
|
|
|
|
|
|
|
364
|
|
|
|
|
|
|
JSON::Schema::Modern::Utilities - Internal utilities for JSON::Schema::Modern |
365
|
|
|
|
|
|
|
|
366
|
|
|
|
|
|
|
=head1 VERSION |
367
|
|
|
|
|
|
|
|
368
|
|
|
|
|
|
|
version 0.571 |
369
|
|
|
|
|
|
|
|
370
|
|
|
|
|
|
|
=head1 SYNOPSIS |
371
|
|
|
|
|
|
|
|
372
|
|
|
|
|
|
|
use JSON::Schema::Modern::Utilities qw(func1 func2..); |
373
|
|
|
|
|
|
|
|
374
|
|
|
|
|
|
|
=head1 DESCRIPTION |
375
|
|
|
|
|
|
|
|
376
|
|
|
|
|
|
|
This class contains internal utilities to be used by L<JSON::Schema::Modern>. |
377
|
|
|
|
|
|
|
|
378
|
|
|
|
|
|
|
=for Pod::Coverage is_type get_type is_equal is_elements_unique jsonp unjsonp local_annotations |
379
|
|
|
|
|
|
|
canonical_uri E A abort assert_keyword_exists assert_keyword_type assert_pattern assert_uri_reference assert_uri |
380
|
|
|
|
|
|
|
annotate_self sprintf_num |
381
|
|
|
|
|
|
|
|
382
|
|
|
|
|
|
|
=for stopwords OpenAPI |
383
|
|
|
|
|
|
|
|
384
|
|
|
|
|
|
|
=head1 SUPPORT |
385
|
|
|
|
|
|
|
|
386
|
|
|
|
|
|
|
Bugs may be submitted through L<https://github.com/karenetheridge/JSON-Schema-Modern/issues>. |
387
|
|
|
|
|
|
|
|
388
|
|
|
|
|
|
|
I am also usually active on irc, as 'ether' at C<irc.perl.org> and C<irc.libera.chat>. |
389
|
|
|
|
|
|
|
|
390
|
|
|
|
|
|
|
You can also find me on the L<JSON Schema Slack server|https://json-schema.slack.com> and L<OpenAPI Slack |
391
|
|
|
|
|
|
|
server|https://open-api.slack.com>, which are also great resources for finding help. |
392
|
|
|
|
|
|
|
|
393
|
|
|
|
|
|
|
=head1 AUTHOR |
394
|
|
|
|
|
|
|
|
395
|
|
|
|
|
|
|
Karen Etheridge <ether@cpan.org> |
396
|
|
|
|
|
|
|
|
397
|
|
|
|
|
|
|
=head1 COPYRIGHT AND LICENCE |
398
|
|
|
|
|
|
|
|
399
|
|
|
|
|
|
|
This software is copyright (c) 2020 by Karen Etheridge. |
400
|
|
|
|
|
|
|
|
401
|
|
|
|
|
|
|
This is free software; you can redistribute it and/or modify it under |
402
|
|
|
|
|
|
|
the same terms as the Perl 5 programming language system itself. |
403
|
|
|
|
|
|
|
|
404
|
|
|
|
|
|
|
=cut |