File Coverage

blib/lib/JSONSchema/Validator/Util.pm
Criterion Covered Total %
statement 114 177 64.4
branch 48 92 52.1
condition 19 36 52.7
subroutine 29 33 87.8
pod 0 17 0.0
total 210 355 59.1


line stmt bran cond sub pod time code
1             package JSONSchema::Validator::Util;
2              
3             # ABSTRACT: Useful functions
4              
5 6     6   42 use strict;
  6         14  
  6         186  
6 6     6   29 use warnings;
  6         13  
  6         169  
7              
8 6     6   30 use URI 1.00;
  6         114  
  6         118  
9 6     6   30 use File::Basename;
  6         12  
  6         380  
10 6     6   44 use B;
  6         22  
  6         284  
11 6     6   35 use Carp 'croak';
  6         12  
  6         286  
12              
13 6     6   37 use Scalar::Util 'looks_like_number';
  6         23  
  6         964  
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         956 use constant FILE_SUFFIX_TO_MIME_TYPE => {
23             'yaml' => 'text/vnd.yaml',
24             'yml' => 'text/vnd.yaml',
25             'json' => 'application/json'
26 6     6   44 };
  6         12  
27              
28 6         594 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   51 };
  6         13  
40              
41             # such order is required
42 6     6   42 use constant TYPE_LIST => ['array', 'object', 'null', '_ref', 'integer', 'number', 'boolean', 'string'];
  6         12  
  6         3931  
43              
44             BEGIN {
45             # YAML
46 6 50   6   26 if (eval { require YAML::XS; YAML::XS->VERSION(0.67); 1; }) {
  6 50       868  
  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         691 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   56 *yaml_load = sub { croak 'No YAML package installed' };
  0         0  
54             }
55              
56             # JSON
57 6         26 my $json_class;
58 6 50       13 if (eval { require Cpanel::JSON::XS; 1; }) {
  6 0       5758  
  6         22717  
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         47 my $json = $json_class->new->canonical(1)->utf8;
67 6     8   35 *json_encode = sub { $json->encode(@_); };
  8         189  
68 6     91   22 *json_decode = sub { $json->decode(@_); };
  91         6395  
69              
70             # UserAgent
71 6 50       16 if (eval { require LWP::UserAgent; 1; }) {
  6 50       848  
  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         747 } 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   7831 *user_agent_get = sub { croak 'No UserAgent package installed' };
  0         0  
93             }
94             }
95              
96             sub unbool {
97 8     8 0 43 my $x = shift;
98 8 50       36 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 23 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 23803 my ($scheme_handlers, $resource) = @_;
116 31         134 my $uri = URI->new($resource);
117              
118 31         1837 for my $s ('http', 'https') {
119 62 50       304 $scheme_handlers->{$s} = \&user_agent_get unless exists $scheme_handlers->{$s};
120             }
121              
122 31         130 my $scheme = $uri->scheme;
123              
124 31         827 my ($response, $mime_type);
125 31 50       140 if ($scheme) {
126 31 50       155 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         130 ($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         225 return ($response, $mime_type);
138             }
139              
140             sub decode_content {
141 77     77 0 214 my ($response, $mime_type, $resource) = @_;
142              
143 77         127 my $schema;
144 77 50       234 if ($mime_type) {
145 77 50       511 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         223 $schema = eval{ json_decode($response) };
  77         225  
151 77 50       275 croak "Failed to load resource $resource as $mime_type ( $@ )" if $@;
152             }
153             }
154 77 50       219 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         256 return $schema;
162             }
163              
164             sub read_file {
165 77     77 0 22501 my $path = shift;
166 77 50       1895 croak "File $path does not exists" unless -e $path;
167 77 50       617 croak "File $path does not have read permission" unless -r _;
168 77         247 my $size = -s _;
169              
170 77         4415 my ($filename, $dir, $suffix) = File::Basename::fileparse($path, 'yml', 'yaml', 'json');
171 77 50       306 croak "Unknown file format of $path" unless $suffix;
172              
173 77         248 my $mime_type = FILE_SUFFIX_TO_MIME_TYPE->{$suffix};
174              
175 77 50       3841 open my $fh, '<', $path or croak "Open file $path error: $!";
176 77         3046 read $fh, (my $file_content), $size;
177 77         1014 close $fh;
178              
179 77         784 return $file_content, $mime_type;
180             }
181              
182             # params: $value, $type, $is_strict
183             sub is_type {
184 7711 50   7711 0 17666 return 0 unless exists TYPE_MAP->{$_[1]};
185 7711         15876 return TYPE_MAP->{$_[1]}->($_[0], $_[2]);
186             }
187              
188             # params: $value, $is_strict
189             sub detect_type {
190 45     45 0 84 for my $type (@{TYPE_LIST()}) {
  45         90  
191 321 100       615 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 3236 return ref $_[0] eq 'ARRAY';
200             }
201              
202             # params: $value, $is_strict
203             sub is_bool {
204 2965 100   2965 0 6702 return 1 if ref $_[0] eq 'JSON::PP::Boolean';
205 2900 100       8827 return 0 if $_[1]; # is strict
206 7   66     42 my $is_number = looks_like_number($_[0]) && ($_[0] == 1 || $_[0] == 0);
207 7   100     44 my $is_string = defined $_[0] && $_[0] eq '';
208 7         14 my $is_undef = !defined $_[0];
209 7 100 66     49 return 1 if $is_number || $is_string || $is_undef;
      100        
210 5         22 return 0;
211             }
212              
213             # params: $value, $is_strict
214             sub is_integer {
215 220 100   220 0 1778 return 1 if B::svref_2object(\$_[0])->FLAGS & B::SVf_IOK();
216 110 100       424 return 0 if $_[1]; # is strict
217 42 100       138 return 0 if ref $_[0];
218 34 100 100     251 return 1 if looks_like_number($_[0]) && int($_[0]) == $_[0];
219 14         60 return 0;
220             }
221              
222             # params: $value, $is_strict
223             sub is_number {
224 1074 100   1074 0 5038 return 1 if B::svref_2object(\$_[0])->FLAGS & (B::SVf_IOK() | B::SVf_NOK());
225 967 100       5489 return 0 if $_[1]; # is strict
226 9 100       37 return 0 if ref $_[0];
227 5 100       33 return 1 if looks_like_number($_[0]);
228 4         17 return 0;
229             }
230              
231             # params: $value, $is_strict
232             sub is_ref {
233 40     40 0 69 my $ref = ref $_[0];
234 40 100       122 return 0 unless $ref;
235 10 0 33     37 return 0 if $ref eq 'JSON::PP::Boolean' ||
      33        
236             $ref eq 'HASH' ||
237             $ref eq 'ARRAY';
238 0         0 return 1;
239             }
240              
241             # params: $value, $is_strict
242             sub is_object {
243 2243     2243 0 8172 return ref $_[0] eq 'HASH';
244             }
245              
246             # params: $value, $is_strict
247             sub is_null {
248 431     431 0 1340 return !(defined $_[0]);
249             }
250              
251             # params: $value, $is_strict
252             sub is_string {
253 467 100 100 467 0 1506 return !(ref $_[0]) && !is_number(@_) && defined $_[0] if $_[1]; # is strict
254 157   33     698 return !(ref $_[0]) && defined $_[0];
255             }
256              
257             sub data_section {
258 0     0 0   my $class = shift;
259 6     6   73 my $handle = do { no strict 'refs'; \*{"${class}::DATA"} };
  6         22  
  6         1095  
  0            
  0            
  0            
260 0 0         return unless fileno $handle;
261 0           seek $handle, 0, 0;
262 0           local $/ = undef;
263 0           my $data = <$handle>;
264 0           $data =~ s/^.*\n__DATA__\r?\n//s;
265 0           $data =~ s/\r?\n__END__\r?\n.*$//s;
266 0           return $data;
267             }
268              
269             1;
270              
271             __END__