Skip to content

Commit b93af22

Browse files
AllT2709Aldo Torresadrian-palanques-cloudappi
authored
feat: adding external ref openapi examples and adding it to some rules (#28)
* feat: adding external ref openapi examples and adding it to some rules * fix: fixing openapi test * fix: fix issues in comments * fix: imports * chore: format --------- Co-authored-by: Aldo Torres <[email protected]> Co-authored-by: Adrian Palanques <[email protected]>
1 parent ff4f53d commit b93af22

File tree

10 files changed

+352
-208
lines changed

10 files changed

+352
-208
lines changed

src/main/java/apiaddicts/sonar/openapi/checks/format/AbstractDefaultMediaTypeCheck.java

Lines changed: 121 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -20,127 +20,135 @@
2020

2121
public abstract class AbstractDefaultMediaTypeCheck extends BaseCheck {
2222

23-
private static final String DEFAULT_MEDIA_TYPE_VALUE = "application/json";
24-
private static final String MEDIA_TYPE_EXCEPTIONS_VALUE = "-";
23+
private static final String DEFAULT_MEDIA_TYPE_VALUE = "application/json";
24+
private static final String MEDIA_TYPE_EXCEPTIONS_VALUE = "-";
2525

26-
private String key;
27-
private String section;
28-
private String message;
26+
private String key;
27+
private String section;
28+
private String message;
29+
protected JsonNode externalRefNode = null;
2930

30-
@RuleProperty(
31-
key = "media-type-exceptions",
32-
description = "Media type exceptions.",
33-
defaultValue = MEDIA_TYPE_EXCEPTIONS_VALUE)
34-
public String mediaTypeExceptionsStr = MEDIA_TYPE_EXCEPTIONS_VALUE;
31+
@RuleProperty(
32+
key = "media-type-exceptions",
33+
description = "Media type exceptions.",
34+
defaultValue = MEDIA_TYPE_EXCEPTIONS_VALUE)
35+
public String mediaTypeExceptionsStr = MEDIA_TYPE_EXCEPTIONS_VALUE;
3536

3637
private Set<String> mediaTypeExceptions;
3738

38-
@RuleProperty(
39-
key = "default-media-type",
40-
description = "Default media type.",
41-
defaultValue = DEFAULT_MEDIA_TYPE_VALUE)
42-
public String defaultMediaType = DEFAULT_MEDIA_TYPE_VALUE;
43-
44-
private boolean globalSupportsDefaultMimeType = false;
45-
46-
public AbstractDefaultMediaTypeCheck(String key, String section, String message) {
47-
this.key = key;
48-
this.section = section;
49-
this.message = translate(message);
50-
}
51-
52-
@Override
53-
public Set<AstNodeType> subscribedKinds() {
54-
return ImmutableSet.of(OpenApi2Grammar.OPERATION, OpenApi3Grammar.OPERATION, OpenApi31Grammar.OPERATION, OpenApi3Grammar.RESPONSES, OpenApi31Grammar.RESPONSES);
55-
}
56-
57-
@Override
58-
protected void visitFile(JsonNode root) {
59-
mediaTypeExceptions = Stream.of(mediaTypeExceptionsStr.split(","))
60-
.map(String::trim)
61-
.map(String::toLowerCase)
62-
.collect(Collectors.toSet());
63-
globalSupportsDefaultMimeType = (root.getType() instanceof OpenApi2Grammar) ? supportsDefaultMimeTypeV2(root) : false;
64-
}
65-
66-
@Override
67-
public void visitNode(JsonNode node) {
68-
if (node.getType() instanceof OpenApi2Grammar) {
69-
visitV2Node(node);
70-
} else {
71-
visitV3Node(node);
72-
}
73-
}
74-
75-
private void visitV2Node(JsonNode node) {
76-
JsonNode sectionNode = node.get(section);
77-
boolean definesMimeTypes = !sectionNode.isMissing();
78-
79-
if (definesMimeTypes) {
80-
if (!supportsDefaultMimeTypeV2(node)) {
81-
addIssue(key, message, sectionNode.key());
82-
}
83-
} else {
84-
if (!globalSupportsDefaultMimeType) {
85-
addIssue(key, message, node.key());
86-
}
87-
}
88-
}
89-
90-
private void visitV3Node(JsonNode node) {
91-
if ( node.getType() == OpenApi3Grammar.OPERATION && section.equals("consumes") ) {
92-
String operation = node.key().getTokenValue().toLowerCase();
93-
if ( operation.equals("post") || operation.equals("put") || operation.equals("patch") ) {
94-
visitContentNode(node);
95-
} else {
96-
JsonNode requestBodyNode = node.at("/requestBody");
97-
if (!requestBodyNode.isMissing()) {
98-
addIssue(key, translate("OAR010.error-request-body-not-allowed", operation.toUpperCase()), node.key());
99-
}
100-
}
101-
}
102-
103-
if (node.getType() == OpenApi3Grammar.RESPONSES && section.equals("produces")) {
39+
@RuleProperty(
40+
key = "default-media-type",
41+
description = "Default media type.",
42+
defaultValue = DEFAULT_MEDIA_TYPE_VALUE)
43+
public String defaultMediaType = DEFAULT_MEDIA_TYPE_VALUE;
44+
45+
private boolean globalSupportsDefaultMimeType = false;
46+
47+
public AbstractDefaultMediaTypeCheck(String key, String section, String message) {
48+
this.key = key;
49+
this.section = section;
50+
this.message = translate(message);
51+
}
52+
53+
@Override
54+
public Set<AstNodeType> subscribedKinds() {
55+
return ImmutableSet.of(OpenApi2Grammar.OPERATION, OpenApi3Grammar.OPERATION, OpenApi31Grammar.OPERATION, OpenApi3Grammar.RESPONSES, OpenApi31Grammar.RESPONSES);
56+
}
57+
58+
@Override
59+
protected void visitFile(JsonNode root) {
60+
mediaTypeExceptions = Stream.of(mediaTypeExceptionsStr.split(","))
61+
.map(String::trim)
62+
.map(String::toLowerCase)
63+
.collect(Collectors.toSet());
64+
globalSupportsDefaultMimeType = (root.getType() instanceof OpenApi2Grammar) ? supportsDefaultMimeTypeV2(root) : false;
65+
}
66+
67+
@Override
68+
public void visitNode(JsonNode node) {
69+
if (node.getType() instanceof OpenApi2Grammar) {
70+
visitV2Node(node);
71+
} else {
72+
visitV3Node(node);
73+
}
74+
}
75+
76+
private void visitV2Node(JsonNode node) {
77+
JsonNode sectionNode = node.get(section);
78+
boolean definesMimeTypes = !sectionNode.isMissing();
79+
80+
if (definesMimeTypes) {
81+
if (!supportsDefaultMimeTypeV2(node)) {
82+
addIssue(key, message, sectionNode.key());
83+
}
84+
} else {
85+
if (!globalSupportsDefaultMimeType) {
86+
addIssue(key, message, node.key());
87+
}
88+
}
89+
}
90+
91+
private void visitV3Node(JsonNode node) {
92+
if (node.getType() == OpenApi3Grammar.OPERATION && section.equals("consumes")) {
93+
String operation = node.key().getTokenValue().toLowerCase();
94+
if (operation.equals("post") || operation.equals("put") || operation.equals("patch")) {
95+
visitContentNode(node);
96+
} else {
97+
JsonNode requestBodyNode = node.at("/requestBody");
98+
if (!requestBodyNode.isMissing()) {
99+
addIssue(key, translate("OAR010.error-request-body-not-allowed", operation.toUpperCase()), node.key());
100+
}
101+
}
102+
}
103+
104+
if (node.getType() == OpenApi3Grammar.RESPONSES && section.equals("produces")) {
104105
List<JsonNode> responseCodes = node.properties().stream().collect(Collectors.toList());
105106
for (JsonNode jsonNode : responseCodes) {
106107
if (!jsonNode.key().getTokenValue().equals("204")) {
107-
JsonNode responseNode = jsonNode.resolve();
108-
visitContentNode(responseNode);
108+
boolean externalRefManagement = false;
109+
if (isExternalRef(jsonNode) && externalRefNode == null) {
110+
externalRefNode = jsonNode;
111+
externalRefManagement = true;
112+
}
113+
jsonNode = resolve(jsonNode);
114+
visitContentNode(jsonNode);
115+
if (externalRefManagement) externalRefNode = null;
109116
}
110117
}
111-
}
112-
}
113-
114-
private void visitContentNode(JsonNode node) {
115-
JsonNode contentNode;
116-
if (section.equals("consumes")) {
117-
JsonNode requestBodyNode = node.at("/requestBody");
118-
contentNode = resolve(requestBodyNode).at("/content");
119-
} else {
120-
contentNode = node.at("/content");
121-
}
122-
boolean definesMimeTypes = !contentNode.isMissing();
123-
if (definesMimeTypes) {
124-
if( !supportsDefaultMimeTypeV3(contentNode) ) {
125-
addIssue(key, message, contentNode.key());
126-
}
127-
} else {
128-
addIssue(key, message, node.key());
129-
}
130-
}
131-
132-
private boolean supportsDefaultMimeTypeV2(JsonNode node) {
133-
JsonNode consumes = node.get(section);
134-
if (consumes.isMissing() || consumes.isNull()) return false;
135-
List<JsonNode> mimeTypeNodes = consumes.elements();
136-
if (mimeTypeNodes.stream().map(AstNode::getTokenValue).anyMatch(mediaTypeExceptions::contains)) return true;
137-
return mimeTypeNodes.stream().map(AstNode::getTokenValue).anyMatch(defaultMediaType::equals);
138-
}
139-
140-
private boolean supportsDefaultMimeTypeV3(JsonNode content) {
141-
if (content.isMissing() || content.isNull()) return false;
142-
Map<String, JsonNode> properties = content.propertyMap();
143-
if (properties.entrySet().stream().map(entry -> entry.getKey()).anyMatch(mediaTypeExceptions::contains)) return true;
144-
return properties.entrySet().stream().map(entry -> entry.getKey()).anyMatch(defaultMediaType::equals);
145-
}
118+
}
119+
}
120+
121+
private void visitContentNode(JsonNode node) {
122+
JsonNode contentNode;
123+
if (section.equals("consumes")) {
124+
JsonNode requestBodyNode = node.at("/requestBody");
125+
contentNode = resolve(requestBodyNode).at("/content");
126+
} else {
127+
contentNode = node.at("/content");
128+
}
129+
boolean definesMimeTypes = !contentNode.isMissing();
130+
if (definesMimeTypes) {
131+
if (!supportsDefaultMimeTypeV3(contentNode)) {
132+
addIssue(key, message, contentNode.key());
133+
}
134+
} else {
135+
addIssue(key, message, node.key());
136+
}
137+
}
138+
139+
private boolean supportsDefaultMimeTypeV2(JsonNode node) {
140+
JsonNode consumes = node.get(section);
141+
if (consumes.isMissing() || consumes.isNull()) return false;
142+
List<JsonNode> mimeTypeNodes = consumes.elements();
143+
if (mimeTypeNodes.stream().map(AstNode::getTokenValue).anyMatch(mediaTypeExceptions::contains)) return true;
144+
return mimeTypeNodes.stream().map(AstNode::getTokenValue).anyMatch(defaultMediaType::equals);
145+
}
146+
147+
private boolean supportsDefaultMimeTypeV3(JsonNode content) {
148+
if (content.isMissing() || content.isNull()) return false;
149+
Map<String, JsonNode> properties = content.propertyMap();
150+
if (properties.entrySet().stream().map(entry -> entry.getKey()).anyMatch(mediaTypeExceptions::contains))
151+
return true;
152+
return properties.entrySet().stream().map(entry -> entry.getKey()).anyMatch(defaultMediaType::equals);
153+
}
146154
}

0 commit comments

Comments
 (0)