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   34 use strict;
  6         12  
  6         155  
6 6     6   28 use warnings;
  6         8  
  6         140  
7              
8 6     6   42 use URI 1.00;
  6         145  
  6         190  
9 6     6   32 use File::Basename;
  6         10  
  6         401  
10 6     6   33 use B;
  6         13  
  6         261  
11 6     6   29 use Carp 'croak';
  6         11  
  6         231  
12              
13 6     6   110 use Scalar::Util 'looks_like_number';
  6         9  
  6         567  
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         936 use constant FILE_SUFFIX_TO_MIME_TYPE => {
23             'yaml' => 'text/vnd.yaml',
24             'yml' => 'text/vnd.yaml',
25             'json' => 'application/json'
26 6     6   56 };
  6         23  
27              
28 6         423 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   35 };
  6         12  
40              
41             # such order is required
42 6     6   41 use constant TYPE_LIST => ['array', 'object', 'null', '_ref', 'integer', 'number', 'boolean', 'string'];
  6         11  
  6         3369  
43              
44             BEGIN {
45             # YAML
46 6 50   6   23 if (eval { require YAML::XS; YAML::XS->VERSION(0.67); 1; }) {
  6 50       777  
  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         529 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   36 *yaml_load = sub { croak 'No YAML package installed' };
  0         0  
54             }
55              
56             # JSON
57 6         20 my $json_class;
58 6 50       9 if (eval { require Cpanel::JSON::XS; 1; }) {
  6 0       4739  
  6         19458  
59 6         15 $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         42 my $json = $json_class->new->canonical(1)->utf8;
67 6     8   36 *json_encode = sub { $json->encode(@_); };
  8         70  
68 6     92   22 *json_decode = sub { $json->decode(@_); };
  92         5712  
69              
70             # UserAgent
71 6 50       13 if (eval { require LWP::UserAgent; 1; }) {
  6 50       666  
  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         580 } 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   6388 *user_agent_get = sub { croak 'No UserAgent package installed' };
  0         0  
93             }
94             }
95              
96             sub unbool {
97 8     8 0 31 my $x = shift;
98 8 50       26 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 19 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 20602 my ($scheme_handlers, $resource) = @_;
116 31         117 my $uri = URI->new($resource);
117              
118 31         1556 for my $s ('http', 'https') {
119 62 50       234 $scheme_handlers->{$s} = \&user_agent_get unless exists $scheme_handlers->{$s};
120             }
121              
122 31         102 my $scheme = $uri->scheme;
123              
124 31         690 my ($response, $mime_type);
125 31 50       80 if ($scheme) {
126 31 50       108 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         103 ($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         183 return ($response, $mime_type);
138             }
139              
140             sub decode_content {
141 77     77 0 181 my ($response, $mime_type, $resource) = @_;
142              
143 77         110 my $schema;
144 77 50       206 if ($mime_type) {
145 77 50       380 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         129 $schema = eval{ json_decode($response) };
  77         194  
151 77 50       222 croak "Failed to load resource $resource as $mime_type ( $@ )" if $@;
152             }
153             }
154 77 50       182 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         224 return $schema;
162             }
163              
164             sub read_file {
165 77     77 0 21545 my $path = shift;
166 77 50       1442 croak "File $path does not exists" unless -e $path;
167 77 50       477 croak "File $path does not have read permission" unless -r _;
168 77         186 my $size = -s _;
169              
170 77         3717 my ($filename, $dir, $suffix) = File::Basename::fileparse($path, 'yml', 'yaml', 'json');
171 77 50       256 croak "Unknown file format of $path" unless $suffix;
172              
173 77         188 my $mime_type = FILE_SUFFIX_TO_MIME_TYPE->{$suffix};
174              
175 77 50       3205 open my $fh, '<', $path or croak "Open file $path error: $!";
176 77         2460 read $fh, (my $file_content), $size;
177 77         820 close $fh;
178              
179 77         560 return $file_content, $mime_type;
180             }
181              
182             # params: $value, $type, $is_strict
183             sub is_type {
184 7816 50   7816 0 14162 return 0 unless exists TYPE_MAP->{$_[1]};
185 7816         13509 return TYPE_MAP->{$_[1]}->($_[0], $_[2]);
186             }
187              
188             # params: $value, $is_strict
189             sub detect_type {
190 190     190 0 254 for my $type (@{TYPE_LIST()}) {
  190         354  
191 1110 100       1816 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 1058     1058 0 3144 return ref $_[0] eq 'ARRAY';
200             }
201              
202             # params: $value, $is_strict
203             sub is_bool {
204 3067     3067 0 4340 my $type = ref $_[0];
205 3067 50 66     10845 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 2982 100       7752 return 0 if $_[1]; # is strict
209 59   66     162 my $is_number = looks_like_number($_[0]) && ($_[0] == 1 || $_[0] == 0);
210 59   100     195 my $is_string = defined $_[0] && $_[0] eq '';
211 59         77 my $is_undef = !defined $_[0];
212 59 100 66     239 return 1 if $is_number || $is_string || $is_undef;
      100        
213 47         97 return 0;
214             }
215              
216             # params: $value, $is_strict
217             sub is_integer {
218 337 100   337 0 1771 return 1 if B::svref_2object(\$_[0])->FLAGS & B::SVf_IOK();
219 203 100       480 return 0 if $_[1]; # is strict
220 135 100       321 return 0 if ref $_[0];
221 107 100 100     507 return 1 if looks_like_number($_[0]) && int($_[0]) == $_[0];
222 83         169 return 0;
223             }
224              
225             # params: $value, $is_strict
226             sub is_number {
227 1168 100   1168 0 4534 return 1 if B::svref_2object(\$_[0])->FLAGS & (B::SVf_IOK() | B::SVf_NOK());
228 1044 100       4714 return 0 if $_[1]; # is strict
229 81 100       162 return 0 if ref $_[0];
230 57 100       154 return 1 if looks_like_number($_[0]);
231 56         111 return 0;
232             }
233              
234             # params: $value, $is_strict
235             sub is_ref {
236 142     142 0 216 my $ref = ref $_[0];
237 142 100       362 return 0 unless $ref;
238 30 0 33     103 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 2379     2379 0 6759 return ref $_[0] eq 'HASH';
247             }
248              
249             # params: $value, $is_strict
250             sub is_null {
251 567     567 0 1415 return !(defined $_[0]);
252             }
253              
254             # params: $value, $is_strict
255             sub is_string {
256 512 100 100 512 0 1291 return !(ref $_[0]) && !is_number(@_) && defined $_[0] if $_[1]; # is strict
257 202   33     782 return !(ref $_[0]) && defined $_[0];
258             }
259              
260             sub data_section {
261 0     0 0   my $class = shift;
262 6     6   45 my $handle = do { no strict 'refs'; \*{"${class}::DATA"} };
  6         13  
  6         1044  
  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__