File Coverage

blib/lib/JSON/Schema/Modern/Vocabulary/OpenAPI.pm
Criterion Covered Total %
statement 75 87 86.2
branch 24 26 92.3
condition 1 3 33.3
subroutine 17 23 73.9
pod 0 2 0.0
total 117 141 82.9


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