File Coverage

blib/lib/JSONSchema/Validator/Util.pm
Criterion Covered Total %
statement 115 178 64.6
branch 47 92 51.0
condition 23 42 54.7
subroutine 29 33 87.8
pod 0 17 0.0
total 214 362 59.1


line stmt bran cond sub pod time code
1             package JSONSchema::Validator::Util;
2              
3             # ABSTRACT: Useful functions
4              
5 6     6   41 use strict;
  6         12  
  6         165  
6 6     6   29 use warnings;
  6         11  
  6         170  
7              
8 6     6   28 use URI 1.00;
  6         147  
  6         142  
9 6     6   35 use File::Basename;
  6         11  
  6         409  
10 6     6   49 use B;
  6         18  
  6         230  
11 6     6   33 use Carp 'croak';
  6         11  
  6         270  
12              
13 6     6   35 use Scalar::Util 'looks_like_number';
  6         12  
  6         665  
14              
15             our @ISA = 'Exporter';
16             our @EXPORT_OK = qw(
17             json_encode json_decode user_agent_get serialize unbool
18             round read_file is_type detect_type get_resource decode_content
19             data_section
20             );
21              
22 6         1062 use constant FILE_SUFFIX_TO_MIME_TYPE => {
23             'yaml' => 'text/vnd.yaml',
24             'yml' => 'text/vnd.yaml',
25             'json' => 'application/json'
26 6     6   65 };
  6         11  
27              
28 6         475 use constant TYPE_MAP => {
29             'array' => \&is_array,
30             'boolean' => \&is_bool,
31             'integer' => \&is_integer,
32             'number' => \&is_number,
33             'object' => \&is_object,
34             'null' => \&is_null, # for OAS30 null is not defined
35             'string' => \&is_string,
36              
37             # it is for some buggy code
38             '_ref' => \&is_ref
39 6     6   41 };
  6         13  
40              
41             # such order is required
42 6     6   39 use constant TYPE_LIST => ['array', 'object', 'null', '_ref', 'integer', 'number', 'boolean', 'string'];
  6         18  
  6         3820  
43              
44             BEGIN {
45             # YAML
46 6 50   6   26 if (eval { require YAML::XS; YAML::XS->VERSION(0.67); 1; }) {
  6 50       860  
  0         0  
  0         0  
47 0         0 *yaml_load = sub { local $YAML::XS::Boolean = 'JSON::PP'; YAML::XS::Load(@_) };
  0         0  
  0         0  
48             }
49 6         635 elsif (eval { require YAML::PP; 1; }) {
  0         0  
50 0         0 my $pp = YAML::PP->new(boolean => 'JSON::PP');
51 0         0 *yaml_load = sub { $pp->load_string(@_) };
  0         0  
52             } else {
53 6     0   40 *yaml_load = sub { croak 'No YAML package installed' };
  0         0  
54             }
55              
56             # JSON
57 6         23 my $json_class;
58 6 50       13 if (eval { require Cpanel::JSON::XS; 1; }) {
  6 0       5514  
  6         21853  
59 6         16 $json_class = 'Cpanel::JSON::XS';
60 0         0 } elsif (eval { require JSON::XS; JSON::XS->VERSION(3.0); 1; }) {
  0         0  
  0         0  
61 0         0 $json_class = 'JSON::XS';
62             } else {
63 0         0 require JSON::PP;
64 0         0 $json_class = 'JSON::PP';
65             }
66 6         46 my $json = $json_class->new->canonical(1)->utf8;
67 6     8   35 *json_encode = sub { $json->encode(@_); };
  8         106  
68 6     91   21 *json_decode = sub { $json->decode(@_); };
  91         6230  
69              
70             # UserAgent
71 6 50       11 if (eval { require LWP::UserAgent; 1; }) {
  6 50       773  
  0         0  
72 0         0 my $ua = LWP::UserAgent->new;
73             *user_agent_get = sub {
74 0         0 my $uri = shift;
75 0         0 my $response = $ua->get($uri);
76 0 0       0 if ($response->is_success) {
77 0         0 return $response->decoded_content, $response->headers->content_type;
78             }
79 0         0 croak "Can not get uri $uri";
80 0         0 };
81 6         708 } elsif (eval { require Mojo::UserAgent; 1; }) {
  0         0  
82 0         0 my $ua = Mojo::UserAgent->new;
83             *user_agent_get = sub {
84 0         0 my $uri = shift;
85 0         0 my $response = $ua->get($uri)->result;
86 0 0       0 if ($response->is_success) {
87 0         0 return $response->body, $response->headers->content_type;
88             }
89 0         0 croak "Can not get uri $uri";
90 0         0 };
91             } else {
92 6     0   7767 *user_agent_get = sub { croak 'No UserAgent package installed' };
  0         0  
93             }
94             }
95              
96             sub unbool {
97 8     8 0 38 my $x = shift;
98 8 50       30 return "$x" if ref $x eq 'JSON::PP::Boolean';
99 0 0       0 return $x if ref $x;
100 0 0 0     0 return '1' if $x && $x eq '1';
101 0 0 0     0 return '0' if !defined $x || $x eq '0' || $x eq '';
      0        
102 0         0 return $x;
103             }
104              
105 8     8 0 25 sub serialize { json_encode(shift) }
106              
107             sub round {
108 0     0 0 0 my $value = shift;
109 0 0       0 return int($value + ($value >= 0 ? 0.5 : -0.5));
110             }
111              
112             # scheme_handlers - map[scheme -> handler]
113             # uri - string
114             sub get_resource {
115 31     31 0 23842 my ($scheme_handlers, $resource) = @_;
116 31         130 my $uri = URI->new($resource);
117              
118 31         1776 for my $s ('http', 'https') {
119 62 50       268 $scheme_handlers->{$s} = \&user_agent_get unless exists $scheme_handlers->{$s};
120             }
121              
122 31         126 my $scheme = $uri->scheme;
123              
124 31         798 my ($response, $mime_type);
125 31 50       92 if ($scheme) {
126 31 50       134 if (exists $scheme_handlers->{$scheme}) {
    50          
127 0         0 ($response, $mime_type) = $scheme_handlers->{$scheme}->($uri->as_string);
128             } elsif ($scheme eq 'file') {
129 31         128 ($response, $mime_type) = read_file($uri->file);
130             } else {
131 0         0 croak 'Unsupported scheme of uri ' . $uri->as_string;
132             }
133             } else {
134             # may it is path of local file without scheme?
135 0         0 ($response, $mime_type) = read_file($resource);
136             }
137 31         203 return ($response, $mime_type);
138             }
139              
140             sub decode_content {
141 77     77 0 252 my ($response, $mime_type, $resource) = @_;
142              
143 77         121 my $schema;
144 77 50       228 if ($mime_type) {
145 77 50       423 if ($mime_type =~ m{yaml}) {
    50          
146 0         0 $schema = eval{ yaml_load($response) };
  0         0  
147 0 0       0 croak "Failed to load resource $resource as $mime_type ( $@ )" if $@;
148             }
149             elsif ($mime_type =~ m{json}) {
150 77         165 $schema = eval{ json_decode($response) };
  77         296  
151 77 50       274 croak "Failed to load resource $resource as $mime_type ( $@ )" if $@;
152             }
153             }
154 77 50       203 unless ($schema) {
155             # try to guess
156 0         0 $schema = eval { json_decode($response) };
  0         0  
157 0 0       0 $schema = eval { yaml_load($response) } if $@;
  0         0  
158 0 0       0 croak "Unsupported mime type $mime_type of resource $resource" unless $schema;
159             }
160              
161 77         249 return $schema;
162             }
163              
164             sub read_file {
165 77     77 0 22355 my $path = shift;
166 77 50       1782 croak "File $path does not exists" unless -e $path;
167 77 50       558 croak "File $path does not have read permission" unless -r _;
168 77         222 my $size = -s _;
169              
170 77         4218 my ($filename, $dir, $suffix) = File::Basename::fileparse($path, 'yml', 'yaml', 'json');
171 77 50       314 croak "Unknown file format of $path" unless $suffix;
172              
173 77         223 my $mime_type = FILE_SUFFIX_TO_MIME_TYPE->{$suffix};
174              
175 77 50       3668 open my $fh, '<', $path or croak "Open file $path error: $!";
176 77         2767 read $fh, (my $file_content), $size;
177 77         998 close $fh;
178              
179 77         667 return $file_content, $mime_type;
180             }
181              
182             # params: $value, $type, $is_strict
183             sub is_type {
184 7711 50   7711 0 16220 return 0 unless exists TYPE_MAP->{$_[1]};
185 7711         15158 return TYPE_MAP->{$_[1]}->($_[0], $_[2]);
186             }
187              
188             # params: $value, $is_strict
189             sub detect_type {
190 45     45 0 82 for my $type (@{TYPE_LIST()}) {
  45         93  
191 321 100       609 return $type if TYPE_MAP->{$type}->(@_);
192             }
193             # it must be unreachable code
194 0           croak 'Unknown type detected';
195             }
196              
197             # params: $value, $is_strict
198             sub is_array {
199 896     896 0 3095 return ref $_[0] eq 'ARRAY';
200             }
201              
202             # params: $value, $is_strict
203             sub is_bool {
204 2965     2965 0 5065 my $type = ref $_[0];
205 2965 50 66     11985 return 1 if $type eq 'JSON::PP::Boolean' or
      66        
206             $type eq 'JSON::XS::Boolean' or
207             $type eq 'Cpanel::JSON::XS::Boolean';
208 2900 100       8571 return 0 if $_[1]; # is strict
209 7   66     36 my $is_number = looks_like_number($_[0]) && ($_[0] == 1 || $_[0] == 0);
210 7   100     112 my $is_string = defined $_[0] && $_[0] eq '';
211 7         17 my $is_undef = !defined $_[0];
212 7 100 66     42 return 1 if $is_number || $is_string || $is_undef;
      100        
213 5         25 return 0;
214             }
215              
216             # params: $value, $is_strict
217             sub is_integer {
218 220 100   220 0 1359 return 1 if B::svref_2object(\$_[0])->FLAGS & B::SVf_IOK();
219 110 100       384 return 0 if $_[1]; # is strict
220 42 100       110 return 0 if ref $_[0];
221 34 100 100     211 return 1 if looks_like_number($_[0]) && int($_[0]) == $_[0];
222 14         52 return 0;
223             }
224              
225             # params: $value, $is_strict
226             sub is_number {
227 1074 100   1074 0 4653 return 1 if B::svref_2object(\$_[0])->FLAGS & (B::SVf_IOK() | B::SVf_NOK());
228 967 100       5021 return 0 if $_[1]; # is strict
229 9 100       31 return 0 if ref $_[0];
230 5 100       24 return 1 if looks_like_number($_[0]);
231 4         19 return 0;
232             }
233              
234             # params: $value, $is_strict
235             sub is_ref {
236 40     40 0 72 my $ref = ref $_[0];
237 40 100       107 return 0 unless $ref;
238 10 0 33     33 return 0 if $ref eq 'JSON::PP::Boolean' ||
      33        
239             $ref eq 'HASH' ||
240             $ref eq 'ARRAY';
241 0         0 return 1;
242             }
243              
244             # params: $value, $is_strict
245             sub is_object {
246 2243     2243 0 7045 return ref $_[0] eq 'HASH';
247             }
248              
249             # params: $value, $is_strict
250             sub is_null {
251 431     431 0 1285 return !(defined $_[0]);
252             }
253              
254             # params: $value, $is_strict
255             sub is_string {
256 467 100 100 467 0 1415 return !(ref $_[0]) && !is_number(@_) && defined $_[0] if $_[1]; # is strict
257 157   33     668 return !(ref $_[0]) && defined $_[0];
258             }
259              
260             sub data_section {
261 0     0 0   my $class = shift;
262 6     6   58 my $handle = do { no strict 'refs'; \*{"${class}::DATA"} };
  6         14  
  6         1077  
  0            
  0            
  0            
263 0 0         return unless fileno $handle;
264 0           seek $handle, 0, 0;
265 0           local $/ = undef;
266 0           my $data = <$handle>;
267 0           $data =~ s/^.*\n__DATA__\r?\n//s;
268 0           $data =~ s/\r?\n__END__\r?\n.*$//s;
269 0           return $data;
270             }
271              
272             1;
273              
274             __END__