File Coverage

blib/lib/JSONSchema/Validator/Constraints/OAS30.pm
Criterion Covered Total %
statement 113 120 94.1
branch 37 48 77.0
condition 17 20 85.0
subroutine 17 20 85.0
pod 0 11 0.0
total 184 219 84.0


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