Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,22 +1,21 @@
package apiaddicts.sonar.openapi.checks.format;

import apiaddicts.sonar.openapi.checks.BaseCheck;
import apiaddicts.sonar.openapi.utils.MediaTypeUtils;

import com.google.common.collect.ImmutableSet;
import com.sonar.sslr.api.AstNode;
import com.sonar.sslr.api.AstNodeType;
import org.sonar.check.RuleProperty;
import org.apiaddicts.apitools.dosonarapi.api.v2.OpenApi2Grammar;
import org.apiaddicts.apitools.dosonarapi.api.v3.OpenApi3Grammar;
import org.apiaddicts.apitools.dosonarapi.api.v31.OpenApi31Grammar;
import apiaddicts.sonar.openapi.checks.BaseCheck;
import org.apiaddicts.apitools.dosonarapi.sslr.yaml.grammar.JsonNode;

import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static apiaddicts.sonar.openapi.utils.JsonNodeUtils.*;
import org.apiaddicts.apitools.dosonarapi.api.v2.OpenApi2Grammar;
import org.apiaddicts.apitools.dosonarapi.api.v3.OpenApi3Grammar;
import org.apiaddicts.apitools.dosonarapi.api.v31.OpenApi31Grammar;
import org.apiaddicts.apitools.dosonarapi.sslr.yaml.grammar.JsonNode;
import org.sonar.check.RuleProperty;

public abstract class AbstractDefaultMediaTypeCheck extends BaseCheck {

Expand Down Expand Up @@ -74,58 +73,58 @@ public void visitNode(JsonNode node) {
}

private void visitV2Node(JsonNode node) {
if (node.getType() != OpenApi2Grammar.OPERATION) return;

String operation = node.key().getTokenValue().toLowerCase();
if (!(operation.equals("post") || operation.equals("put") || operation.equals("patch"))) return;

JsonNode sectionNode = node.get(section);
boolean definesMimeTypes = !sectionNode.isMissing();

if (definesMimeTypes) {
if (!supportsDefaultMimeTypeV2(node)) {
addIssue(key, message, sectionNode.key());
}
} else {
if (!globalSupportsDefaultMimeType) {
addIssue(key, message, node.key());
}
return;
}

if (!globalSupportsDefaultMimeType) {
addIssue(key, message, node.key());
}
}

private void visitV3Node(JsonNode node) {

if (node.getType() == OpenApi3Grammar.OPERATION && section.equals("consumes")) {
String operation = node.key().getTokenValue().toLowerCase();
if (operation.equals("post") || operation.equals("put") || operation.equals("patch")) {
visitContentNode(node);
} else {
JsonNode requestBodyNode = node.at("/requestBody");
if (!requestBodyNode.isMissing()) {
addIssue(key, translate("OAR010.error-request-body-not-allowed", operation.toUpperCase()), node.key());
}
}
handleConsumesOperation(node);
return;
}

if (node.getType() == OpenApi3Grammar.RESPONSES && section.equals("produces")) {
List<JsonNode> responseCodes = node.properties().stream().collect(Collectors.toList());
for (JsonNode jsonNode : responseCodes) {
if (!jsonNode.key().getTokenValue().equals("204")) {
boolean externalRefManagement = false;
if (isExternalRef(jsonNode) && externalRefNode == null) {
externalRefNode = jsonNode;
externalRefManagement = true;
}
jsonNode = resolve(jsonNode);
visitContentNode(jsonNode);
if (externalRefManagement) externalRefNode = null;
}
}
MediaTypeUtils.handleProducesResponses(node, externalRefNode, this::visitContentNode);
}
}

private void visitContentNode(JsonNode node) {
JsonNode contentNode;
if (section.equals("consumes")) {
JsonNode requestBodyNode = node.at("/requestBody");
contentNode = resolve(requestBodyNode).at("/content");
} else {
contentNode = node.at("/content");
private void handleConsumesOperation(JsonNode node) {
String operation = node.key().getTokenValue().toLowerCase();
boolean allowsBody = operation.equals("post") || operation.equals("put") || operation.equals("patch");

if (allowsBody) {
visitContentNode(node);
return;
}

JsonNode requestBodyNode = node.at("/requestBody");
if (!requestBodyNode.isMissing()) {
addIssue(key,
translate("OAR010.error-request-body-not-allowed", operation.toUpperCase()),
node.key());
}
}

private void visitContentNode(JsonNode node) {
JsonNode contentNode = MediaTypeUtils.getContentNode(node, section);

boolean definesMimeTypes = !contentNode.isMissing();
if (definesMimeTypes) {
if (!supportsDefaultMimeTypeV3(contentNode)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,17 @@
package apiaddicts.sonar.openapi.checks.format;

import apiaddicts.sonar.openapi.checks.BaseCheck;
import apiaddicts.sonar.openapi.utils.MediaTypeUtils;

import com.google.common.collect.ImmutableSet;
import com.sonar.sslr.api.AstNodeType;
import java.util.Map;
import java.util.Set;
import org.apiaddicts.apitools.dosonarapi.api.v2.OpenApi2Grammar;
import org.apiaddicts.apitools.dosonarapi.api.v3.OpenApi3Grammar;
import org.apiaddicts.apitools.dosonarapi.api.v31.OpenApi31Grammar;
import apiaddicts.sonar.openapi.checks.BaseCheck;
import org.apiaddicts.apitools.dosonarapi.sslr.yaml.grammar.JsonNode;

import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import static apiaddicts.sonar.openapi.utils.JsonNodeUtils.*;

public abstract class AbstractUndefinedMediaTypeCheck extends BaseCheck {

private static final String MESSAGE = "generic.section";
Expand Down Expand Up @@ -49,47 +46,49 @@ public void visitNode(JsonNode node) {
}

private void visitV2Node(JsonNode node) {
if (!globalDefinesMediaTypes && !definesMimeTypesV2(node)) {
addIssue(key, translate(MESSAGE, section), node.key());
if (node.getType() == OpenApi2Grammar.OPERATION) {
String operation = node.key().getTokenValue().toLowerCase();

if ((operation.equals("post") || operation.equals("put") || operation.equals("patch"))
&& !globalDefinesMediaTypes
&& !definesMimeTypesV2(node)) {

addIssue(key, translate(MESSAGE, section), node.key());
}
}
}

private void visitV3Node(JsonNode node) {
if (node.getType() == OpenApi3Grammar.OPERATION && section.equals("consumes")) {
String operation = node.key().getTokenValue().toLowerCase();
if (operation.equals("post") || operation.equals("put") || operation.equals("patch")) {
visitContentNode(node);
}
handleConsumesOperation(node);
return;
}

if (node.getType() == OpenApi3Grammar.RESPONSES && section.equals("produces")) {
List<JsonNode> responseCodes = node.properties().stream().collect(Collectors.toList());
for (JsonNode jsonNode : responseCodes) {
if (!jsonNode.key().getTokenValue().equals("204")) {
boolean externalRefManagement = false;
if (isExternalRef(jsonNode) && externalRefNode == null) {
externalRefNode = jsonNode;
externalRefManagement = true;
}
jsonNode = resolve(jsonNode);
visitContentNode(jsonNode);
if (externalRefManagement) externalRefNode = null;
}
}
MediaTypeUtils.handleProducesResponses(node, externalRefNode, this::visitContentNode);
}
}

private void handleConsumesOperation(JsonNode node) {
String operation = node.key().getTokenValue().toLowerCase();

if (operation.equals("post") || operation.equals("put") || operation.equals("patch")) {
visitContentNode(node);
}
}

private void visitContentNode(JsonNode node) {
JsonNode contentNode;

if (section.equals("consumes")) {
JsonNode requestBodyNode = node.at("/requestBody");
if (requestBodyNode.isMissing() || requestBodyNode.isNull()) {
addIssue(key, translate(MESSAGE, "requestBody"), node.key());
return;
}
contentNode = resolve(requestBodyNode).at("/content");
contentNode = MediaTypeUtils.getContentNode(node, section);
} else {
contentNode = node.at("/content");
contentNode = MediaTypeUtils.getContentNode(node, section);
}

if (!globalDefinesMediaTypes && !definesMimeTypesV3(contentNode)) {
Expand Down
35 changes: 35 additions & 0 deletions src/main/java/apiaddicts/sonar/openapi/utils/MediaTypeUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package apiaddicts.sonar.openapi.utils;

import org.apiaddicts.apitools.dosonarapi.sslr.yaml.grammar.JsonNode;
import java.util.function.Consumer;
import static apiaddicts.sonar.openapi.utils.JsonNodeUtils.isExternalRef;
import static apiaddicts.sonar.openapi.utils.JsonNodeUtils.resolve;

public class MediaTypeUtils {

private MediaTypeUtils() {
}

public static void handleProducesResponses(JsonNode node, JsonNode externalRefNode, Consumer<JsonNode> visitContentNode) {
for (JsonNode jsonNode : node.properties()) {
if (jsonNode.key().getTokenValue().equals("204")) continue;

boolean manageExternal = isExternalRef(jsonNode) && externalRefNode == null;
if (manageExternal) externalRefNode = jsonNode;

JsonNode resolved = resolve(jsonNode);
visitContentNode.accept(resolved);

if (manageExternal) externalRefNode = null;
}
}

public static JsonNode getContentNode(JsonNode node, String section) {
if ("consumes".equals(section)) {
JsonNode requestBodyNode = node.at("/requestBody");
return resolve(requestBodyNode).at("/content");
} else {
return node.at("/content");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
],
"paths": {
"/pets": {
"get": {
"post": {
"consumes": [
"application/xml"
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ consumes:
- application/json
paths:
/pets:
get:
post:
consumes:
- application/xml
responses:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
],
"paths": {
"/pets": {
"get": {
"post": {
"responses": {
"200": {
"description": "Ok"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ consumes:
- application/json
paths:
/pets:
get:
post:
responses:
200:
description: Ok
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
},
"paths": {
"/pets": {
"get": {
"post": {
"consumes": [
"application/json"
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ info:

paths:
/pets:
get:
post:
consumes:
- application/json
responses:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
},
"paths": {
"/pets": {
"get": { # Noncompliant {{OAR006: Section consumes is mandatory}}
"post": { # Noncompliant {{OAR006: Section consumes is mandatory}}
"responses": {
"200": {
"description": "Ok"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ info:
title: Swagger Petstore
paths:
/pets:
get: # Noncompliant {{OAR006: Section consumes is mandatory}}
post: # Noncompliant {{OAR006: Section consumes is mandatory}}
responses:
200:
description: Ok
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
},
"paths": {
"/pets": {
"get": { # Noncompliant {{OAR007: Section produces is mandatory}}
"post": { # Noncompliant {{OAR007: Section produces is mandatory}}
"responses": {
"200": {
"description": "Ok"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ info:
title: Swagger Petstore
paths:
/pets:
get: # Noncompliant {{OAR007: Section produces is mandatory}}
post: # Noncompliant {{OAR007: Section produces is mandatory}}
responses:
200:
description: Ok
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
],
"paths": {
"/pets": {
"get": {
"post": {
"consumes": [ # Noncompliant {{OAR009: Should indicate the default request media type}}
"application/xml"
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ consumes:
- application/json
paths:
/pets:
get:
post:
consumes: # Noncompliant {{OAR009: Should indicate the default request media type}}
- application/xml
responses:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
],
"paths": {
"/pets": {
"get": {
"post": {
"responses": {
"200": {
"description": "Ok"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ consumes:
- application/json
paths:
/pets:
get:
post:
responses:
200:
description: Ok
Loading
Loading