File Coverage

blib/lib/JSONSchema/Validator/Constraints/OAS30.pm
Criterion Covered Total %
statement 109 116 93.9
branch 37 48 77.0
condition 17 20 85.0
subroutine 16 19 84.2
pod 0 11 0.0
total 179 214 83.6


line stmt bran cond sub pod time code
1             package JSONSchema::Validator::Constraints::OAS30;
2              
3             # ABSTRACT: OpenAPI 3.0 specification constraints
4              
5 6     6   40 use strict;
  6         11  
  6         206  
6 6     6   32 use warnings;
  6         12  
  6         166  
7 6     6   32 use URI;
  6         11  
  6         162  
8 6     6   42 use Carp 'croak';
  6         21  
  6         315  
9              
10 6     6   45 use JSONSchema::Validator::JSONPointer 'json_pointer';
  6         13  
  6         247  
11 6     6   45 use JSONSchema::Validator::Error 'error';
  6         12  
  6         251  
12              
13 6     6   51 use parent 'JSONSchema::Validator::Constraints::Draft4';
  6         11  
  6         31  
14              
15             sub type {
16 372     372 0 873 my ($self, $instance, $type, $schema, $instance_path, $schema_path, $data) = @_;
17              
18 372 100       950 if ($self->check_type($instance, 'null')) {
19             return $self->nullable( $instance,
20 2   100     11 $schema->{nullable} // 0,
21             $schema,
22             $instance_path,
23             $schema_path,
24             $data);
25             }
26              
27 370         671 my $result = 1;
28 370 100       830 $result = 0 unless $self->check_type($instance, $type);
29              
30             # # items must be present if type eq array
31             # if ($result && $type eq 'array') {
32             # $result = 0 unless exists $schema->{items};
33             # }
34              
35 370 100       1192 return 1 if $result;
36              
37 10         17 push @{$data->{errors}}, error(
  10         38  
38             message => 'type mismatch',
39             instance_path => $instance_path,
40             schema_path => $schema_path
41             );
42 10         30 return 0;
43             }
44              
45             sub items {
46 15     15 0 67 my ($self, $instance, $items, $schema, $instance_path, $schema_path, $data) = @_;
47 15 50       56 return 1 unless $self->check_type($instance, 'array');
48              
49             # items is object and NOT array
50              
51 15         35 my $result = 1;
52 15         25 for my $i (0 .. $#{$instance}) {
  15         49  
53 28         45 my $item = $instance->[$i];
54 28         67 my $ipath = json_pointer->append($instance_path, $i);
55 28         92 my $r = $self->validator->_validate_schema($item, $items, $ipath, $schema_path, $data);
56 28 100       86 $result = 0 unless $r;
57             }
58 15         34 return $result;
59             }
60              
61             sub nullable {
62 8     8 0 29 my ($self, $instance, $nullable, $schema, $instance_path, $schema_path, $data) = @_;
63             # A true value adds "null" to the allowed type specified by the type keyword, only if type is explicitly defined within the same Schema Object.
64 8 100       21 return 1 unless $schema->{type};
65 6 100       19 return 1 if $nullable;
66 1 50       5 unless (defined $instance) {
67 1         3 push @{$data->{errors}}, error(
  1         5  
68             message => 'instance is nullable',
69             instance_path => $instance_path,
70             schema_path => $schema_path
71             );
72 1         5 return 0;
73             }
74 0         0 return 1;
75             }
76              
77             sub readOnly {
78 14     14 0 37 my ($self, $instance, $readOnly, $schema, $instance_path, $schema_path, $data) = @_;
79 14 50       55 return 1 unless $readOnly;
80 14 100       158 return 1 if $data->{direction} eq 'response';
81              
82 2         6 push @{$data->{errors}}, error(
  2         11  
83             message => 'instance is invalid in request because of readOnly property',
84             instance_path => $instance_path,
85             schema_path => $schema_path
86             );
87 2         16 return 0;
88             }
89              
90             sub writeOnly {
91 11     11 0 30 my ($self, $instance, $writeOnly, $schema, $instance_path, $schema_path, $data) = @_;
92 11 50       68 return 1 unless $writeOnly;
93 11 100       122 return 1 if $data->{direction} eq 'request';
94              
95 2         6 push @{$data->{errors}}, error(
  2         16  
96             message => "instance is invalid in response because of writeOnly property",
97             instance_path => $instance_path,
98             schema_path => $schema_path
99             );
100 2         8 return 0;
101             }
102              
103             sub required {
104 93     93 0 230 my ($self, $instance, $required, $schema, $instance_path, $schema_path, $data) = @_;
105 93 50       233 return 1 unless $self->check_type($instance, 'object');
106              
107 93         192 my $result = 1;
108 93         175 for my $idx (0 .. $#{$required}) {
  93         255  
109 184         323 my $prop = $required->[$idx];
110 184 100       463 next if exists $instance->{$prop};
111              
112 26 50 33     1275 if ($schema->{properties} && $schema->{properties}{$prop}) {
113 26         46 my $prop = $schema->{properties}{$prop};
114 26   100     182 my $read_only = $prop->{readOnly} // 0;
115 26   100     201 my $write_only = $prop->{writeOnly} // 0;
116 26         88 my $direction = $data->{direction};
117              
118 26 100 100     94 next if $direction eq 'request' && $read_only;
119 18 100 100     80 next if $direction eq 'response' && $write_only;
120             }
121              
122 12         20 push @{$data->{errors}}, error(
  12         54  
123             message => qq{instance does not have required property "${prop}"},
124             instance_path => $instance_path,
125             schema_path => json_pointer->append($schema_path, $idx)
126             );
127 12         34 $result = 0;
128             }
129 93         432 return $result;
130             }
131              
132             sub discriminator {
133 28     28 0 73 my ($self, $instance, $discriminator, $origin_schema, $instance_path, $schema_path, $data) = @_;
134 28 50       82 return 1 unless $self->check_type($instance, 'object');
135              
136 28         61 my $path = $instance_path;
137              
138 28         52 my $property_name = $discriminator->{propertyName};
139 28   100     88 my $mapping = $discriminator->{mapping} // {};
140              
141 28         67 my $type = $instance->{$property_name};
142 28         46 my $ref = $mapping->{$type};
143              
144 28   66     104 $ref = $self->__detect_discriminator_ref($ref || $type);
145              
146             # status == 1 needs to prevent recursion
147 28         76 $data->{discriminator}{$path} = 1;
148              
149 28         81 my $scope = $self->validator->scope;
150 28         98 $ref = URI->new($ref);
151 28 100       1261 $ref = $ref->abs($scope) if $scope;
152              
153 28         4403 my ($current_scope, $schema) = $self->validator->resolver->resolve($ref);
154              
155 28 50       158 croak "schema not resolved by ref $ref" unless $schema;
156              
157 28         46 push @{$self->validator->scopes}, $current_scope;
  28         75  
158              
159 28         51 my $result = eval {
160 28         57 $self->validator->_validate_schema($instance, $schema, $instance_path, $schema_path, $data, apply_scope => 0);
161             };
162              
163 28 50       71 if ($@) {
164 0         0 $result = 0;
165 0         0 push @{$data->{errors}}, error(
  0         0  
166             message => "exception: $@",
167             instance_path => $instance_path,
168             schema_path => $schema_path
169             );
170             }
171              
172 28         42 pop @{$self->validator->scopes};
  28         70  
173              
174 28         76 delete $data->{discriminator}{$path};
175              
176 28         102 return $result;
177             }
178              
179             sub deprecated {
180 6     6 0 18 my ($self, $instance, $deprecated, $schema, $instance_path, $schema_path, $data) = @_;
181 6 50       15 return 1 unless $deprecated;
182 6         58 push @{$data->{warnings}}, error(
  6         22  
183             message => 'instance is deprecated',
184             instance_path => $instance_path,
185             schema_path => $schema_path
186             );
187 6         20 return 1;
188             }
189              
190             # Additional properties defined by the JSON Schema specification that are not mentioned in OAS30 are strictly unsupported.
191 0     0 0 0 sub dependencies { 1 }
192 0     0 0 0 sub additionalItems { 1 }
193 0     0 0 0 sub patternProperties { 1 }
194              
195             sub __detect_discriminator_ref {
196 28     28   59 my ($self, $ref) = @_;
197             # heuristic
198 28 100       77 return $ref if $ref =~ m|/|;
199 26 50       58 return $ref if $ref =~ m/\.json$/;
200 26         64 return '#/components/schemas/' . $ref;
201             }
202              
203             1;
204              
205             __END__