File Coverage

blib/lib/JSON/Schema/Modern/Vocabulary/OpenAPI.pm
Criterion Covered Total %
statement 80 92 86.9
branch 22 24 91.6
condition 7 9 77.7
subroutine 20 26 76.9
pod 0 2 0.0
total 129 153 84.3


line stmt bran cond sub pod time code
1 17     17   2708087 use strictures 2;
  17         118  
  17         533  
2             package JSON::Schema::Modern::Vocabulary::OpenAPI;
3             # vim: set ts=8 sts=2 sw=2 tw=100 et :
4             # ABSTRACT: Implementation of the JSON Schema OpenAPI vocabulary
5              
6             our $VERSION = '0.139';
7              
8 17     17   6030 use 5.020;
  17         53  
9 17     17   72 use utf8;
  17         31  
  17         117  
10 17     17   367 use Moo;
  17         26  
  17         93  
11 17     17   5469 use strictures 2;
  17         80  
  17         510  
12 17     17   5493 use stable 0.031 'postderef';
  17         247  
  17         83  
13 17     17   2588 use experimental 'signatures';
  17         31  
  17         55  
14 17     17   917 no autovivification warn => qw(fetch store exists delete);
  17         85  
  17         100  
15 17     17   1337 use if "$]" >= 5.022, experimental => 're_strict';
  17         22  
  17         304  
16 17     17   1229 no if "$]" >= 5.031009, feature => 'indirect';
  17         58  
  17         965  
17 17     17   82 no if "$]" >= 5.033001, feature => 'multidimensional';
  17         45  
  17         849  
18 17     17   68 no if "$]" >= 5.033006, feature => 'bareword_filehandles';
  17         28  
  17         809  
19 17     17   89 no if "$]" >= 5.041009, feature => 'smartmatch';
  17         28  
  17         652  
20 17     17   61 no feature 'switch';
  17         53  
  17         629  
21 17     17   63 use JSON::Schema::Modern::Utilities qw(assert_keyword_type annotate_self E is_type jsonp);
  17         38  
  17         1244  
22 17     17   111 use namespace::clean;
  17         32  
  17         104  
23              
24             with 'JSON::Schema::Modern::Vocabulary';
25              
26             sub vocabulary {
27 385     385 0 349745 'https://spec.openapis.org/oas/3.1/vocab/base' => 'draft2020-12',
28             'https://spec.openapis.org/oas/3.2/vocab/base' => 'draft2020-12',
29             }
30              
31             sub keywords {
32 1136     1136 0 3143920 qw(discriminator example externalDocs xml);
33             }
34              
35 15     15   78660 sub _traverse_keyword_discriminator ($self, $schema, $state) {
  15         28  
  15         23  
  15         21  
  15         23  
36 15 100       49 return if not assert_keyword_type($state, $schema, 'object');
37              
38             # "the discriminator field MUST be a required field"
39             return E($state, 'missing required property "propertyName"')
40 13 100       334 if not exists $schema->{discriminator}{propertyName};
41             return E({ %$state, _keyword_path_suffix => 'propertyName' }, 'discriminator propertyName is not a string')
42 11 100       36 if not is_type('string', $schema->{discriminator}{propertyName});
43              
44 9         197 my $valid = 1;
45 9 100       37 if (exists $schema->{discriminator}{mapping}) {
46 7 50       65 return if not assert_keyword_type({ %$state, _keyword_path_suffix => 'mapping' }, $schema, 'object');
47             return E({ %$state, _keyword_path_suffix => 'mapping' }, 'discriminator mapping is not an object ')
48 7 100       137 if not is_type('object', $schema->{discriminator}{mapping});
49 5         62 foreach my $mapping_key (sort keys $schema->{discriminator}{mapping}->%*) {
50 5         14 my $uri = $schema->{discriminator}{mapping}{$mapping_key};
51 5 100       14 $valid = E({ %$state, _keyword_path_suffix => [ 'mapping', $mapping_key ] }, 'discriminator mapping value for "%s" is not a string', $mapping_key), next if not is_type('string', $uri);
52             }
53             }
54              
55 7         516 return $valid;
56             }
57              
58 11     11   25361 sub _eval_keyword_discriminator ($self, $data, $schema, $state) {
  11         22  
  11         19  
  11         16  
  11         17  
  11         14  
59             # Note: the spec is unclear of the expected behaviour when the data instance is not an object
60 11 50       40 return 1 if not is_type('object', $data);
61              
62             # v3.1.2 §4.8.25.1: "This property SHOULD be required in the payload schema, as the behavior when
63             # the property is absent is undefined."
64             # v3.2.0 §4.25.1: "The discriminating property MAY be defined as required or optional, but when
65             # defined as optional the Discriminator Object MUST include a defaultMapping field that specifies
66             # which schema is expected to validate the structure of the model when the discriminating property
67             # is not present."
68 11         203 my $discriminator_key = $schema->{discriminator}{propertyName};
69             return E($state, 'missing required discriminator property "%s" and no defaultMapping present',
70             $discriminator_key)
71 11 100 100     67 if not exists $data->{$discriminator_key} and not exists $schema->{discriminator}{defaultMapping};
72              
73 8   66     33 my $discriminator_value = $data->{$discriminator_key} // $schema->{discriminator}{defaultMapping};
74              
75 8 100 66     86 if (exists $schema->{discriminator}{mapping} and exists $schema->{discriminator}{mapping}{$discriminator_value}) {
    100          
76             # use 'mapping' to determine which schema to use.
77             # Note that the spec uses an example that assumes that the mapping value can be appended to
78             # /components/schemas/, _as well as_ being treated as a uri-reference, but this is ambiguous.
79             # For now we will handle it by prepending '#/components/schemas' if it is not already a
80             # fragment-only uri reference.
81 3         11 my $mapping = $schema->{discriminator}{mapping}{$discriminator_value};
82              
83             return $self->eval_subschema_at_uri($data, $schema,
84             { %$state, keyword => $state->{keyword}.jsonp('', 'mapping', $discriminator_value) },
85 3 100       20 Mojo::URL->new($mapping =~ /^#/ ? $mapping : '#/components/schemas/'.$mapping)->to_abs($state->{initial_schema_uri}),
86             );
87             }
88             elsif ($state->{document}->get_entity_at_location('/components/schemas/'.$discriminator_value) eq 'schema') {
89             return $self->eval_subschema_at_uri($data, $schema, { %$state, keyword => $state->{keyword}.'/propertyName' },
90 4         68 Mojo::URL->new->fragment(jsonp('/components/schemas', $discriminator_value))->to_abs($state->{initial_schema_uri}),
91             );
92             }
93             else {
94             # v3.2.0 §4.25.5, "Discriminator Property": If the discriminating value does not match an
95             # implicit or explicit mapping, no schema can be determined and validation SHOULD fail.
96 1         36 return E({ %$state, data_path => jsonp($state->{data_path}, $discriminator_key) },
97             'invalid %s: "%s"', $discriminator_key, $discriminator_value);
98             }
99             }
100              
101 0     0     sub _traverse_keyword_example { 1 }
102              
103 0     0     sub _eval_keyword_example ($self, $data, $schema, $state) {
  0            
  0            
  0            
  0            
  0            
104 0           annotate_self($state, $schema);
105             }
106              
107             # until we do something with these values, we do not bother checking the structure
108 0     0     sub _traverse_keyword_externalDocs { 1 }
109              
110 0     0     sub _eval_keyword_externalDocs { goto \&_eval_keyword_example }
111              
112             # until we do something with these values, we do not bother checking the structure
113 0     0     sub _traverse_keyword_xml { 1 }
114              
115 0     0     sub _eval_keyword_xml { goto \&_eval_keyword_example }
116              
117             1;
118              
119             __END__