File Coverage

blib/lib/JSON/Schema/Fit.pm
Criterion Covered Total %
statement 123 129 95.3
branch 52 60 86.6
condition 32 39 82.0
subroutine 32 33 96.9
pod 10 10 100.0
total 249 271 91.8


line stmt bran cond sub pod time code
1             package JSON::Schema::Fit;
2             $JSON::Schema::Fit::VERSION = '0.07';
3             # ABSTRACT: adjust data structure according to json-schema
4              
5              
6 2     2   183188 use 5.010;
  2         28  
7 2     2   11 use strict;
  2         4  
  2         40  
8 2     2   9 use warnings;
  2         5  
  2         48  
9 2     2   1088 use utf8;
  2         27  
  2         11  
10              
11 2     2   63 use Carp;
  2         5  
  2         105  
12              
13 2     2   13 use JSON;
  2         3  
  2         9  
14 2     2   1395 use Storable;
  2         5727  
  2         118  
15 2     2   18 use Scalar::Util qw/reftype/;
  2         5  
  2         101  
16 2     2   11 use List::Util qw/first none/;
  2         4  
  2         232  
17 2     2   913 use Math::Round qw/round nearest/;
  2         15320  
  2         3365  
18              
19              
20              
21              
22 13     13 1 35 sub booleans { return _attr('booleans', @_); }
23              
24              
25 52     52 1 90 sub numbers { return _attr('numbers', @_); }
26              
27              
28              
29 31     31 1 55 sub round_numbers { return _attr('round_numbers', @_); }
30              
31              
32 31     31 1 58 sub clamp_numbers { return _attr('clamp_numbers', @_); }
33              
34              
35              
36 21     21 1 38 sub strings { return _attr('strings', @_); }
37              
38              
39 15     15 1 36 sub hash_keys { return _attr('hash_keys', @_); }
40              
41              
42              
43 17     17 1 31 sub fill_defaults { return _attr('fill_defaults', @_); }
44              
45              
46 16     16 1 34 sub replace_invalid_values { return _attr('replace_invalid_values', @_); }
47              
48              
49              
50             # Store valid options as well as default values
51             my %valid_option =(
52             ( map { ($_ => 1) } qw!booleans numbers round_numbers strings hash_keys! ),
53             ( map { ($_ => 0) } qw!clamp_numbers fill_defaults replace_invalid_values! ),
54             );
55              
56             sub new {
57 12     12 1 5069 my ($class, %opts) = @_;
58 12         21 my $self = bless {}, $class;
59 12         58 for my $k (keys %opts) {
60 19 50       43 next unless exists $valid_option{$k};
61 19         36 _attr($k, $self, $opts{$k});
62             }
63 12         57 return $self
64             }
65              
66             sub _attr {
67 215     215   300 my $attr = shift;
68 215         278 my $self = shift;
69              
70 215 100       372 if (@_) {
71 19         44 return $self->{$attr} = shift;
72             } else {
73 196   100     789 return $self->{$attr} //= $valid_option{$attr};
74             }
75             }
76              
77              
78             sub get_adjusted {
79 96     96 1 49913 my ($self, $struc, $schema, $jpath) = @_;
80              
81 96 100 66     465 return $struc if !ref $schema || reftype $schema ne 'HASH';
82              
83 93 100 100     265 if (
      100        
84             exists $schema->{default} && (
85             !defined $struc && $self->fill_defaults ||
86             $self->replace_invalid_values && !$self->_is_valid($struc, $schema)
87             )
88             ) {
89 6         28 return $self->_default_value($schema);
90             }
91              
92 87         209 my $method = $self->_adjuster_by_type($schema->{type});
93 87 100       181 return $struc if !$method;
94 85         194 return $self->$method($struc, $schema, $jpath);
95             }
96              
97              
98             sub _is_valid {
99 7     7   16 my ($self, $struc, $schema) = @_;
100              
101 7 100       21 if (my $enum = $schema->{enum}) {
102 6 100   15   39 return '' if none {my $v = $_; $struc eq $v} @$enum;
  15         22  
  15         62  
103             }
104              
105 3         15 return 1;
106              
107              
108 0         0 return grep { Compare($struc, $_) } @{$schema->{enum}};
  0         0  
  0         0  
109             }
110              
111              
112             sub _default_value {
113 9     9   24 my ($self, $schema) = @_;
114 9 50       28 return if !exists $schema->{default};
115              
116 9         15 my $default = $schema->{default};
117 9 50       38 return ref $default ? Storable::dclone($default) : $default;
118             }
119              
120              
121             sub _adjuster_by_type {
122 87     87   153 my ($self, $type) = @_;
123              
124 87 100       167 return if !$type;
125 85         159 my $method = "_get_adjusted_$type";
126 85 50       325 return $method if $self->can($method);
127 0         0 return;
128             }
129              
130              
131             sub _get_adjusted_boolean {
132 13     13   29 my ($self, $struc, $schema, $jpath) = @_;
133              
134 13 100       27 return $struc if !$self->booleans();
135 11 100       48 return JSON::true if $struc;
136 1         5 return JSON::false;
137             }
138              
139              
140             sub _get_adjusted_integer {
141 19     19   43 my ($self, $struc, $schema, $jpath) = @_;
142              
143 19 100       42 return $struc if !$self->numbers();
144 17         62 my $result = $self->_get_adjusted_number($struc, $schema, $jpath);
145 17         53 return round($result);
146             }
147              
148              
149             sub _get_adjusted_number {
150 33     33   67 my ($self, $struc, $schema, $jpath) = @_;
151              
152 33 100       57 return $struc if !$self->numbers();
153 31         87 my $result = 0+$struc;
154              
155 31 100       66 if ($self->round_numbers) {
156 29   66     79 my $quantum = $schema->{multipleOf} || $schema->{divisibleBy};
157 29 100       85 $result = nearest $quantum, $result if $quantum;
158             }
159              
160 31 100       294 if ($self->clamp_numbers) {
161 17 100 100     57 $result = $schema->{maximum} if exists $schema->{maximum} and $result > $schema->{maximum};
162 17 100 100     46 $result = $schema->{minimum} if exists $schema->{minimum} and $result < $schema->{minimum};
163             }
164              
165 31         76 return $result;
166             }
167              
168              
169             sub _get_adjusted_string {
170 21     21   39 my ($self, $struc, $schema, $jpath) = @_;
171              
172 21 100       41 return $struc if !$self->strings();
173 17         63 return "$struc";
174             }
175              
176              
177             sub _get_adjusted_array {
178 1     1   3 my ($self, $struc, $schema, $jpath) = @_;
179              
180 1 50       5 croak "Structure is not ARRAY at $jpath" if reftype $struc ne 'ARRAY';
181              
182 1         2 my $result = [];
183 1         5 for my $i ( 0 .. $#$struc ) {
184 3         32 push @$result, $self->get_adjusted($struc->[$i], $schema->{items}, $self->_jpath($jpath, $i));
185             }
186              
187 1         13 return $result;
188             }
189              
190             sub _get_adjusted_object {
191 15     15   32 my ($self, $struc, $schema, $jpath) = @_;
192              
193 15 50       47 croak "Structure is not HASH at $jpath" if reftype $struc ne 'HASH';
194              
195 15         24 my $result = {};
196 15         27 my $keys_re;
197              
198 15   50     48 my $properties = $schema->{properties} || {};
199 15   50     55 my $p_properties = $schema->{patternProperties} || {};
200              
201 15 100 100     35 if ($self->hash_keys() && exists $schema->{additionalProperties} && !$schema->{additionalProperties}) {
      66        
202             my $keys_re_text = join q{|}, (
203             keys %$p_properties,
204 10         47 map {quotemeta} keys %$properties,
  55         116  
205             );
206 10         119 $keys_re = qr{^$keys_re_text$}x;
207             }
208              
209 15         54 for my $key (keys %$struc) {
210 71 100 100     594 next if $keys_re && $key !~ $keys_re;
211              
212 61         108 my $subschema = $properties->{$key};
213 61 50 66 0   184 if (my $re_key = !$subschema && first {$key =~ /$_/x} keys %$p_properties) {
  0         0  
214 0         0 $subschema = $p_properties->{$re_key};
215             }
216              
217 61         135 $result->{$key} = $self->get_adjusted($struc->{$key}, $subschema, $self->_jpath($jpath, $key));
218             }
219              
220 15 100       50 if ($self->fill_defaults) {
221 6         18 for my $key (keys %$properties) {
222 16 100       36 next if exists $result->{$key};
223 3         8 my $subschema = $properties->{$key};
224 3 50       11 next if !exists $subschema->{default};
225 3         8 $result->{$key} = $self->_default_value($subschema);
226             }
227             }
228              
229 15         74 return $result;
230             }
231              
232              
233             sub _jpath {
234 64     64   130 my ($self, $path, $key) = @_;
235 64   50     229 $path //= q{$};
236              
237 64 100       348 return "$path.$key" if $key =~ /^[_A-Za-z]\w*$/x;
238              
239 3         8 $key =~ s/(['\\])/\\$1/gx;
240 3         12 return $path . "['$key']";
241             }
242              
243              
244             1;
245              
246             __END__