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   41 use strict;
  6         13  
  6         193  
6 6     6   29 use warnings;
  6         10  
  6         134  
7 6     6   28 use URI;
  6         11  
  6         144  
8 6     6   32 use Carp 'croak';
  6         12  
  6         265  
9              
10 6     6   43 use JSONSchema::Validator::JSONPointer 'json_pointer';
  6         16  
  6         261  
11 6     6   39 use JSONSchema::Validator::Error 'error';
  6         15  
  6         407  
12              
13 6     6   48 use parent 'JSONSchema::Validator::Constraints::Draft4';
  6         14  
  6         36  
14              
15             sub type {
16 372     372 0 1365 my ($self, $instance, $type, $schema, $instance_path, $schema_path, $data) = @_;
17              
18 372 100       969 if ($self->check_type($instance, 'null')) {
19             return $self->nullable( $instance,
20 2   100     15 $schema->{nullable} // 0,
21             $schema,
22             $instance_path,
23             $schema_path,
24             $data);
25             }
26              
27 370         723 my $result = 1;
28 370 100       850 $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       1186 return 1 if $result;
36              
37 10         21 push @{$data->{errors}}, error(
  10         48  
38             message => 'type mismatch',
39             instance_path => $instance_path,
40             schema_path => $schema_path
41             );
42 10         33 return 0;
43             }
44              
45             sub items {
46 15     15 0 64 my ($self, $instance, $items, $schema, $instance_path, $schema_path, $data) = @_;
47 15 50       54 return 1 unless $self->check_type($instance, 'array');
48              
49             # items is object and NOT array
50              
51 15         37 my $result = 1;
52 15         26 for my $i (0 .. $#{$instance}) {
  15         49  
53 28         53 my $item = $instance->[$i];
54 28         71 my $ipath = json_pointer->append($instance_path, $i);
55 28         88 my $r = $self->validator->_validate_schema($item, $items, $ipath, $schema_path, $data);
56 28 100       84 $result = 0 unless $r;
57             }
58 15         44 return $result;
59             }
60              
61             sub nullable {
62 8     8 0 33 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       25 return 1 unless $schema->{type};
65 6 100       26 return 1 if $nullable;
66 1 50       6 unless (defined $instance) {
67 1         3 push @{$data->{errors}}, error(
  1         8  
68             message => 'instance is nullable',
69             instance_path => $instance_path,
70             schema_path => $schema_path
71             );
72 1         7 return 0;
73             }
74 0         0 return 1;
75             }
76              
77             sub readOnly {
78 14     14 0 42 my ($self, $instance, $readOnly, $schema, $instance_path, $schema_path, $data) = @_;
79 14 50       56 return 1 unless $readOnly;
80 14 100       160 return 1 if $data->{direction} eq 'response';
81              
82 2         8 push @{$data->{errors}}, error(
  2         17  
83             message => 'instance is invalid in request because of readOnly property',
84             instance_path => $instance_path,
85             schema_path => $schema_path
86             );
87 2         6 return 0;
88             }
89              
90             sub writeOnly {
91 11     11 0 61 my ($self, $instance, $writeOnly, $schema, $instance_path, $schema_path, $data) = @_;
92 11 50       73 return 1 unless $writeOnly;
93 11 100       131 return 1 if $data->{direction} eq 'request';
94              
95 2         6 push @{$data->{errors}}, error(
  2         15  
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 242 my ($self, $instance, $required, $schema, $instance_path, $schema_path, $data) = @_;
105 93 50       285 return 1 unless $self->check_type($instance, 'object');
106              
107 93         195 my $result = 1;
108 93         161 for my $idx (0 .. $#{$required}) {
  93         270  
109 184         385 my $prop = $required->[$idx];
110 184 100       473 next if exists $instance->{$prop};
111              
112 26 50 33     220 if ($schema->{properties} && $schema->{properties}{$prop}) {
113 26         114 my $prop = $schema->{properties}{$prop};
114 26   100     102 my $read_only = $prop->{readOnly} // 0;
115 26   100     154 my $write_only = $prop->{writeOnly} // 0;
116 26         91 my $direction = $data->{direction};
117              
118 26 100 100     96 next if $direction eq 'request' && $read_only;
119 18 100 100     110 next if $direction eq 'response' && $write_only;
120             }
121              
122 12         24 push @{$data->{errors}}, error(
  12         58  
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         36 $result = 0;
128             }
129 93         425 return $result;
130             }
131              
132             sub discriminator {
133 28     28 0 75 my ($self, $instance, $discriminator, $origin_schema, $instance_path, $schema_path, $data) = @_;
134 28 50       96 return 1 unless $self->check_type($instance, 'object');
135              
136 28         70 my $path = $instance_path;
137              
138 28         76 my $property_name = $discriminator->{propertyName};
139 28   100     97 my $mapping = $discriminator->{mapping} // {};
140              
141 28         72 my $type = $instance->{$property_name};
142 28         56 my $ref = $mapping->{$type};
143              
144 28   66     118 $ref = $self->__detect_discriminator_ref($ref || $type);
145              
146             # status == 1 needs to prevent recursion
147 28         81 $data->{discriminator}{$path} = 1;
148              
149 28         92 my $scope = $self->validator->scope;
150 28         117 $ref = URI->new($ref);
151 28 100       1413 $ref = $ref->abs($scope) if $scope;
152              
153 28         4463 my ($current_scope, $schema) = $self->validator->resolver->resolve($ref);
154              
155 28 50       146 croak "schema not resolved by ref $ref" unless $schema;
156              
157 28         73 push @{$self->validator->scopes}, $current_scope;
  28         82  
158              
159 28         60 my $result = eval {
160 28         63 $self->validator->_validate_schema($instance, $schema, $instance_path, $schema_path, $data, apply_scope => 0);
161             };
162              
163 28 50       73 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         45 pop @{$self->validator->scopes};
  28         84  
173              
174 28         87 delete $data->{discriminator}{$path};
175              
176 28         113 return $result;
177             }
178              
179             sub deprecated {
180 6     6 0 20 my ($self, $instance, $deprecated, $schema, $instance_path, $schema_path, $data) = @_;
181 6 50       18 return 1 unless $deprecated;
182 6         53 push @{$data->{warnings}}, error(
  6         25  
183             message => 'instance is deprecated',
184             instance_path => $instance_path,
185             schema_path => $schema_path
186             );
187 6         18 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   77 my ($self, $ref) = @_;
197             # heuristic
198 28 100       92 return $ref if $ref =~ m|/|;
199 26 50       66 return $ref if $ref =~ m/\.json$/;
200 26         67 return '#/components/schemas/' . $ref;
201             }
202              
203             1;
204              
205             __END__