@@ -137,7 +137,8 @@ public class OpenAPIDeserializer {
137
137
// 3.1
138
138
// TODO use a map instead for 3.0 and 3.1. Care about compatibility
139
139
protected static Set <String > ROOT_KEYS_31 = new LinkedHashSet <>(Arrays .asList ("openapi" , "info" , "servers" , "paths" ,
140
- "components" , "security" , "tags" , "externalDocs" , "webhooks" ));
140
+ "components" , "security" , "tags" , "externalDocs" , "webhooks" , "jsonSchemaDialect" ));
141
+ protected static Set <String > RESERVED_KEYWORDS_31 = new LinkedHashSet <>(Arrays .asList ("x-oai-" ,"x-oas-" ));
141
142
protected static Set <String > INFO_KEYS_31 = new LinkedHashSet <>(Arrays .asList ("title" ,"summary" , "description" , "termsOfService"
142
143
, "contact" , "license" , "version" ));
143
144
protected static Set <String > CONTACT_KEYS_31 = new LinkedHashSet <>(Arrays .asList ("name" , "url" , "email" ));
@@ -245,6 +246,7 @@ public class OpenAPIDeserializer {
245
246
keys31 .put ("OAUTHFLOW_KEYS" , OAUTHFLOW_KEYS_31 );
246
247
keys31 .put ("OAUTHFLOWS_KEYS" , OAUTHFLOWS_KEYS_31 );
247
248
keys31 .put ("ENCODING_KEYS" , ENCODING_KEYS_31 );
249
+ keys31 .put ("RESERVED_KEYWORDS" , RESERVED_KEYWORDS_31 );
248
250
KEYS .put ("openapi30" , keys30 );
249
251
KEYS .put ("openapi31" , keys31 );
250
252
@@ -329,7 +331,11 @@ public OpenAPI parseRoot(JsonNode node, ParseResult result, String path) {
329
331
}
330
332
}
331
333
332
- obj = getObject ("paths" , rootNode , true , location , result );
334
+ boolean pathsRequired = true ;
335
+ if (result .isOpenapi31 ()) {
336
+ pathsRequired = false ;
337
+ }
338
+ obj = getObject ("paths" , rootNode , pathsRequired , location , result );
333
339
if (obj != null ) {
334
340
Paths paths = getPaths (obj , "paths" , result );
335
341
openAPI .setPaths (paths );
@@ -381,12 +387,25 @@ public OpenAPI parseRoot(JsonNode node, ParseResult result, String path) {
381
387
openAPI .setExtensions (extensions );
382
388
}
383
389
390
+ if (result .isOpenapi31 ()) {
391
+ value = getString ("jsonSchemaDialect" , rootNode , false , location , result );
392
+ if (value != null ) {
393
+ openAPI .setJsonSchemaDialect (value );
394
+ }
395
+ }
396
+
397
+ if (result .isOpenapi31 () && openAPI .getComponents () == null && openAPI .getPaths () == null && openAPI .getWebhooks () == null ){
398
+ result .warning (location , "The OpenAPI document MUST contain at least one paths field, a components field or a webhooks field" );
399
+ }
400
+
384
401
Set <String > keys = getKeys (rootNode );
385
402
Map <String , Set <String >> specKeys = KEYS .get (result .isOpenapi31 () ? "openapi31" : "openapi30" );
386
403
for (String key : keys ) {
387
404
if (!specKeys .get ("ROOT_KEYS" ).contains (key ) && !key .startsWith ("x-" )) {
388
405
result .extra (location , key , node .get (key ));
389
406
}
407
+ validateReservedKeywords (specKeys , key , location , result );
408
+
390
409
}
391
410
392
411
} else {
@@ -398,6 +417,15 @@ public OpenAPI parseRoot(JsonNode node, ParseResult result, String path) {
398
417
return openAPI ;
399
418
}
400
419
420
+ private void validateReservedKeywords (Map <String , Set <String >> specKeys , String key , String location , ParseResult result ) {
421
+ if (result .isOpenapi31 () && specKeys .get ("RESERVED_KEYWORDS" ).stream ()
422
+ .filter (rk -> key .startsWith (rk ))
423
+ .findAny ()
424
+ .orElse (null ) != null ){
425
+ result .reserved (location , key );
426
+ }
427
+ }
428
+
401
429
public String mungedRef (String refString ) {
402
430
// Ref: IETF RFC 3966, Section 5.2.2
403
431
if (!refString .contains (":" ) && // No scheme
@@ -520,6 +548,8 @@ public Components getComponents(ObjectNode obj, String location, ParseResult res
520
548
if (!specKeys .get ("COMPONENTS_KEYS" ).contains (key ) && !key .startsWith ("x-" )) {
521
549
result .extra (location , key , obj .get (key ));
522
550
}
551
+ validateReservedKeywords (specKeys , key , location , result );
552
+
523
553
}
524
554
525
555
@@ -584,6 +614,7 @@ public Tag getTag(ObjectNode obj, String location, ParseResult result) {
584
614
if (!specKeys .get ("TAG_KEYS" ).contains (key ) && !key .startsWith ("x-" )) {
585
615
result .extra (location , key , obj .get (key ));
586
616
}
617
+ validateReservedKeywords (specKeys , key , location , result );
587
618
}
588
619
589
620
return tag ;
@@ -672,6 +703,7 @@ public Server getServer(ObjectNode obj, String location, ParseResult result, Str
672
703
if (!specKeys .get ("SERVER_KEYS" ).contains (key ) && !key .startsWith ("x-" )) {
673
704
result .extra (location , key , obj .get (key ));
674
705
}
706
+ validateReservedKeywords (specKeys , key , location , result );
675
707
}
676
708
677
709
@@ -750,6 +782,7 @@ public ServerVariable getServerVariable(ObjectNode obj, String location, ParseRe
750
782
if (!specKeys .get ("SERVER_VARIABLE_KEYS" ).contains (key ) && !key .startsWith ("x-" )) {
751
783
result .extra (location , key , obj .get (key ));
752
784
}
785
+ validateReservedKeywords (specKeys , key , location , result );
753
786
}
754
787
755
788
return serverVariable ;
@@ -1011,6 +1044,7 @@ public PathItem getPathItem(ObjectNode obj, String location, ParseResult result)
1011
1044
if (!specKeys .get ("PATHITEM_KEYS" ).contains (key ) && !key .startsWith ("x-" )) {
1012
1045
result .extra (location , key , obj .get (key ));
1013
1046
}
1047
+ validateReservedKeywords (specKeys , key , location , result );
1014
1048
}
1015
1049
1016
1050
return pathItem ;
@@ -1172,6 +1206,7 @@ public Info getInfo(ObjectNode node, String location, ParseResult result) {
1172
1206
if (!specKeys .get ("INFO_KEYS" ).contains (key ) && !key .startsWith ("x-" )) {
1173
1207
result .extra (location , key , node .get (key ));
1174
1208
}
1209
+ validateReservedKeywords (specKeys , key , location , result );
1175
1210
}
1176
1211
1177
1212
return info ;
@@ -1217,6 +1252,7 @@ public License getLicense(ObjectNode node, String location, ParseResult result)
1217
1252
if (!specKeys .get ("LICENSE_KEYS" ).contains (key ) && !key .startsWith ("x-" )) {
1218
1253
result .extra (location , key , node .get (key ));
1219
1254
}
1255
+ validateReservedKeywords (specKeys , key , location , result );
1220
1256
}
1221
1257
1222
1258
return license ;
@@ -1259,6 +1295,7 @@ public Contact getContact(ObjectNode node, String location, ParseResult result)
1259
1295
if (!specKeys .get ("CONTACT_KEYS" ).contains (key ) && !key .startsWith ("x-" )) {
1260
1296
result .extra (location , key , node .get (key ));
1261
1297
}
1298
+ validateReservedKeywords (specKeys , key , location , result );
1262
1299
}
1263
1300
1264
1301
return contact ;
@@ -1326,6 +1363,7 @@ public MediaType getMediaType(ObjectNode contentNode, String location, ParseResu
1326
1363
if (!specKeys .get ("MEDIATYPE_KEYS" ).contains (key ) && !key .startsWith ("x-" )) {
1327
1364
result .extra (location , key , contentNode .get (key ));
1328
1365
}
1366
+ validateReservedKeywords (specKeys , key , location , result );
1329
1367
}
1330
1368
1331
1369
return mediaType ;
@@ -1401,6 +1439,7 @@ public Encoding getEncoding(ObjectNode node, String location, ParseResult result
1401
1439
if (!specKeys .get ("ENCODING_KEYS" ).contains (key ) && !key .startsWith ("x-" )) {
1402
1440
result .extra (location , key , node .get (key ));
1403
1441
}
1442
+ validateReservedKeywords (specKeys , key , location , result );
1404
1443
}
1405
1444
1406
1445
return encoding ;
@@ -1509,6 +1548,7 @@ public Link getLink(ObjectNode linkNode, String location, ParseResult result) {
1509
1548
if (!specKeys .get ("LINK_KEYS" ).contains (key ) && !key .startsWith ("x-" )) {
1510
1549
result .extra (location , key , linkNode .get (key ));
1511
1550
}
1551
+ validateReservedKeywords (specKeys , key , location , result );
1512
1552
}
1513
1553
1514
1554
return link ;
@@ -1638,6 +1678,7 @@ public XML getXml(ObjectNode node, String location, ParseResult result) {
1638
1678
if (!specKeys .get ("XML_KEYS" ).contains (key ) && !key .startsWith ("x-" )) {
1639
1679
result .extra (location , key , node .get (key ));
1640
1680
}
1681
+ validateReservedKeywords (specKeys , key , location , result );
1641
1682
}
1642
1683
1643
1684
@@ -1973,6 +2014,7 @@ else if(parameter.getSchema() == null) {
1973
2014
if (!specKeys .get ("PARAMETER_KEYS" ).contains (key ) && !key .startsWith ("x-" )) {
1974
2015
result .extra (location , key , obj .get (key ));
1975
2016
}
2017
+ validateReservedKeywords (specKeys , key , location , result );
1976
2018
}
1977
2019
1978
2020
return parameter ;
@@ -2193,9 +2235,9 @@ public SecurityScheme getSecurityScheme(ObjectNode node, String location, ParseR
2193
2235
}
2194
2236
2195
2237
boolean descriptionRequired , bearerFormatRequired , nameRequired , inRequired , schemeRequired , flowsRequired ,
2196
- openIdConnectRequired ;
2238
+ openIdConnectRequired , mutualTLSRequired ;
2197
2239
descriptionRequired = bearerFormatRequired = nameRequired = inRequired = schemeRequired = flowsRequired =
2198
- openIdConnectRequired = false ;
2240
+ openIdConnectRequired = mutualTLSRequired = false ;
2199
2241
2200
2242
String value = getString ("type" , node , true , location , result );
2201
2243
if ((result .isAllowEmptyStrings () && value != null ) || (!result .isAllowEmptyStrings () && !StringUtils .isBlank (value ))) {
@@ -2211,8 +2253,11 @@ public SecurityScheme getSecurityScheme(ObjectNode node, String location, ParseR
2211
2253
} else if (SecurityScheme .Type .OPENIDCONNECT .toString ().equals (value )) {
2212
2254
securityScheme .setType (SecurityScheme .Type .OPENIDCONNECT );
2213
2255
openIdConnectRequired = true ;
2256
+ }else if (result .isOpenapi31 () && SecurityScheme .Type .MUTUALTLS .toString ().equals (value )) {
2257
+ securityScheme .setType (SecurityScheme .Type .MUTUALTLS );
2258
+ mutualTLSRequired = true ;
2214
2259
} else {
2215
- result .invalidType (location + ".type" , "type" , "http|apiKey|oauth2|openIdConnect " , node );
2260
+ result .invalidType (location + ".type" , "type" , "http|apiKey|oauth2|openIdConnect|mutualTLS " , node );
2216
2261
}
2217
2262
}
2218
2263
value = getString ("description" , node , descriptionRequired , location , result );
@@ -2437,6 +2482,7 @@ public Discriminator getDiscriminator(ObjectNode node, String location, ParseRes
2437
2482
if (extensions != null && extensions .size () > 0 ) {
2438
2483
discriminator .setExtensions (extensions );
2439
2484
}
2485
+ //validateReservedKeywords(keys,key, location, result);
2440
2486
}
2441
2487
}
2442
2488
}
@@ -2870,6 +2916,7 @@ at the moment path passed as string (basePath) from upper components can be both
2870
2916
if (!specKeys .get ("SCHEMA_KEYS" ).contains (key ) && !key .startsWith ("x-" )) {
2871
2917
result .extra (location , key , node .get (key ));
2872
2918
}
2919
+ validateReservedKeywords (specKeys , key , location , result );
2873
2920
}
2874
2921
2875
2922
return schema ;
@@ -3100,6 +3147,8 @@ public Example getExample(ObjectNode node, String location, ParseResult result)
3100
3147
if (!specKeys .get ("EXAMPLE_KEYS" ).contains (key ) && !key .startsWith ("x-" )) {
3101
3148
result .extra (location , key , node .get (key ));
3102
3149
}
3150
+ validateReservedKeywords (specKeys , key , location , result );
3151
+
3103
3152
}
3104
3153
return example ;
3105
3154
}
@@ -3230,6 +3279,7 @@ public ApiResponse getResponse(ObjectNode node, String location, ParseResult res
3230
3279
if (!specKeys .get ("RESPONSE_KEYS" ).contains (key ) && !key .startsWith ("x-" )) {
3231
3280
result .extra (location , key , node .get (key ));
3232
3281
}
3282
+ validateReservedKeywords (specKeys , key , location , result );
3233
3283
}
3234
3284
3235
3285
@@ -3338,6 +3388,7 @@ public Operation getOperation(ObjectNode obj, String location, ParseResult resul
3338
3388
if (!specKeys .get ("OPERATION_KEYS" ).contains (key ) && !key .startsWith ("x-" )) {
3339
3389
result .extra (location , key , obj .get (key ));
3340
3390
}
3391
+ validateReservedKeywords (specKeys , key , location , result );
3341
3392
}
3342
3393
3343
3394
@@ -3469,6 +3520,7 @@ public RequestBody getRequestBody(ObjectNode node, String location, ParseResult
3469
3520
if (!specKeys .get ("REQUEST_BODY_KEYS" ).contains (key ) && !key .startsWith ("x-" )) {
3470
3521
result .extra (location , key , node .get (key ));
3471
3522
}
3523
+ validateReservedKeywords (specKeys , key , location , result );
3472
3524
}
3473
3525
3474
3526
return body ;
@@ -3975,6 +4027,7 @@ public static class ParseResult {
3975
4027
private List <Location > unique = new ArrayList <>();
3976
4028
private List <Location > uniqueTags = new ArrayList <>();
3977
4029
private boolean allowEmptyStrings = true ;
4030
+ private List <Location > reserved = new ArrayList <>();
3978
4031
private boolean validateInternalRefs ;
3979
4032
3980
4033
private boolean openapi31 = false ;
@@ -3999,6 +4052,10 @@ public void unsupported(String location, String key, JsonNode value) {
3999
4052
unsupported .put (new Location (location , key ), value );
4000
4053
}
4001
4054
4055
+ public void reserved (String location , String key ) {
4056
+ reserved .add (new Location (location , key ));
4057
+ }
4058
+
4002
4059
public void extra (String location , String key , JsonNode value ) {
4003
4060
extra .put (new Location (location , key ), value );
4004
4061
}
@@ -4064,7 +4121,7 @@ public List<String> getMessages() {
4064
4121
}
4065
4122
for (Location l : warnings ) {
4066
4123
String location = l .location .equals ("" ) ? "" : l .location + "." ;
4067
- String message = "attribute " + location + l .key ;
4124
+ String message = location + l .key ;
4068
4125
messages .add (message );
4069
4126
}
4070
4127
for (Location l : unsupported .keySet ()) {
@@ -4082,6 +4139,11 @@ public List<String> getMessages() {
4082
4139
String message = "attribute " + location + l .key + " is repeated" ;
4083
4140
messages .add (message );
4084
4141
}
4142
+ for (Location l : reserved ) {
4143
+ String location = l .location .equals ("" ) ? "" : l .location + "." ;
4144
+ String message = "attribute " + location + l .key + " is reserved by The OpenAPI Iniciative" ;
4145
+ messages .add (message );
4146
+ }
4085
4147
return messages ;
4086
4148
}
4087
4149
0 commit comments