File Coverage

blib/lib/JSON/Validator/Util.pm
Criterion Covered Total %
statement 131 132 99.2
branch 87 92 94.5
condition 45 49 91.8
subroutine 23 23 100.0
pod 9 10 90.0
total 295 306 96.4


line stmt bran cond sub pod time code
1             package JSON::Validator::Util;
2 49     49   334 use Mojo::Base -strict;
  49         97  
  49         302  
3              
4 49     49   5372 use Carp ();
  49         99  
  49         712  
5 49     49   234 use Data::Dumper ();
  49         87  
  49         807  
6 49     49   549 use Exporter 'import';
  49         316  
  49         1431  
7 49     49   253 use JSON::Validator::Error;
  49         87  
  49         253  
8 49     49   22994 use Mojo::Collection;
  49         189999  
  49         1957  
9 49     49   20382 use Mojo::JSON;
  49         987676  
  49         2853  
10 49     49   21072 use Mojo::Loader;
  49         1432019  
  49         2442  
11 49     49   372 use Mojo::Util;
  49         145  
  49         1591  
12 49     49   307 use Scalar::Util 'blessed';
  49         109  
  49         15481  
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 699     699 0 50599 sub E { JSON::Validator::Error->new(@_) }
18              
19             sub data_checksum {
20 2782     2782 1 11883 Mojo::Util::md5_sum(Data::Dumper->new([@_])->Sortkeys(1)->Useqq(1)->Dump);
21             }
22              
23             sub data_section {
24 18     18 1 50 my ($class, $file, $params) = @_;
25 18         96 state $skip_re
26             = qr{(^JSON::Validator|^Mojo::Base$|^Mojolicious$|\w+::_Dynamic)};
27              
28 18 100       75 my @classes = $class ? ([$class]) : ();
29 18 100       57 unless (@classes) {
30 4         6 my $i = 0;
31 4         16 while ($class = caller($i++)) {
32 22 100       117 push @classes, [$class] unless $class =~ $skip_re;
33             }
34             }
35              
36 18         51 for my $group (@classes) {
37             push @$group,
38 49     49   414 grep { !/$skip_re/ } do { no strict 'refs'; @{"$group->[0]\::ISA"} };
  49         137  
  49         89419  
  21         170  
  6         38  
  21         30  
  21         121  
39 21         54 for my $class (@$group) {
40 22 100       178 next unless my $text = Mojo::Loader::data_section($class, $file);
41             return Mojo::Util::encode($params->{encoding}, $text)
42 17 50       1760 if $params->{encoding};
43 0         0 return $text;
44             }
45             }
46              
47 1 50       14 return undef unless $params->{confess};
48              
49 1 50       7 my $err = Mojo::JSON::encode_json([map { @$_ == 1 ? $_->[0] : $_ } @classes]);
  3         13  
50 1         321 Carp::confess(qq(Could not find "$file" in __DATA__ section of $err.));
51             }
52              
53             sub data_type {
54 1317     1317 1 3339 my $ref = ref $_[0];
55 1317         2729 my $blessed = blessed $_[0];
56 1317 100       3413 return 'object' if $ref eq 'HASH';
57 1000 100 100     2914 return lc $ref if $ref and !$blessed;
58 873 100       1922 return 'null' if !defined $_[0];
59 855 100 100     1878 return 'boolean' if $blessed and ("$_[0]" eq "1" or !"$_[0]");
      100        
60              
61 814 100       1669 if (is_type($_[0], 'NUM')) {
62 193 100 100     444 return 'integer' if grep { ($_->{type} // '') eq 'integer' } @{$_[1] || []};
  249 100       1400  
  193         782  
63 102         498 return 'number';
64             }
65              
66 621   100     3028 return $blessed || 'string';
67             }
68              
69             sub is_type {
70 111975     111975 1 169653 my $type = $_[1];
71              
72 111975 100       200692 if ($type eq 'BOOL') {
73 21498   100     83210 return blessed $_[0]
74             && ($_[0]->isa('JSON::PP::Boolean') || "$_[0]" eq "1" || !$_[0]);
75             }
76              
77             # NUM
78 90477 100       144961 if ($type eq 'NUM') {
79             return
80 1976   66     15497 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 88501 100       352160 return blessed $_[0] ? $_[0]->isa($type) : ref $_[0] eq $type;
87             }
88              
89             sub schema_extract {
90 12     12 1 82 my ($data, $p, $cb) = @_;
91 12 50       56 $p = [ref $p ? @$p : length $p ? split('/', $p, -1) : $p];
    100          
92 12 100 66     85 shift @$p if @$p and defined $p->[0] and !length $p->[0];
      100        
93 12         39 _schema_extract($data, $p, '', $cb);
94             }
95              
96             sub json_pointer {
97 2690     2690 1 4979 local $_ = $_[1];
98 2690         6604 s!~!~0!g;
99 2690         4125 s!/!~1!g;
100 2690         11647 "$_[0]/$_";
101             }
102              
103             sub prefix_errors {
104 31     31 1 107 my ($type, @errors_with_index) = @_;
105 31         58 my @errors;
106              
107 31         80 for my $e (@errors_with_index) {
108 42         81 my $index = shift @$e;
109             push @errors, map {
110 42         149 my $msg = sprintf '/%s/%s %s', $type, $index, $_->message;
  44         148  
111 44         231 $msg =~ s!(\d+)\s/!$1/!g;
112 44         129 E $_->path, $msg;
113             } @$e;
114             }
115              
116 31         136 return @errors;
117             }
118              
119             sub schema_type {
120 2137 100   2137 1 5789 return $_[0]->{type} if $_[0]->{type};
121 1996 100       3904 return _guessed_right(object => $_[1]) if $_[0]->{additionalProperties};
122 1980 100       4026 return _guessed_right(object => $_[1]) if $_[0]->{patternProperties};
123 1961 100       3931 return _guessed_right(object => $_[1]) if $_[0]->{properties};
124 1858 100       3391 return _guessed_right(object => $_[1]) if $_[0]->{propertyNames};
125 1857 100       3601 return _guessed_right(object => $_[1]) if $_[0]->{required};
126             return _guessed_right(object => $_[1])
127             if defined $_[0]->{maxProperties}
128 1855 100 100     6377 or defined $_[0]->{minProperties};
129 1847 100       3536 return _guessed_right(array => $_[1]) if $_[0]->{additionalItems};
130 1845 100       3460 return _guessed_right(array => $_[1]) if $_[0]->{items};
131 1829 100       3580 return _guessed_right(array => $_[1]) if $_[0]->{uniqueItems};
132             return _guessed_right(array => $_[1])
133             if defined $_[0]->{maxItems}
134 1816 100 100     6014 or defined $_[0]->{minItems};
135 1800 100       3635 return _guessed_right(string => $_[1]) if $_[0]->{pattern};
136             return _guessed_right(string => $_[1])
137             if defined $_[0]->{maxLength}
138 1760 100 100     5366 or defined $_[0]->{minLength};
139 1731 100       3216 return _guessed_right(number => $_[1]) if $_[0]->{multipleOf};
140             return _guessed_right(number => $_[1])
141             if defined $_[0]->{maximum}
142 1719 100 100     5284 or defined $_[0]->{minimum};
143 1686 100       3307 return 'const' if exists $_[0]->{const};
144 1669         4779 return '';
145             }
146              
147             sub uniq {
148 30     30 1 59 my %uniq;
149 30         76 grep { !$uniq{$_}++ } @_;
  73         302  
150             }
151              
152             # _guessed_right($type, $data);
153             sub _guessed_right {
154 310 100   310   995 return $_[0] if !defined $_[1];
155 273 100       1182 return $_[0] if $_[0] eq data_type $_[1], [{type => $_[0]}];
156 19         127 return '';
157             }
158              
159             sub _schema_extract {
160 33     33   68 my ($data, $path, $pos, $cb) = @_, my $tied;
161              
162 33         112 while (@$path) {
163 44         68 my $p = shift @$path;
164              
165 44 100       84 unless (defined $p) {
166 9         15 my $i = 0;
167             return Mojo::Collection->new(
168             map {
169 21         69 _schema_extract($_->[0], [@$path], json_pointer($pos, $_->[1]), $cb)
170 17         35 } ref $data eq 'ARRAY' ? map { [$_, $i++] }
171 9 50       34 @$data : ref $data eq 'HASH' ? map { [$data->{$_}, $_] }
  4 100       11  
172             sort keys %$data : [$data, '']
173             );
174             }
175              
176 35         65 $p =~ s!~1!/!g;
177 35         67 $p =~ s/~0/~/g;
178 35 100       67 $pos = json_pointer $pos, $p if $cb;
179              
180 35 100 100     168 if (ref $data eq 'HASH' and exists $data->{$p}) {
    100 66        
      66        
181 26         45 $data = $data->{$p};
182             }
183             elsif (ref $data eq 'ARRAY' and $p =~ /^\d+$/ and @$data > $p) {
184 2         5 $data = $data->[$p];
185             }
186             else {
187 7         39 return undef;
188             }
189              
190 28 100 100     101 $data = $tied->schema if ref $data eq 'HASH' and $tied = tied %$data;
191             }
192              
193 17 100       35 return $cb->($data, $pos) if $cb;
194 15         57 return $data;
195             }
196              
197             1;
198              
199             =encoding utf8
200              
201             =head1 NAME
202              
203             JSON::Validator::Util - Utility functions for JSON::Validator
204              
205             =head1 DESCRIPTION
206              
207             L is a package containing utility functions for
208             L. Each of the L can be imported.
209              
210             =head1 FUNCTIONS
211              
212             =head2 data_checksum
213              
214             $str = data_checksum $any;
215              
216             Will create a checksum for any data structure stored in C<$any>.
217              
218             =head2 data_section
219              
220             $str = data_section "Some::Module", "file.json";
221             $str = data_section "Some::Module", "file.json", {encode => 'UTF-8'};
222              
223             Same as L, but will also look up the file in any
224             inherited class.
225              
226             =head2 data_type
227              
228             $str = data_type $any;
229             $str = data_type $any, [@schemas];
230             $str = data_type $any, [{type => "integer", ...}];
231              
232             Returns the JSON type for C<$any>. C<$str> can be array, boolean, integer,
233             null, number object or string. Note that a list of schemas need to be provided
234             to differentiate between "integer" and "number".
235              
236             =head2 is_type
237              
238             $bool = is_type $any, $class;
239             $bool = is_type $any, $type; # $type = "ARRAY", "BOOL", "HASH", "NUM" ...
240              
241             Checks if C<$any> is a, or inherits from, C<$class> or C<$type>. Two special
242             types can be checked:
243              
244             =over 2
245              
246             =item * BOOL
247              
248             Checks if C<$any> is a boolean value. C<$any> is considered boolean if it is an
249             object inheriting from L or is another object that
250             stringifies to "1" or "0".
251              
252             =item * NUM
253              
254             Checks if C<$any> is indeed a number.
255              
256             =back
257              
258             =head2 json_pointer
259              
260             $str = json_pointer $path, $append;
261              
262             Will concat C<$append> on to C<$path>, but will also escape the two special
263             characters "~" and "/" in C<$append>.
264              
265             =head2 prefix_errors
266              
267             @errors = prefix_errors $prefix, @errors;
268              
269             Consider this internal for now.
270              
271             =head2 schema_extract
272              
273             $data = schema_extract $any, $json_pointer;
274             $data = schema_extract $any, "/x/cool_beans/y";
275             $collection = schema_extract $any, ["x", undef, "y"];
276             schema_extract $any, $json_pointer, sub { my ($data, $json_pointer) = @_ };
277              
278             The basic usage is to extract data from C<$any>, using a C<$json_pointer> -
279             L. It can however be used in a
280             more complex way by passing in an array-ref, instead of a plain string. The
281             array-ref can contain C values, will result in extracting any element
282             on that point, regardsless of value. In that case a L will
283             be returned.
284              
285             A callback can also be given. This callback will be called each time the
286             C<$json_pointer> matches some data, and will pass in the C<$json_pointer> at
287             that place.
288              
289             In addition, if the C<$json_pointer> points to a L at any
290             point, the "$ref" will be followed, while if you used L,
291             it would return either the L or C.
292              
293             Even though L has special capabilities for handling a
294             JSON-Schema, it can be used for any data-structure, just like
295             L.
296              
297             =head2 schema_type
298              
299             $str = schema_type $hash_ref;
300             $str = schema_type $hash_ref, $any;
301              
302             Looks at C<$hash_ref> and tries to figure out what kind of type the schema
303             represents. C<$str> can be "array", "const", "number", "object", "string", or
304             fallback to empty string if the correct type could not be figured out.
305              
306             C<$any> can be provided to double check the type, so if C<$hash_ref> describes
307             an "object", but C<$any> is an array-ref, then C<$str> will become an empty
308             string. Example:
309              
310             # $str = "";
311             $str = schema {additionalProperties => false}, [];
312              
313             # $str = "object"
314             $str = schema {additionalProperties => false};
315             $str = schema {additionalProperties => false}, {};
316              
317             Note that this process is relatively slow, so it will make your validation
318             faster if you specify "type". Both of the two below is valid, but the one with
319             "type" will be faster.
320              
321             {"type": "object", "properties": {}} # Faster
322             {"properties": {}} # Slower
323              
324             =head2 uniq
325              
326             @items = uniq @items;
327              
328             See L.
329              
330             =head1 SEE ALSO
331              
332             L.
333              
334             =cut