File Coverage

blib/lib/JSON/Schema/Modern/Vocabulary/OpenAPI.pm
Criterion Covered Total %
statement 72 84 85.7
branch 24 26 92.3
condition 1 3 33.3
subroutine 16 22 72.7
pod 0 2 0.0
total 113 137 82.4


line stmt bran cond sub pod time code
1 11     11   1527246 use strict;
  11         40  
  11         345  
2 11     11   96 use warnings;
  11         28  
  11         586  
3             package JSON::Schema::Modern::Vocabulary::OpenAPI;
4             # vim: set ts=8 sts=2 sw=2 tw=100 et :
5             # ABSTRACT: Implementation of the JSON Schema OpenAPI vocabulary
6              
7             our $VERSION = '0.045';
8              
9 11     11   209 use 5.020;
  11         52  
10 11     11   80 use Moo;
  11         35  
  11         64  
11 11     11   4057 use strictures 2;
  11         117  
  11         419  
12 11     11   2008 use experimental qw(signatures postderef);
  11         54  
  11         146  
13 11     11   1811 use if "$]" >= 5.022, experimental => 're_strict';
  11         44  
  11         128  
14 11     11   1061 no if "$]" >= 5.031009, feature => 'indirect';
  11         63  
  11         84  
15 11     11   554 no if "$]" >= 5.033001, feature => 'multidimensional';
  11         31  
  11         95  
16 11     11   657 no if "$]" >= 5.033006, feature => 'bareword_filehandles';
  11         60  
  11         89  
17 11     11   652 use JSON::Schema::Modern::Utilities 0.524 qw(assert_keyword_type annotate_self E is_type jsonp);
  11         317  
  11         837  
18 11     11   203 use namespace::clean;
  11         75  
  11         115  
19              
20             with 'JSON::Schema::Modern::Vocabulary';
21              
22             sub vocabulary {
23 110     110 0 143791 'https://spec.openapis.org/oas/3.1/vocab/base' => 'draft2020-12',
24             }
25              
26             sub keywords {
27 14     14 0 70507 qw(discriminator example externalDocs xml);
28             }
29              
30 12     12   106834 sub _traverse_keyword_discriminator ($self, $schema, $state) {
  12         24  
  12         18  
  12         23  
  12         17  
31 12 100       35 return if not assert_keyword_type($state, $schema, 'object');
32              
33             # "the discriminator field MUST be a required field"
34             return E($state, 'missing required field propertyName')
35 11 100       329 if not exists $schema->{discriminator}{propertyName};
36             return E({ %$state, _schema_path_suffix => 'propertyName' }, 'discriminator propertyName is not a string')
37 10 100       26 if not is_type('string', $schema->{discriminator}{propertyName});
38              
39 9         215 my $valid = 1;
40 9 100       30 if (exists $schema->{discriminator}{mapping}) {
41 8 100       78 return if not assert_keyword_type({ %$state, _schema_path_suffix => 'mapping' }, $schema, 'object');
42             return E({ %$state, _schema_path_suffix => 'mapping' }, 'discriminator mapping is not an object ')
43 7 50       217 if not is_type('object', $schema->{discriminator}{mapping});
44 7         101 foreach my $mapping_key (sort keys $schema->{discriminator}{mapping}->%*) {
45 7         16 my $uri = $schema->{discriminator}{mapping}{$mapping_key};
46 7 100       18 $valid = E({ %$state, _schema_path_suffix => [ 'mapping', $mapping_key ] }, 'discriminator mapping value for "%s" is not a string', $mapping_key), next if not is_type('string', $uri);
47             }
48             }
49              
50             $valid = E($state, 'missing sibling keyword: one of oneOf, anyOf, allOf')
51 8 100       1047 if not grep exists $schema->{$_}, qw(oneOf anyOf allOf);
52              
53 8         939 return 1;
54             }
55              
56 6     6   75867 sub _eval_keyword_discriminator ($self, $data, $schema, $state) {
  6         14  
  6         20  
  6         13  
  6         10  
  6         12  
57             # Note: the spec is unclear of the expected behaviour when the data instance is not an object
58 6 50       16 return 1 if not is_type('object', $data);
59              
60 6         96 my $discriminator_key = $schema->{discriminator}{propertyName};
61              
62             # property with name <propertyName> MUST be present in the data payload
63             return E($state, 'missing required discriminator field "%s"', $discriminator_key)
64 6 100       29 if not exists $data->{$discriminator_key};
65              
66 5         12 my $discriminator_value = $data->{$discriminator_key};
67              
68             # if /components/$discriminator_value exists, that schema must validate
69             my $uri = Mojo::URL->new->fragment(jsonp('', qw(components schemas), $discriminator_value))
70 5         19 ->to_abs($state->{initial_schema_uri});
71 5 100 33     1403 if (my $component_schema_info = $state->{evaluator}->_fetch_from_uri($uri)) {
    100          
72 2         1610 $state = { %$state, _schema_path_suffix => 'propertyName' };
73             }
74             elsif (exists $schema->{discriminator}{mapping} and exists $schema->{discriminator}{mapping}{$discriminator_value}) {
75             # use 'mapping' to determine which schema to use.
76 2         867 $uri = Mojo::URL->new($schema->{discriminator}{mapping}{$discriminator_value});
77 2         411 $state = { %$state, _schema_path_suffix => [ 'mapping', $discriminator_value ] };
78             }
79             else {
80             # If the discriminator value does not match an implicit or explicit mapping, no schema can be
81             # determined and validation SHOULD fail.
82 1         491 return E($state, 'invalid %s: "%s"', $discriminator_key, $discriminator_value);
83             }
84              
85 4 100       34 return E($state, 'subschema for %s: %s is invalid', $discriminator_key, $discriminator_value)
86             if not $self->eval_subschema_at_uri($data, $schema, $state, $uri);
87 2         6113 return 1;
88             }
89              
90 0     0     sub _traverse_keyword_example { 1 }
91              
92 0     0     sub _eval_keyword_example ($self, $data, $schema, $state) {
  0            
  0            
  0            
  0            
  0            
93 0           annotate_self($state, $schema);
94             }
95              
96             # until we do something with these values, we do not bother checking the structure
97 0     0     sub _traverse_keyword_externalDocs { 1 }
98              
99 0     0     sub _eval_keyword_externalDocs { goto \&_eval_keyword_example }
100              
101             # until we do something with these values, we do not bother checking the structure
102 0     0     sub _traverse_keyword_xml { 1 }
103              
104 0     0     sub _eval_keyword_xml { goto \&_eval_keyword_example }
105              
106             1;
107              
108             __END__
109              
110             =pod
111              
112             =encoding UTF-8
113              
114             =head1 NAME
115              
116             JSON::Schema::Modern::Vocabulary::OpenAPI - Implementation of the JSON Schema OpenAPI vocabulary
117              
118             =head1 VERSION
119              
120             version 0.045
121              
122             =head1 DESCRIPTION
123              
124             =for Pod::Coverage vocabulary keywords
125              
126             =for stopwords metaschema
127              
128             Implementation of the JSON Schema "OpenAPI" vocabulary, indicated in metaschemas
129             with the URI C<https://spec.openapis.org/oas/3.1/vocab/base> and formally specified in
130             L<https://spec.openapis.org/oas/v3.1.0#schema-object>.
131              
132             This vocabulary is normally made available by using the metaschema
133             L<https://spec.openapis.org/oas/3.1/dialect/base>.
134              
135             =head1 SEE ALSO
136              
137             =over 4
138              
139             =item *
140              
141             L<Mojolicious::Plugin::OpenAPI::Modern>
142              
143             =item *
144              
145             L<OpenAPI::Modern>
146              
147             =item *
148              
149             L<JSON::Schema::Modern::Document::OpenAPI>
150              
151             =item *
152              
153             L<JSON::Schema::Modern>
154              
155             =item *
156              
157             L<https://json-schema.org>
158              
159             =item *
160              
161             L<https://www.openapis.org/>
162              
163             =item *
164              
165             L<https://oai.github.io/Documentation/>
166              
167             =item *
168              
169             L<https://spec.openapis.org/oas/v3.1.0>
170              
171             =back
172              
173             =head1 SUPPORT
174              
175             Bugs may be submitted through L<https://github.com/karenetheridge/OpenAPI-Modern/issues>.
176              
177             I am also usually active on irc, as 'ether' at C<irc.perl.org> and C<irc.libera.chat>.
178              
179             You can also find me on the L<JSON Schema Slack server|https://json-schema.slack.com> and L<OpenAPI
180             Slack server|https://open-api.slack.com>, which are also great resources for finding help.
181              
182             =head1 AUTHOR
183              
184             Karen Etheridge <ether@cpan.org>
185              
186             =head1 COPYRIGHT AND LICENCE
187              
188             This software is copyright (c) 2021 by Karen Etheridge.
189              
190             This is free software; you can redistribute it and/or modify it under
191             the same terms as the Perl 5 programming language system itself.
192              
193             Some schema files have their own licence, in share/oas/LICENSE.
194              
195             =cut