File Coverage

blib/lib/JSON/Validator/Util.pm
Criterion Covered Total %
statement 132 133 99.2
branch 89 94 94.6
condition 45 49 91.8
subroutine 23 23 100.0
pod 9 10 90.0
total 298 309 96.4


line stmt bran cond sub pod time code
1             package JSON::Validator::Util;
2 48     48   315 use Mojo::Base -strict;
  48         89  
  48         291  
3              
4 48     48   4461 use Carp ();
  48         88  
  48         560  
5 48     48   219 use Data::Dumper ();
  48         87  
  48         685  
6 48     48   468 use Exporter 'import';
  48         261  
  48         1204  
7 48     48   307 use JSON::Validator::Error;
  48         76  
  48         249  
8 48     48   18892 use Mojo::Collection;
  48         158588  
  48         1694  
9 48     48   17042 use Mojo::JSON;
  48         853045  
  48         2521  
10 48     48   18370 use Mojo::Loader;
  48         1207495  
  48         2145  
11 48     48   356 use Mojo::Util;
  48         93  
  48         1286  
12 48     48   244 use Scalar::Util 'blessed';
  48         88  
  48         13093  
13              
14             our @EXPORT_OK
15             = qw(E data_checksum data_section data_type is_type schema_extract json_pointer prefix_errors schema_type uniq);
16              
17 661     661 0 90672 sub E { JSON::Validator::Error->new(@_) }
18              
19             sub data_checksum {
20 2782     2782 1 12891 Mojo::Util::md5_sum(Data::Dumper->new([@_])->Sortkeys(1)->Useqq(1)->Dump);
21             }
22              
23             sub data_section {
24 18     18 1 46 my ($class, $file, $params) = @_;
25 18         66 state $skip_re
26             = qr{(^JSON::Validator|^Mojo::Base$|^Mojolicious$|\w+::_Dynamic)};
27              
28 18 100       63 my @classes = $class ? ([$class]) : ();
29 18 100       46 unless (@classes) {
30 4         6 my $i = 0;
31 4         13 while ($class = caller($i++)) {
32 22 100       99 push @classes, [$class] unless $class =~ $skip_re;
33             }
34             }
35              
36 18         44 for my $group (@classes) {
37             push @$group,
38 48     48   338 grep { !/$skip_re/ } do { no strict 'refs'; @{"$group->[0]\::ISA"} };
  48         124  
  48         73945  
  21         137  
  6         40  
  21         42  
  21         104  
39 21         43 for my $class (@$group) {
40 22 100       158 next unless my $text = Mojo::Loader::data_section($class, $file);
41             return Mojo::Util::encode($params->{encoding}, $text)
42 17 50       1489 if $params->{encoding};
43 0         0 return $text;
44             }
45             }
46              
47 1 50       10 return undef unless $params->{confess};
48              
49 1 50       3 my $err = Mojo::JSON::encode_json([map { @$_ == 1 ? $_->[0] : $_ } @classes]);
  3         11  
50 1         273 Carp::confess(qq(Could not find "$file" in __DATA__ section of $err.));
51             }
52              
53             sub data_type {
54 1277     1277 1 2981 my $ref = ref $_[0];
55 1277         2498 my $blessed = blessed $_[0];
56 1277 100       3237 return 'object' if $ref eq 'HASH';
57 956 100 100     2923 return lc $ref if $ref and !$blessed;
58 835 100       1746 return 'null' if !defined $_[0];
59 817 100 100     1803 return 'boolean' if $blessed and ("$_[0]" eq "1" or !"$_[0]");
      100        
60              
61 776 100       1405 if (is_type($_[0], 'NUM')) {
62 193 100 100     460 return 'integer' if grep { ($_->{type} // '') eq 'integer' } @{$_[1] || []};
  249 100       1400  
  193         841  
63 102         547 return 'number';
64             }
65              
66 583   100     2592 return $blessed || 'string';
67             }
68              
69             sub is_type {
70 111518     111518 1 174095 my $type = $_[1];
71              
72 111518 100       199279 if ($type eq 'BOOL') {
73 21438   100     78045 return blessed $_[0]
74             && ($_[0]->isa('JSON::PP::Boolean') || "$_[0]" eq "1" || !$_[0]);
75             }
76              
77             # NUM
78 90080 100       138028 if ($type eq 'NUM') {
79             return
80 1935   66     15811 B::svref_2object(\$_[0])->FLAGS & (B::SVp_IOK | B::SVp_NOK)
81             && 0 + $_[0] eq $_[0]
82             && $_[0] * 0 == 0;
83             }
84              
85             # Class or data type
86 88145 100       335938 return blessed $_[0] ? $_[0]->isa($type) : ref $_[0] eq $type;
87             }
88              
89             sub schema_extract {
90 12     12 1 65 my ($data, $p, $cb) = @_;
91 12 50       51 $p = [ref $p ? @$p : length $p ? split('/', $p, -1) : $p];
    100          
92 12 100 66     66 shift @$p if @$p and defined $p->[0] and !length $p->[0];
      100        
93 12         31 _schema_extract($data, $p, '', $cb);
94             }
95              
96             sub json_pointer {
97 2682     2682 1 5094 local $_ = $_[1];
98 2682         5620 s!~!~0!g;
99 2682         4263 s!/!~1!g;
100 2682         11513 "$_[0]/$_";
101             }
102              
103             sub prefix_errors {
104 27     27 1 87 my ($type, @errors_with_index) = @_;
105 27         44 my @errors;
106              
107 27         77 for my $e (@errors_with_index) {
108 35         64 my $index = shift @$e;
109             push @errors, map {
110 35         122 my $msg = sprintf '/%s/%s %s', $type, $index, $_->message;
  36         122  
111 36         168 $msg =~ s!(\d+)\s/!$1/!g;
112 36         114 E $_->path, $msg;
113             } @$e;
114             }
115              
116 27         111 return @errors;
117             }
118              
119             sub schema_type {
120 2091 100   2091 1 5505 return $_[0]->{type} if $_[0]->{type};
121 1951 100       3877 return _guessed_right(object => $_[1]) if $_[0]->{additionalProperties};
122 1935 100       4043 return _guessed_right(object => $_[1]) if $_[0]->{patternProperties};
123 1916 100       4273 return _guessed_right(object => $_[1]) if $_[0]->{properties};
124 1810 100       3243 return _guessed_right(object => $_[1]) if $_[0]->{propertyNames};
125 1809 100       3302 return _guessed_right(object => $_[1]) if $_[0]->{required};
126 1807 100       3833 return _guessed_right(object => $_[1]) if $_[0]->{if};
127             return _guessed_right(object => $_[1])
128             if defined $_[0]->{maxProperties}
129 1806 100 100     6245 or defined $_[0]->{minProperties};
130 1798 100       3586 return _guessed_right(array => $_[1]) if $_[0]->{additionalItems};
131 1796 100       3688 return _guessed_right(array => $_[1]) if $_[0]->{items};
132 1783 100       3420 return _guessed_right(array => $_[1]) if $_[0]->{uniqueItems};
133             return _guessed_right(array => $_[1])
134             if defined $_[0]->{maxItems}
135 1770 100 100     5906 or defined $_[0]->{minItems};
136 1757 100       3213 return _guessed_right(string => $_[1]) if $_[0]->{pattern};
137             return _guessed_right(string => $_[1])
138             if defined $_[0]->{maxLength}
139 1753 100 100     5615 or defined $_[0]->{minLength};
140 1724 100       3235 return _guessed_right(number => $_[1]) if $_[0]->{multipleOf};
141             return _guessed_right(number => $_[1])
142             if defined $_[0]->{maximum}
143 1712 100 100     5290 or defined $_[0]->{minimum};
144 1679 100       3103 return 'const' if exists $_[0]->{const};
145 1662         4835 return '';
146             }
147              
148             sub uniq {
149 29     29 1 55 my %uniq;
150 29         70 grep { !$uniq{$_}++ } @_;
  73         271  
151             }
152              
153             # _guessed_right($type, $data);
154             sub _guessed_right {
155 272 100   272   911 return $_[0] if !defined $_[1];
156 245 100       892 return $_[0] if $_[0] eq data_type $_[1], [{type => $_[0]}];
157 19         125 return '';
158             }
159              
160             sub _schema_extract {
161 33     33   56 my ($data, $path, $pos, $cb) = @_, my $tied;
162              
163 33         55 while (@$path) {
164 44         60 my $p = shift @$path;
165              
166 44 100       83 unless (defined $p) {
167 9         10 my $i = 0;
168             return Mojo::Collection->new(
169             map {
170 21         50 _schema_extract($_->[0], [@$path], json_pointer($pos, $_->[1]), $cb)
171 17         29 } ref $data eq 'ARRAY' ? map { [$_, $i++] }
172 9 50       28 @$data : ref $data eq 'HASH' ? map { [$data->{$_}, $_] }
  4 100       9  
173             sort keys %$data : [$data, '']
174             );
175             }
176              
177 35         88 $p =~ s!~1!/!g;
178 35         41 $p =~ s/~0/~/g;
179 35 100       52 $pos = json_pointer $pos, $p if $cb;
180              
181 35 100 100     138 if (ref $data eq 'HASH' and exists $data->{$p}) {
    100 66        
      66        
182 26         42 $data = $data->{$p};
183             }
184             elsif (ref $data eq 'ARRAY' and $p =~ /^\d+$/ and @$data > $p) {
185 2         4 $data = $data->[$p];
186             }
187             else {
188 7         36 return undef;
189             }
190              
191 28 100 100     108 $data = $tied->schema if ref $data eq 'HASH' and $tied = tied %$data;
192             }
193              
194 17 100       34 return $cb->($data, $pos) if $cb;
195 15         46 return $data;
196             }
197              
198             1;
199              
200             =encoding utf8
201              
202             =head1 NAME
203              
204             JSON::Validator::Util - Utility functions for JSON::Validator
205              
206             =head1 DESCRIPTION
207              
208             L is a package containing utility functions for
209             L. Each of the L can be imported.
210              
211             =head1 FUNCTIONS
212              
213             =head2 data_checksum
214              
215             $str = data_checksum $any;
216              
217             Will create a checksum for any data structure stored in C<$any>.
218              
219             =head2 data_section
220              
221             $str = data_section "Some::Module", "file.json";
222             $str = data_section "Some::Module", "file.json", {encode => 'UTF-8'};
223              
224             Same as L, but will also look up the file in any
225             inherited class.
226              
227             =head2 data_type
228              
229             $str = data_type $any;
230             $str = data_type $any, [@schemas];
231             $str = data_type $any, [{type => "integer", ...}];
232              
233             Returns the JSON type for C<$any>. C<$str> can be array, boolean, integer,
234             null, number object or string. Note that a list of schemas need to be provided
235             to differentiate between "integer" and "number".
236              
237             =head2 is_type
238              
239             $bool = is_type $any, $class;
240             $bool = is_type $any, $type; # $type = "ARRAY", "BOOL", "HASH", "NUM" ...
241              
242             Checks if C<$any> is a, or inherits from, C<$class> or C<$type>. Two special
243             types can be checked:
244              
245             =over 2
246              
247             =item * BOOL
248              
249             Checks if C<$any> is a boolean value. C<$any> is considered boolean if it is an
250             object inheriting from L or is another object that
251             stringifies to "1" or "0".
252              
253             =item * NUM
254              
255             Checks if C<$any> is indeed a number.
256              
257             =back
258              
259             =head2 json_pointer
260              
261             $str = json_pointer $path, $append;
262              
263             Will concat C<$append> on to C<$path>, but will also escape the two special
264             characters "~" and "/" in C<$append>.
265              
266             =head2 prefix_errors
267              
268             @errors = prefix_errors $prefix, @errors;
269              
270             Consider this internal for now.
271              
272             =head2 schema_extract
273              
274             $data = schema_extract $any, $json_pointer;
275             $data = schema_extract $any, "/x/cool_beans/y";
276             $collection = schema_extract $any, ["x", undef, "y"];
277             schema_extract $any, $json_pointer, sub { my ($data, $json_pointer) = @_ };
278              
279             The basic usage is to extract data from C<$any>, using a C<$json_pointer> -
280             L. It can however be used in a
281             more complex way by passing in an array-ref, instead of a plain string. The
282             array-ref can contain C values, will result in extracting any element
283             on that point, regardsless of value. In that case a L will
284             be returned.
285              
286             A callback can also be given. This callback will be called each time the
287             C<$json_pointer> matches some data, and will pass in the C<$json_pointer> at
288             that place.
289              
290             In addition, if the C<$json_pointer> points to a L at any
291             point, the "$ref" will be followed, while if you used L,
292             it would return either the L or C.
293              
294             Even though L has special capabilities for handling a
295             JSON-Schema, it can be used for any data-structure, just like
296             L.
297              
298             =head2 schema_type
299              
300             $str = schema_type $hash_ref;
301             $str = schema_type $hash_ref, $any;
302              
303             Looks at C<$hash_ref> and tries to figure out what kind of type the schema
304             represents. C<$str> can be "array", "const", "number", "object", "string", or
305             fallback to empty string if the correct type could not be figured out.
306              
307             C<$any> can be provided to double check the type, so if C<$hash_ref> describes
308             an "object", but C<$any> is an array-ref, then C<$str> will become an empty
309             string. Example:
310              
311             # $str = "";
312             $str = schema {additionalProperties => false}, [];
313              
314             # $str = "object"
315             $str = schema {additionalProperties => false};
316             $str = schema {additionalProperties => false}, {};
317              
318             Note that this process is relatively slow, so it will make your validation
319             faster if you specify "type". Both of the two below is valid, but the one with
320             "type" will be faster.
321              
322             {"type": "object", "properties": {}} # Faster
323             {"properties": {}} # Slower
324              
325             =head2 uniq
326              
327             @items = uniq @items;
328              
329             See L.
330              
331             =head1 SEE ALSO
332              
333             L.
334              
335             =cut