Skip to content

Commit d1def76

Browse files
gracekarinafrantuma
authored andcommitted
oas 3.1 - parse/validate OAS 3.1 fields
1 parent 2c1c2fa commit d1def76

File tree

6 files changed

+280
-17
lines changed

6 files changed

+280
-17
lines changed

modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/util/OpenAPIDeserializer.java

Lines changed: 68 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,8 @@ public class OpenAPIDeserializer {
137137
// 3.1
138138
// TODO use a map instead for 3.0 and 3.1. Care about compatibility
139139
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-"));
141142
protected static Set<String> INFO_KEYS_31 = new LinkedHashSet<>(Arrays.asList("title","summary", "description", "termsOfService"
142143
, "contact", "license", "version"));
143144
protected static Set<String> CONTACT_KEYS_31 = new LinkedHashSet<>(Arrays.asList("name", "url", "email"));
@@ -245,6 +246,7 @@ public class OpenAPIDeserializer {
245246
keys31.put("OAUTHFLOW_KEYS", OAUTHFLOW_KEYS_31);
246247
keys31.put("OAUTHFLOWS_KEYS", OAUTHFLOWS_KEYS_31);
247248
keys31.put("ENCODING_KEYS", ENCODING_KEYS_31);
249+
keys31.put("RESERVED_KEYWORDS", RESERVED_KEYWORDS_31);
248250
KEYS.put("openapi30", keys30);
249251
KEYS.put("openapi31", keys31);
250252

@@ -329,7 +331,11 @@ public OpenAPI parseRoot(JsonNode node, ParseResult result, String path) {
329331
}
330332
}
331333

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);
333339
if (obj != null) {
334340
Paths paths = getPaths(obj, "paths", result);
335341
openAPI.setPaths(paths);
@@ -381,12 +387,25 @@ public OpenAPI parseRoot(JsonNode node, ParseResult result, String path) {
381387
openAPI.setExtensions(extensions);
382388
}
383389

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+
384401
Set<String> keys = getKeys(rootNode);
385402
Map<String, Set<String>> specKeys = KEYS.get(result.isOpenapi31() ? "openapi31" : "openapi30");
386403
for (String key : keys) {
387404
if (!specKeys.get("ROOT_KEYS").contains(key) && !key.startsWith("x-")) {
388405
result.extra(location, key, node.get(key));
389406
}
407+
validateReservedKeywords(specKeys, key, location, result);
408+
390409
}
391410

392411
} else {
@@ -398,6 +417,15 @@ public OpenAPI parseRoot(JsonNode node, ParseResult result, String path) {
398417
return openAPI;
399418
}
400419

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+
401429
public String mungedRef(String refString) {
402430
// Ref: IETF RFC 3966, Section 5.2.2
403431
if (!refString.contains(":") && // No scheme
@@ -520,6 +548,8 @@ public Components getComponents(ObjectNode obj, String location, ParseResult res
520548
if (!specKeys.get("COMPONENTS_KEYS").contains(key) && !key.startsWith("x-")) {
521549
result.extra(location, key, obj.get(key));
522550
}
551+
validateReservedKeywords(specKeys, key, location, result);
552+
523553
}
524554

525555

@@ -584,6 +614,7 @@ public Tag getTag(ObjectNode obj, String location, ParseResult result) {
584614
if (!specKeys.get("TAG_KEYS").contains(key) && !key.startsWith("x-")) {
585615
result.extra(location, key, obj.get(key));
586616
}
617+
validateReservedKeywords(specKeys, key, location, result);
587618
}
588619

589620
return tag;
@@ -672,6 +703,7 @@ public Server getServer(ObjectNode obj, String location, ParseResult result, Str
672703
if (!specKeys.get("SERVER_KEYS").contains(key) && !key.startsWith("x-")) {
673704
result.extra(location, key, obj.get(key));
674705
}
706+
validateReservedKeywords(specKeys, key, location, result);
675707
}
676708

677709

@@ -750,6 +782,7 @@ public ServerVariable getServerVariable(ObjectNode obj, String location, ParseRe
750782
if (!specKeys.get("SERVER_VARIABLE_KEYS").contains(key) && !key.startsWith("x-")) {
751783
result.extra(location, key, obj.get(key));
752784
}
785+
validateReservedKeywords(specKeys, key, location, result);
753786
}
754787

755788
return serverVariable;
@@ -1011,6 +1044,7 @@ public PathItem getPathItem(ObjectNode obj, String location, ParseResult result)
10111044
if (!specKeys.get("PATHITEM_KEYS").contains(key) && !key.startsWith("x-")) {
10121045
result.extra(location, key, obj.get(key));
10131046
}
1047+
validateReservedKeywords(specKeys, key, location, result);
10141048
}
10151049

10161050
return pathItem;
@@ -1172,6 +1206,7 @@ public Info getInfo(ObjectNode node, String location, ParseResult result) {
11721206
if (!specKeys.get("INFO_KEYS").contains(key) && !key.startsWith("x-")) {
11731207
result.extra(location, key, node.get(key));
11741208
}
1209+
validateReservedKeywords(specKeys, key, location, result);
11751210
}
11761211

11771212
return info;
@@ -1217,6 +1252,7 @@ public License getLicense(ObjectNode node, String location, ParseResult result)
12171252
if (!specKeys.get("LICENSE_KEYS").contains(key) && !key.startsWith("x-")) {
12181253
result.extra(location, key, node.get(key));
12191254
}
1255+
validateReservedKeywords(specKeys, key, location, result);
12201256
}
12211257

12221258
return license;
@@ -1259,6 +1295,7 @@ public Contact getContact(ObjectNode node, String location, ParseResult result)
12591295
if (!specKeys.get("CONTACT_KEYS").contains(key) && !key.startsWith("x-")) {
12601296
result.extra(location, key, node.get(key));
12611297
}
1298+
validateReservedKeywords(specKeys, key, location, result);
12621299
}
12631300

12641301
return contact;
@@ -1326,6 +1363,7 @@ public MediaType getMediaType(ObjectNode contentNode, String location, ParseResu
13261363
if (!specKeys.get("MEDIATYPE_KEYS").contains(key) && !key.startsWith("x-")) {
13271364
result.extra(location, key, contentNode.get(key));
13281365
}
1366+
validateReservedKeywords(specKeys, key, location, result);
13291367
}
13301368

13311369
return mediaType;
@@ -1401,6 +1439,7 @@ public Encoding getEncoding(ObjectNode node, String location, ParseResult result
14011439
if (!specKeys.get("ENCODING_KEYS").contains(key) && !key.startsWith("x-")) {
14021440
result.extra(location, key, node.get(key));
14031441
}
1442+
validateReservedKeywords(specKeys, key, location, result);
14041443
}
14051444

14061445
return encoding;
@@ -1509,6 +1548,7 @@ public Link getLink(ObjectNode linkNode, String location, ParseResult result) {
15091548
if (!specKeys.get("LINK_KEYS").contains(key) && !key.startsWith("x-")) {
15101549
result.extra(location, key, linkNode.get(key));
15111550
}
1551+
validateReservedKeywords(specKeys, key, location, result);
15121552
}
15131553

15141554
return link;
@@ -1638,6 +1678,7 @@ public XML getXml(ObjectNode node, String location, ParseResult result) {
16381678
if (!specKeys.get("XML_KEYS").contains(key) && !key.startsWith("x-")) {
16391679
result.extra(location, key, node.get(key));
16401680
}
1681+
validateReservedKeywords(specKeys, key, location, result);
16411682
}
16421683

16431684

@@ -1973,6 +2014,7 @@ else if(parameter.getSchema() == null) {
19732014
if (!specKeys.get("PARAMETER_KEYS").contains(key) && !key.startsWith("x-")) {
19742015
result.extra(location, key, obj.get(key));
19752016
}
2017+
validateReservedKeywords(specKeys, key, location, result);
19762018
}
19772019

19782020
return parameter;
@@ -2193,9 +2235,9 @@ public SecurityScheme getSecurityScheme(ObjectNode node, String location, ParseR
21932235
}
21942236

21952237
boolean descriptionRequired, bearerFormatRequired, nameRequired, inRequired, schemeRequired, flowsRequired,
2196-
openIdConnectRequired;
2238+
openIdConnectRequired, mutualTLSRequired;
21972239
descriptionRequired = bearerFormatRequired = nameRequired = inRequired = schemeRequired = flowsRequired =
2198-
openIdConnectRequired = false;
2240+
openIdConnectRequired = mutualTLSRequired = false;
21992241

22002242
String value = getString("type", node, true, location, result);
22012243
if ((result.isAllowEmptyStrings() && value != null) || (!result.isAllowEmptyStrings() && !StringUtils.isBlank(value))) {
@@ -2211,8 +2253,11 @@ public SecurityScheme getSecurityScheme(ObjectNode node, String location, ParseR
22112253
} else if (SecurityScheme.Type.OPENIDCONNECT.toString().equals(value)) {
22122254
securityScheme.setType(SecurityScheme.Type.OPENIDCONNECT);
22132255
openIdConnectRequired = true;
2256+
}else if (result.isOpenapi31() && SecurityScheme.Type.MUTUALTLS.toString().equals(value)) {
2257+
securityScheme.setType(SecurityScheme.Type.MUTUALTLS);
2258+
mutualTLSRequired = true;
22142259
} else {
2215-
result.invalidType(location + ".type", "type", "http|apiKey|oauth2|openIdConnect ", node);
2260+
result.invalidType(location + ".type", "type", "http|apiKey|oauth2|openIdConnect|mutualTLS ", node);
22162261
}
22172262
}
22182263
value = getString("description", node, descriptionRequired, location, result);
@@ -2437,6 +2482,7 @@ public Discriminator getDiscriminator(ObjectNode node, String location, ParseRes
24372482
if (extensions != null && extensions.size() > 0) {
24382483
discriminator.setExtensions(extensions);
24392484
}
2485+
//validateReservedKeywords(keys,key, location, result);
24402486
}
24412487
}
24422488
}
@@ -2870,6 +2916,7 @@ at the moment path passed as string (basePath) from upper components can be both
28702916
if (!specKeys.get("SCHEMA_KEYS").contains(key) && !key.startsWith("x-")) {
28712917
result.extra(location, key, node.get(key));
28722918
}
2919+
validateReservedKeywords(specKeys, key, location, result);
28732920
}
28742921

28752922
return schema;
@@ -3100,6 +3147,8 @@ public Example getExample(ObjectNode node, String location, ParseResult result)
31003147
if (!specKeys.get("EXAMPLE_KEYS").contains(key) && !key.startsWith("x-")) {
31013148
result.extra(location, key, node.get(key));
31023149
}
3150+
validateReservedKeywords(specKeys, key, location, result);
3151+
31033152
}
31043153
return example;
31053154
}
@@ -3230,6 +3279,7 @@ public ApiResponse getResponse(ObjectNode node, String location, ParseResult res
32303279
if (!specKeys.get("RESPONSE_KEYS").contains(key) && !key.startsWith("x-")) {
32313280
result.extra(location, key, node.get(key));
32323281
}
3282+
validateReservedKeywords(specKeys, key, location, result);
32333283
}
32343284

32353285

@@ -3338,6 +3388,7 @@ public Operation getOperation(ObjectNode obj, String location, ParseResult resul
33383388
if (!specKeys.get("OPERATION_KEYS").contains(key) && !key.startsWith("x-")) {
33393389
result.extra(location, key, obj.get(key));
33403390
}
3391+
validateReservedKeywords(specKeys, key, location, result);
33413392
}
33423393

33433394

@@ -3469,6 +3520,7 @@ public RequestBody getRequestBody(ObjectNode node, String location, ParseResult
34693520
if (!specKeys.get("REQUEST_BODY_KEYS").contains(key) && !key.startsWith("x-")) {
34703521
result.extra(location, key, node.get(key));
34713522
}
3523+
validateReservedKeywords(specKeys, key, location, result);
34723524
}
34733525

34743526
return body;
@@ -3975,6 +4027,7 @@ public static class ParseResult {
39754027
private List<Location> unique = new ArrayList<>();
39764028
private List<Location> uniqueTags = new ArrayList<>();
39774029
private boolean allowEmptyStrings = true;
4030+
private List<Location> reserved = new ArrayList<>();
39784031
private boolean validateInternalRefs;
39794032

39804033
private boolean openapi31 = false;
@@ -3999,6 +4052,10 @@ public void unsupported(String location, String key, JsonNode value) {
39994052
unsupported.put(new Location(location, key), value);
40004053
}
40014054

4055+
public void reserved(String location, String key) {
4056+
reserved.add(new Location(location, key));
4057+
}
4058+
40024059
public void extra(String location, String key, JsonNode value) {
40034060
extra.put(new Location(location, key), value);
40044061
}
@@ -4064,7 +4121,7 @@ public List<String> getMessages() {
40644121
}
40654122
for (Location l : warnings) {
40664123
String location = l.location.equals("") ? "" : l.location + ".";
4067-
String message = "attribute " + location + l.key;
4124+
String message = location + l.key;
40684125
messages.add(message);
40694126
}
40704127
for (Location l : unsupported.keySet()) {
@@ -4082,6 +4139,11 @@ public List<String> getMessages() {
40824139
String message = "attribute " + location + l.key + " is repeated";
40834140
messages.add(message);
40844141
}
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+
}
40854147
return messages;
40864148
}
40874149

0 commit comments

Comments
 (0)