From 082d6c83ee4b968e865a4f76d7ef02bf8f4394d2 Mon Sep 17 00:00:00 2001 From: Melsy Huamani Date: Wed, 31 Dec 2025 20:55:06 -0500 Subject: [PATCH 1/7] fix prevent oar019 for paths ending with params --- .../parameters/OAR019SelectParameterCheck.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/main/java/apiaddicts/sonar/openapi/checks/parameters/OAR019SelectParameterCheck.java b/src/main/java/apiaddicts/sonar/openapi/checks/parameters/OAR019SelectParameterCheck.java index 47cacecc..55028eea 100644 --- a/src/main/java/apiaddicts/sonar/openapi/checks/parameters/OAR019SelectParameterCheck.java +++ b/src/main/java/apiaddicts/sonar/openapi/checks/parameters/OAR019SelectParameterCheck.java @@ -68,6 +68,10 @@ public void visitNode(JsonNode node) { String path = getPath(node); + if (endsWithPathParam(path)) { + return; + } + boolean hasParameter = hasParameterInNode(node); if (shouldIncludePath(path) && !hasParameter) { @@ -76,6 +80,14 @@ public void visitNode(JsonNode node) { } } + private boolean endsWithPathParam(String path) { + String[] segments = path.split("/"); + if (segments.length == 0) return false; + + String last = segments[segments.length - 1].trim(); + return last.matches("^\\{[^}]+\\}$"); + } + private boolean hasParameterInNode(JsonNode node) { JsonNode parametersNode = node.get("parameters"); if (parametersNode != null) { From b758565a2bdf93a8f76aafeedf4985895da1405e Mon Sep 17 00:00:00 2001 From: Melsy Huamani Date: Wed, 31 Dec 2025 21:04:29 -0500 Subject: [PATCH 2/7] add oar019 tests for paths ending with params --- CHANGELOG.md | 5 ++++ pom.xml | 2 +- .../OAR019SelectParameterCheckTest.java | 10 +++++++ .../v2/parameters/OAR019/with-param.json | 27 +++++++++++++++++++ .../v2/parameters/OAR019/with-param.yaml | 15 +++++++++++ .../v3/parameters/OAR019/with-param.json | 27 +++++++++++++++++++ .../v3/parameters/OAR019/with-param.yaml | 15 +++++++++++ 7 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 src/test/resources/checks/v2/parameters/OAR019/with-param.json create mode 100644 src/test/resources/checks/v2/parameters/OAR019/with-param.yaml create mode 100644 src/test/resources/checks/v3/parameters/OAR019/with-param.json create mode 100644 src/test/resources/checks/v3/parameters/OAR019/with-param.yaml diff --git a/CHANGELOG.md b/CHANGELOG.md index d34f6869..fd0f39b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.2.6] - 2025-12-31 + +### Fixed + - OAR019 - SelectParameterCheck + ## [1.2.5] - 2025-12-31 ### Fixed diff --git a/pom.xml b/pom.xml index e078c8c0..a69ffed0 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 org.apiaddicts.apitools.dosonarapi sonaropenapi-rules-community - 1.2.5 + 1.2.6 sonar-plugin SonarQube OpenAPI Community Rules diff --git a/src/test/java/apiaddicts/sonar/openapi/checks/parameters/OAR019SelectParameterCheckTest.java b/src/test/java/apiaddicts/sonar/openapi/checks/parameters/OAR019SelectParameterCheckTest.java index 90282011..46999060 100644 --- a/src/test/java/apiaddicts/sonar/openapi/checks/parameters/OAR019SelectParameterCheckTest.java +++ b/src/test/java/apiaddicts/sonar/openapi/checks/parameters/OAR019SelectParameterCheckTest.java @@ -40,6 +40,11 @@ public void verifyInV2WithRef() { verifyV2("with-ref"); } + @Test + public void verifyInV2PathEndingWithParam() { + verifyV3("with-param"); + } + @Test public void verifyInV3() { verifyV3("plain"); @@ -80,6 +85,11 @@ public void verifyInV31WithRef() { verifyV31("with-ref"); } + @Test + public void verifyInV3PathEndingWithParam() { + verifyV3("with-param"); + } + @Override public void verifyRule() { assertRuleProperties("OAR019 - SelectParameter - the chosen parameter must be defined in this operation", RuleType.BUG, Severity.MINOR, tags("parameters")); diff --git a/src/test/resources/checks/v2/parameters/OAR019/with-param.json b/src/test/resources/checks/v2/parameters/OAR019/with-param.json new file mode 100644 index 00000000..799521d9 --- /dev/null +++ b/src/test/resources/checks/v2/parameters/OAR019/with-param.json @@ -0,0 +1,27 @@ +{ + "swagger": "2.0", + "info": { + "title": "Swagger Petstore", + "version": "1.0.0" + }, + "paths": { + "/examples/{id}": { + "get": { + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/examples/{id}/items/{id}": { + "get": { + "responses": { + "200": { + "description": "OK" + } + } + } + } + } +} \ No newline at end of file diff --git a/src/test/resources/checks/v2/parameters/OAR019/with-param.yaml b/src/test/resources/checks/v2/parameters/OAR019/with-param.yaml new file mode 100644 index 00000000..3f4a3407 --- /dev/null +++ b/src/test/resources/checks/v2/parameters/OAR019/with-param.yaml @@ -0,0 +1,15 @@ +swagger: "2.0" +info: + title: Swagger Petstore + version: "1.0.0" +paths: + /examples/{id}: + get: + responses: + 200: + description: OK + /examples/{id}/items/{id}: + get: + responses: + 200: + description: OK diff --git a/src/test/resources/checks/v3/parameters/OAR019/with-param.json b/src/test/resources/checks/v3/parameters/OAR019/with-param.json new file mode 100644 index 00000000..1097b436 --- /dev/null +++ b/src/test/resources/checks/v3/parameters/OAR019/with-param.json @@ -0,0 +1,27 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "Swagger Petstore", + "version": "1.0.0" + }, + "paths": { + "/examples/{id}": { + "get": { + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/examples/{id}/items/{id}": { + "get": { + "responses": { + "200": { + "description": "OK" + } + } + } + } + } +} \ No newline at end of file diff --git a/src/test/resources/checks/v3/parameters/OAR019/with-param.yaml b/src/test/resources/checks/v3/parameters/OAR019/with-param.yaml new file mode 100644 index 00000000..8b012753 --- /dev/null +++ b/src/test/resources/checks/v3/parameters/OAR019/with-param.yaml @@ -0,0 +1,15 @@ +openapi: 3.0.0 +info: + title: Swagger Petstore + version: 1.0.0 +paths: + /examples/{id}: + get: + responses: + '200': + description: OK + /examples/{id}/items/{id}: + get: + responses: + '200': + description: OK \ No newline at end of file From bfac7c3c08ffd6342874f272ef0c18b30f358eca Mon Sep 17 00:00:00 2001 From: Melsy Huamani Date: Fri, 2 Jan 2026 13:50:12 -0500 Subject: [PATCH 3/7] refactor query parameter rules to use common base --- .../AbstractQueryParameterCheck.java | 157 +++++++++++++++++ .../OAR019SelectParameterCheck.java | 159 +---------------- .../OAR020ExpandParameterCheck.java | 147 +--------------- .../OAR021ExcludeParameterCheck.java | 161 +----------------- .../OAR022OrderbyParameterCheck.java | 160 +---------------- .../parameters/OAR023TotalParameterCheck.java | 146 +--------------- .../parameters/OAR024StartParameterCheck.java | 147 +--------------- .../parameters/OAR025LimitParameterCheck.java | 144 +--------------- 8 files changed, 202 insertions(+), 1019 deletions(-) create mode 100644 src/main/java/apiaddicts/sonar/openapi/checks/parameters/AbstractQueryParameterCheck.java diff --git a/src/main/java/apiaddicts/sonar/openapi/checks/parameters/AbstractQueryParameterCheck.java b/src/main/java/apiaddicts/sonar/openapi/checks/parameters/AbstractQueryParameterCheck.java new file mode 100644 index 00000000..5ae33dcc --- /dev/null +++ b/src/main/java/apiaddicts/sonar/openapi/checks/parameters/AbstractQueryParameterCheck.java @@ -0,0 +1,157 @@ +package apiaddicts.sonar.openapi.checks.parameters; + +import apiaddicts.sonar.openapi.checks.BaseCheck; +import com.google.common.collect.ImmutableSet; +import com.sonar.sslr.api.AstNode; +import com.sonar.sslr.api.AstNodeType; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Collectors; +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 AbstractQueryParameterCheck extends BaseCheck { + + protected static final String DEFAULT_PATH = "/examples"; + protected static final String PATH_STRATEGY = "/include"; + protected Set paths; + protected JsonNode rootNode; + + + @RuleProperty( + key = "paths", + description = "List of explicit paths to include/exclude from this rule separated by comma", + defaultValue = DEFAULT_PATH + ) + protected String pathsStr = DEFAULT_PATH; + + @RuleProperty( + key = "pathValidationStrategy", + description = "Path validation strategy (include/exclude)", + defaultValue = PATH_STRATEGY + ) + protected String pathCheckStrategy = PATH_STRATEGY; + + protected String parameterName; + + protected abstract String getDefaultParameterName(); + + @Override + public Set subscribedKinds() { + return ImmutableSet.of(OpenApi2Grammar.OPERATION, OpenApi3Grammar.OPERATION, OpenApi31Grammar.OPERATION); + } + + @Override + protected void visitFile(JsonNode root) { + this.rootNode = root; + paths = parsePaths(pathsStr); + + if (parameterName == null || parameterName.isEmpty()) { + parameterName = getDefaultParameterName(); + } + super.visitFile(root); + } + + protected boolean hasParameterInNode(JsonNode node) { + JsonNode parametersNode = node.get("parameters"); + if (parametersNode != null) { + + for (JsonNode parameterNode : parametersNode.elements()) { + if (isRefParameter(parameterNode) && hasNamedRefParameter(parameterNode)) { + return true; + } + if (hasDirectParameter(parameterNode)) { + return true; + } + } + } + return false; + } + + protected boolean isRefParameter(JsonNode parameterNode) { + JsonNode refNode = parameterNode.get("$ref"); + if (refNode != null) { + return true; + } + return false; + } + + protected boolean hasNamedRefParameter(JsonNode parameterNode) { + String refValue = parameterNode.get("$ref").getTokenValue(); + JsonNode refParameterNode = resolveReference(refValue, rootNode); + if (refParameterNode != null) { + JsonNode nameNode = refParameterNode.get("name"); + JsonNode inNode = refParameterNode.get("in"); + return inNode != null && "query".equals(inNode.getTokenValue()) && nameNode != null && parameterName.equals(nameNode.getTokenValue()); + } + return false; + } + + protected boolean hasDirectParameter(JsonNode parameterNode) { + JsonNode nameNode = parameterNode.get("name"); + JsonNode inNode = parameterNode.get("in"); + return inNode != null && "query".equals(inNode.getTokenValue()) && nameNode != null && parameterName.equals(nameNode.getTokenValue()); + } + + protected String getPath(JsonNode node) { + StringBuilder pathBuilder = new StringBuilder(); + AstNode pathNode = node.getFirstAncestor(OpenApi2Grammar.PATH, OpenApi3Grammar.PATH, OpenApi31Grammar.PATH); + if (pathNode != null) { + while (pathNode.getType() != OpenApi2Grammar.PATH && pathNode.getType() != OpenApi3Grammar.PATH && pathNode.getType() != OpenApi31Grammar.PATH) { + pathNode = pathNode.getParent(); + } + pathBuilder.append(((JsonNode) pathNode).key().getTokenValue()); + } + return pathBuilder.toString(); + } + + protected boolean shouldIncludePath(String path) { + if (pathCheckStrategy.equals("/exclude")) { + return !paths.contains(path); + } else if (pathCheckStrategy.equals("/include")) { + return paths.contains(path); + } + return false; + } + + protected Set parsePaths(String pathsStr) { + if (!pathsStr.trim().isEmpty()) { + return Arrays.stream(pathsStr.split(",")) + .map(String::trim) + .collect(Collectors.toSet()); + } else { + return new HashSet<>(); + } + } + + protected JsonNode resolveReference(String refValue, JsonNode root) { + if (refValue == null || !refValue.startsWith("#/")) { + return null; + } + + String pathToReference = refValue.substring(2); + String[] pathParts = pathToReference.split("/"); + + JsonNode currentNode = root; + for (String part : pathParts) { + if (currentNode == null) { + return null; + } + currentNode = currentNode.get(part); + } + + return currentNode; + } + + protected boolean endsWithPathParam(String path) { + String[] segments = path.split("/"); + if (segments.length == 0) return false; + + String last = segments[segments.length - 1].trim(); + return last.matches("^\\{[^}]+\\}$"); + } +} \ No newline at end of file diff --git a/src/main/java/apiaddicts/sonar/openapi/checks/parameters/OAR019SelectParameterCheck.java b/src/main/java/apiaddicts/sonar/openapi/checks/parameters/OAR019SelectParameterCheck.java index 55028eea..978078d7 100644 --- a/src/main/java/apiaddicts/sonar/openapi/checks/parameters/OAR019SelectParameterCheck.java +++ b/src/main/java/apiaddicts/sonar/openapi/checks/parameters/OAR019SelectParameterCheck.java @@ -1,65 +1,25 @@ package apiaddicts.sonar.openapi.checks.parameters; -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.Rule; import org.sonar.check.RuleProperty; -import apiaddicts.sonar.openapi.checks.BaseCheck; - -import com.google.common.collect.ImmutableSet; -import com.sonar.sslr.api.AstNode; -import com.sonar.sslr.api.AstNodeType; - -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; -import java.util.stream.Collectors; @Rule(key = OAR019SelectParameterCheck.KEY) -public class OAR019SelectParameterCheck extends BaseCheck { +public class OAR019SelectParameterCheck extends AbstractQueryParameterCheck { public static final String KEY = "OAR019"; private static final String MESSAGE = "OAR019.error"; - private static final String DEFAULT_PATH = "/examples"; - private static final String PATH_STRATEGY = "/include"; - private static final String PARAM_NAME = "$select"; - - @RuleProperty( - key = "paths", - description = "List of explicit paths to include/exclude from this rule separated by comma", - defaultValue = DEFAULT_PATH - ) - private String pathsStr = DEFAULT_PATH; - - @RuleProperty( - key = "pathValidationStrategy", - description = "Path validation strategy (include/exclude)", - defaultValue = PATH_STRATEGY - ) - private String pathCheckStrategy = PATH_STRATEGY; @RuleProperty( key = "parameterName", description = "Name of the parameter to be checked", - defaultValue = PARAM_NAME + defaultValue = "$select" ) - private String parameterName = PARAM_NAME; - - private Set paths; - private JsonNode rootNode; - - @Override - public Set subscribedKinds() { - return ImmutableSet.of(OpenApi2Grammar.OPERATION, OpenApi3Grammar.OPERATION, OpenApi31Grammar.OPERATION); - } + protected String parameterName = "$select"; @Override - protected void visitFile(JsonNode root) { - this.rootNode = root; - paths = parsePaths(pathsStr); - super.visitFile(root); + protected String getDefaultParameterName() { + return parameterName; } @Override @@ -67,118 +27,13 @@ public void visitNode(JsonNode node) { if ("get".equals(node.key().getTokenValue())) { String path = getPath(node); - - if (endsWithPathParam(path)) { - return; - } + if (endsWithPathParam(path)) return; boolean hasParameter = hasParameterInNode(node); if (shouldIncludePath(path) && !hasParameter) { - addIssue(KEY, translate(MESSAGE, PARAM_NAME), node.key()); - } - } - } - - private boolean endsWithPathParam(String path) { - String[] segments = path.split("/"); - if (segments.length == 0) return false; - - String last = segments[segments.length - 1].trim(); - return last.matches("^\\{[^}]+\\}$"); - } - - private boolean hasParameterInNode(JsonNode node) { - JsonNode parametersNode = node.get("parameters"); - if (parametersNode != null) { - - for (JsonNode parameterNode : parametersNode.elements()) { - if (isRefParameter(parameterNode) && hasNamedRefParameter(parameterNode)) { - return true; - } else if (hasDirectParameter(parameterNode)) { - return true; - } - } - } - return false; - } - - private boolean isRefParameter(JsonNode parameterNode) { - JsonNode refNode = parameterNode.get("$ref"); - if (refNode != null) { - return true; - } - return false; - } - - private boolean hasNamedRefParameter(JsonNode parameterNode) { - String refValue = parameterNode.get("$ref").getTokenValue(); - JsonNode refParameterNode = resolveReference(refValue, rootNode); - if (refParameterNode != null) { - JsonNode nameNode = refParameterNode.get("name"); - JsonNode inNode = refParameterNode.get("in"); - return inNode != null && "query".equals(inNode.getTokenValue()) && nameNode != null && parameterName.equals(nameNode.getTokenValue()); - } - return false; - } - - private boolean hasDirectParameter(JsonNode parameterNode) { - JsonNode nameNode = parameterNode.get("name"); - JsonNode inNode = parameterNode.get("in"); - return inNode != null && "query".equals(inNode.getTokenValue()) && nameNode != null && parameterName.equals(nameNode.getTokenValue()); - } - - private String getPath(JsonNode node) { - StringBuilder pathBuilder = new StringBuilder(); - AstNode pathNode = node.getFirstAncestor(OpenApi2Grammar.PATH, OpenApi3Grammar.PATH, OpenApi31Grammar.PATH); - if (pathNode != null) { - while (pathNode.getType() != OpenApi2Grammar.PATH && pathNode.getType() != OpenApi3Grammar.PATH && pathNode.getType() != OpenApi31Grammar.PATH) { - pathNode = pathNode.getParent(); + addIssue(KEY, translate(MESSAGE, parameterName), node.key()); } - pathBuilder.append(((JsonNode) pathNode).key().getTokenValue()); - } - return pathBuilder.toString(); - } - - private boolean shouldIncludePath(String path) { - if (pathCheckStrategy.equals("/exclude")) { - return !paths.contains(path); - } else if (pathCheckStrategy.equals("/include")) { - return paths.contains(path); - } - return false; - } - - private Set parsePaths(String pathsStr) { - if (!pathsStr.trim().isEmpty()) { - return Arrays.stream(pathsStr.split(",")) - .map(String::trim) - .collect(Collectors.toSet()); - } else { - return new HashSet<>(); - } - } - - private JsonNode resolveReference(String refValue, JsonNode root) { - if (refValue == null || !refValue.startsWith("#/")) { - return null; - } - - String pathToReference = refValue.substring(2); - String[] pathParts = pathToReference.split("/"); - - JsonNode currentNode = root; - for (String part : pathParts) { - if (currentNode == null) { - return null; - } - currentNode = currentNode.get(part); - } - - if (currentNode == null) { - } else { } - - return currentNode; } } \ No newline at end of file diff --git a/src/main/java/apiaddicts/sonar/openapi/checks/parameters/OAR020ExpandParameterCheck.java b/src/main/java/apiaddicts/sonar/openapi/checks/parameters/OAR020ExpandParameterCheck.java index 08cafe2f..af87c022 100644 --- a/src/main/java/apiaddicts/sonar/openapi/checks/parameters/OAR020ExpandParameterCheck.java +++ b/src/main/java/apiaddicts/sonar/openapi/checks/parameters/OAR020ExpandParameterCheck.java @@ -1,66 +1,25 @@ package apiaddicts.sonar.openapi.checks.parameters; -import java.util.Set; -import java.util.stream.Collectors; - -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.Rule; import org.sonar.check.RuleProperty; -import apiaddicts.sonar.openapi.checks.BaseCheck; - -import com.google.common.collect.ImmutableSet; -import com.sonar.sslr.api.AstNode; -import com.sonar.sslr.api.AstNodeType; - -import java.util.Arrays; -import java.util.HashSet; @Rule(key = OAR020ExpandParameterCheck.KEY) -public class OAR020ExpandParameterCheck extends BaseCheck { +public class OAR020ExpandParameterCheck extends AbstractQueryParameterCheck { public static final String KEY = "OAR020"; private static final String MESSAGE = "OAR020.error"; - private static final String DEFAULT_PATH = "/examples"; - private static final String PATH_STRATEGY = "/include"; - private static final String PARAM_NAME = "$expand"; - - @RuleProperty( - key = "paths", - description = "List of explicit paths to include/exclude from this rule separated by comma", - defaultValue = DEFAULT_PATH - ) - private String pathsStr = DEFAULT_PATH; - - @RuleProperty( - key = "pathValidationStrategy", - description = "Path validation strategy (include/exclude)", - defaultValue = PATH_STRATEGY - ) - private String pathCheckStrategy = PATH_STRATEGY; @RuleProperty( key = "parameterName", description = "Name of the parameter to be checked", - defaultValue = PARAM_NAME + defaultValue = "$expand" ) - private String parameterName = PARAM_NAME; - - private Set paths; - private JsonNode rootNode; + protected String parameterName = "$expand"; @Override - public Set subscribedKinds() { - return ImmutableSet.of(OpenApi2Grammar.OPERATION, OpenApi3Grammar.OPERATION, OpenApi31Grammar.OPERATION); - } - - @Override - protected void visitFile(JsonNode root) { - this.rootNode = root; - paths = parsePaths(pathsStr); - super.visitFile(root); + protected String getDefaultParameterName() { + return parameterName; } @Override @@ -72,102 +31,8 @@ public void visitNode(JsonNode node) { boolean hasParameter = hasParameterInNode(node); if (shouldIncludePath(path) && !hasParameter) { - addIssue(KEY, translate(MESSAGE, PARAM_NAME), node.key()); - } - } - } - - private boolean hasParameterInNode(JsonNode node) { - JsonNode parametersNode = node.get("parameters"); - if (parametersNode != null) { - - for (JsonNode parameterNode : parametersNode.elements()) { - if (isRefParameter(parameterNode) && hasNamedRefParameter(parameterNode)) { - return true; - } else if (hasDirectParameter(parameterNode)) { - return true; - } - } - } - return false; - } - - private boolean isRefParameter(JsonNode parameterNode) { - JsonNode refNode = parameterNode.get("$ref"); - if (refNode != null) { - return true; - } - return false; - } - - private boolean hasNamedRefParameter(JsonNode parameterNode) { - String refValue = parameterNode.get("$ref").getTokenValue(); - JsonNode refParameterNode = resolveReference(refValue, rootNode); - if (refParameterNode != null) { - JsonNode nameNode = refParameterNode.get("name"); - JsonNode inNode = refParameterNode.get("in"); - return inNode != null && "query".equals(inNode.getTokenValue()) && nameNode != null && parameterName.equals(nameNode.getTokenValue()); - } - return false; - } - - private boolean hasDirectParameter(JsonNode parameterNode) { - JsonNode nameNode = parameterNode.get("name"); - JsonNode inNode = parameterNode.get("in"); - return inNode != null && "query".equals(inNode.getTokenValue()) && nameNode != null && parameterName.equals(nameNode.getTokenValue()); - } - - private String getPath(JsonNode node) { - StringBuilder pathBuilder = new StringBuilder(); - AstNode pathNode = node.getFirstAncestor(OpenApi2Grammar.PATH, OpenApi3Grammar.PATH, OpenApi31Grammar.PATH); - if (pathNode != null) { - while (pathNode.getType() != OpenApi2Grammar.PATH && pathNode.getType() != OpenApi3Grammar.PATH && pathNode.getType() != OpenApi31Grammar.PATH) { - pathNode = pathNode.getParent(); + addIssue(KEY, translate(MESSAGE, parameterName), node.key()); } - pathBuilder.append(((JsonNode) pathNode).key().getTokenValue()); - } - return pathBuilder.toString(); - } - - private boolean shouldIncludePath(String path) { - if (pathCheckStrategy.equals("/exclude")) { - return !paths.contains(path); - } else if (pathCheckStrategy.equals("/include")) { - return paths.contains(path); - } - return false; - } - - private Set parsePaths(String pathsStr) { - if (!pathsStr.trim().isEmpty()) { - return Arrays.stream(pathsStr.split(",")) - .map(String::trim) - .collect(Collectors.toSet()); - } else { - return new HashSet<>(); - } - } - - private JsonNode resolveReference(String refValue, JsonNode root) { - if (refValue == null || !refValue.startsWith("#/")) { - return null; - } - - String pathToReference = refValue.substring(2); - String[] pathParts = pathToReference.split("/"); - - JsonNode currentNode = root; - for (String part : pathParts) { - if (currentNode == null) { - return null; - } - currentNode = currentNode.get(part); - } - - if (currentNode == null) { - } else { } - - return currentNode; } } \ No newline at end of file diff --git a/src/main/java/apiaddicts/sonar/openapi/checks/parameters/OAR021ExcludeParameterCheck.java b/src/main/java/apiaddicts/sonar/openapi/checks/parameters/OAR021ExcludeParameterCheck.java index e6057e7c..8d75b37d 100644 --- a/src/main/java/apiaddicts/sonar/openapi/checks/parameters/OAR021ExcludeParameterCheck.java +++ b/src/main/java/apiaddicts/sonar/openapi/checks/parameters/OAR021ExcludeParameterCheck.java @@ -1,67 +1,25 @@ package apiaddicts.sonar.openapi.checks.parameters; -import java.util.Set; -import java.util.stream.Collectors; - -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.Rule; import org.sonar.check.RuleProperty; -import apiaddicts.sonar.openapi.checks.BaseCheck; - -import com.google.common.collect.ImmutableSet; -import com.sonar.sslr.api.AstNode; -import com.sonar.sslr.api.AstNodeType; - -import java.util.Arrays; -import java.util.HashSet; @Rule(key = OAR021ExcludeParameterCheck.KEY) -public class OAR021ExcludeParameterCheck extends BaseCheck { +public class OAR021ExcludeParameterCheck extends AbstractQueryParameterCheck { public static final String KEY = "OAR021"; private static final String MESSAGE = "OAR021.error"; - private static final String DEFAULT_PATH = "/examples"; - private static final String PATH_STRATEGY = "/include"; - private static final String PARAM_NAME = "$exclude"; - - @RuleProperty( - key = "paths", - description = "List of explicit paths to include/exclude from this rule separated by comma", - defaultValue = DEFAULT_PATH - ) - private String pathsStr = DEFAULT_PATH; - - @RuleProperty( - key = "pathValidationStrategy", - description = "Path validation strategy (include/exclude)", - defaultValue = PATH_STRATEGY - ) - private String pathCheckStrategy = PATH_STRATEGY; @RuleProperty( key = "parameterName", description = "Name of the parameter to be checked", - defaultValue = PARAM_NAME + defaultValue = "$exclude" ) - private String parameterName = PARAM_NAME; - - private Set paths; - private JsonNode rootNode; - - @Override - public Set subscribedKinds() { - return ImmutableSet.of(OpenApi2Grammar.OPERATION, OpenApi3Grammar.OPERATION, OpenApi31Grammar.OPERATION); - } + protected String parameterName = "$exclude"; @Override - protected void visitFile(JsonNode root) { - this.rootNode = root; - paths = parsePaths(pathsStr); - - super.visitFile(root); + protected String getDefaultParameterName() { + return parameterName; } @Override @@ -69,118 +27,13 @@ public void visitNode(JsonNode node) { if ("get".equals(node.key().getTokenValue())) { String path = getPath(node); - - if (endsWithPathParam(path)) { - return; - } + if (endsWithPathParam(path)) return; boolean hasParameter = hasParameterInNode(node); if (shouldIncludePath(path) && !hasParameter) { - addIssue(KEY, translate(MESSAGE, PARAM_NAME), node.key()); - } - } - } - - private boolean endsWithPathParam(String path) { - String[] segments = path.split("/"); - if (segments.length == 0) return false; - - String last = segments[segments.length - 1].trim(); - return last.matches("^\\{[^}]+\\}$"); - } - - private boolean hasParameterInNode(JsonNode node) { - JsonNode parametersNode = node.get("parameters"); - if (parametersNode != null) { - - for (JsonNode parameterNode : parametersNode.elements()) { - if (isRefParameter(parameterNode) && hasNamedRefParameter(parameterNode)) { - return true; - } else if (hasDirectParameter(parameterNode)) { - return true; - } + addIssue(KEY, translate(MESSAGE, parameterName), node.key()); } } - return false; - } - - private boolean isRefParameter(JsonNode parameterNode) { - JsonNode refNode = parameterNode.get("$ref"); - if (refNode != null) { - return true; - } - return false; - } - - private boolean hasNamedRefParameter(JsonNode parameterNode) { - String refValue = parameterNode.get("$ref").getTokenValue(); - JsonNode refParameterNode = resolveReference(refValue, rootNode); - if (refParameterNode != null) { - JsonNode nameNode = refParameterNode.get("name"); - JsonNode inNode = refParameterNode.get("in"); - return inNode != null && "query".equals(inNode.getTokenValue()) && nameNode != null && parameterName.equals(nameNode.getTokenValue()); - } - return false; - } - - private boolean hasDirectParameter(JsonNode parameterNode) { - JsonNode nameNode = parameterNode.get("name"); - JsonNode inNode = parameterNode.get("in"); - return inNode != null && "query".equals(inNode.getTokenValue()) && nameNode != null && parameterName.equals(nameNode.getTokenValue()); - } - - private String getPath(JsonNode node) { - StringBuilder pathBuilder = new StringBuilder(); - AstNode pathNode = node.getFirstAncestor(OpenApi2Grammar.PATH, OpenApi3Grammar.PATH, OpenApi31Grammar.PATH); - if (pathNode != null) { - while (pathNode.getType() != OpenApi2Grammar.PATH && pathNode.getType() != OpenApi3Grammar.PATH && pathNode.getType() != OpenApi31Grammar.PATH) { - pathNode = pathNode.getParent(); - } - pathBuilder.append(((JsonNode) pathNode).key().getTokenValue()); - } - return pathBuilder.toString(); - } - - private boolean shouldIncludePath(String path) { - if (pathCheckStrategy.equals("/exclude")) { - return !paths.contains(path); - } else if (pathCheckStrategy.equals("/include")) { - return paths.contains(path); - } - return false; - } - - private Set parsePaths(String pathsStr) { - if (!pathsStr.trim().isEmpty()) { - return Arrays.stream(pathsStr.split(",")) - .map(String::trim) - .collect(Collectors.toSet()); - } else { - return new HashSet<>(); - } - } - - private JsonNode resolveReference(String refValue, JsonNode root) { - if (refValue == null || !refValue.startsWith("#/")) { - return null; - } - - String pathToReference = refValue.substring(2); - String[] pathParts = pathToReference.split("/"); - - JsonNode currentNode = root; - for (String part : pathParts) { - if (currentNode == null) { - return null; - } - currentNode = currentNode.get(part); - } - - if (currentNode == null) { - } else { - } - - return currentNode; } } \ No newline at end of file diff --git a/src/main/java/apiaddicts/sonar/openapi/checks/parameters/OAR022OrderbyParameterCheck.java b/src/main/java/apiaddicts/sonar/openapi/checks/parameters/OAR022OrderbyParameterCheck.java index 30924966..24dc0a67 100644 --- a/src/main/java/apiaddicts/sonar/openapi/checks/parameters/OAR022OrderbyParameterCheck.java +++ b/src/main/java/apiaddicts/sonar/openapi/checks/parameters/OAR022OrderbyParameterCheck.java @@ -1,66 +1,25 @@ package apiaddicts.sonar.openapi.checks.parameters; -import java.util.Set; -import java.util.stream.Collectors; - -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.Rule; import org.sonar.check.RuleProperty; -import apiaddicts.sonar.openapi.checks.BaseCheck; - -import com.google.common.collect.ImmutableSet; -import com.sonar.sslr.api.AstNode; -import com.sonar.sslr.api.AstNodeType; - -import java.util.Arrays; -import java.util.HashSet; @Rule(key = OAR022OrderbyParameterCheck.KEY) -public class OAR022OrderbyParameterCheck extends BaseCheck { +public class OAR022OrderbyParameterCheck extends AbstractQueryParameterCheck { public static final String KEY = "OAR022"; private static final String MESSAGE = "OAR022.error"; - private static final String DEFAULT_PATH = "/examples"; - private static final String PATH_STRATEGY = "/include"; - private static final String PARAM_NAME = "$orderby"; - - @RuleProperty( - key = "paths", - description = "List of explicit paths to include/exclude from this rule separated by comma", - defaultValue = DEFAULT_PATH - ) - private String pathsStr = DEFAULT_PATH; - - @RuleProperty( - key = "pathValidationStrategy", - description = "Path validation strategy (include/exclude)", - defaultValue = PATH_STRATEGY - ) - private String pathCheckStrategy = PATH_STRATEGY; @RuleProperty( key = "parameterName", description = "Name of the parameter to be checked", - defaultValue = PARAM_NAME + defaultValue = "$orderby" ) - private String parameterName = PARAM_NAME; - - private Set paths; - private JsonNode rootNode; - - @Override - public Set subscribedKinds() { - return ImmutableSet.of(OpenApi2Grammar.OPERATION, OpenApi3Grammar.OPERATION, OpenApi31Grammar.OPERATION); - } + protected String parameterName = "$orderby"; @Override - protected void visitFile(JsonNode root) { - this.rootNode = root; - paths = parsePaths(pathsStr); - super.visitFile(root); + protected String getDefaultParameterName() { + return parameterName; } @Override @@ -68,118 +27,13 @@ public void visitNode(JsonNode node) { if ("get".equals(node.key().getTokenValue())) { String path = getPath(node); + if (endsWithPathParam(path)) return; boolean hasParameter = hasParameterInNode(node); if (shouldIncludePath(path) && !hasParameter) { - addIssue(KEY, translate(MESSAGE, PARAM_NAME), node.key()); - } - } - } - - private boolean hasParameterInNode(JsonNode node) { - JsonNode parametersNode = node.get("parameters"); - if (parametersNode != null) { - - for (JsonNode parameterNode : parametersNode.elements()) { - if (isRefParameter(parameterNode) && hasNamedRefParameter(parameterNode)) { - return true; - } else if (hasDirectParameter(parameterNode)) { - return true; - } - } - } - return false; - } - - private boolean isRefParameter(JsonNode parameterNode) { - JsonNode refNode = parameterNode.get("$ref"); - if (refNode != null) { - return true; - } - return false; - } - - private boolean hasNamedRefParameter(JsonNode parameterNode) { - String refValue = parameterNode.get("$ref").getTokenValue(); - JsonNode refParameterNode = resolveReference(refValue, rootNode); - if (refParameterNode != null) { - JsonNode nameNode = refParameterNode.get("name"); - JsonNode inNode = refParameterNode.get("in"); - return inNode != null && "query".equals(inNode.getTokenValue()) && nameNode != null && parameterName.equals(nameNode.getTokenValue()); - } - return false; - } - - private boolean hasDirectParameter(JsonNode parameterNode) { - JsonNode nameNode = parameterNode.get("name"); - JsonNode inNode = parameterNode.get("in"); - return inNode != null && "query".equals(inNode.getTokenValue()) && nameNode != null && parameterName.equals(nameNode.getTokenValue()); - } - - private String getPath(JsonNode node) { - StringBuilder pathBuilder = new StringBuilder(); - AstNode pathNode = node.getFirstAncestor(OpenApi2Grammar.PATH, OpenApi3Grammar.PATH , OpenApi31Grammar.PATH); - if (pathNode != null) { - while (pathNode.getType() != OpenApi2Grammar.PATH && pathNode.getType() != OpenApi3Grammar.PATH && pathNode.getType() != OpenApi31Grammar.PATH) { - pathNode = pathNode.getParent(); + addIssue(KEY, translate(MESSAGE, parameterName), node.key()); } - pathBuilder.append(((JsonNode) pathNode).key().getTokenValue()); - } - return pathBuilder.toString(); - } - - private boolean endsWithPathParam(String path) { - String[] segments = path.split("/"); - if (segments.length == 0) return false; - - String last = segments[segments.length - 1].trim(); - return last.matches("^\\{[^}]+\\}$"); - } - - private boolean shouldIncludePath(String path) { - if (endsWithPathParam(path)) { - return false; - } - - if (pathCheckStrategy.equals("/exclude")) { - return !paths.contains(path); - } else if (pathCheckStrategy.equals("/include")) { - return paths.contains(path); - } - return false; - } - - private Set parsePaths(String pathsStr) { - if (!pathsStr.trim().isEmpty()) { - return Arrays.stream(pathsStr.split(",")) - .map(String::trim) - .collect(Collectors.toSet()); - } else { - return new HashSet<>(); - } - } - - private JsonNode resolveReference(String refValue, JsonNode root) { - if (refValue == null || !refValue.startsWith("#/")) { - return null; - } - - String pathToReference = refValue.substring(2); - String[] pathParts = pathToReference.split("/"); - - JsonNode currentNode = root; - for (String part : pathParts) { - if (currentNode == null) { - return null; - } - currentNode = currentNode.get(part); - } - - if (currentNode == null) { - } else { } - - return currentNode; } } \ No newline at end of file diff --git a/src/main/java/apiaddicts/sonar/openapi/checks/parameters/OAR023TotalParameterCheck.java b/src/main/java/apiaddicts/sonar/openapi/checks/parameters/OAR023TotalParameterCheck.java index b8226c4a..7ef8580f 100644 --- a/src/main/java/apiaddicts/sonar/openapi/checks/parameters/OAR023TotalParameterCheck.java +++ b/src/main/java/apiaddicts/sonar/openapi/checks/parameters/OAR023TotalParameterCheck.java @@ -1,65 +1,25 @@ package apiaddicts.sonar.openapi.checks.parameters; -import java.util.Set; -import java.util.stream.Collectors; - -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.Rule; import org.sonar.check.RuleProperty; -import apiaddicts.sonar.openapi.checks.BaseCheck; - -import com.google.common.collect.ImmutableSet; -import com.sonar.sslr.api.AstNode; -import com.sonar.sslr.api.AstNodeType; - -import java.util.Arrays; -import java.util.HashSet; @Rule(key = OAR023TotalParameterCheck.KEY) -public class OAR023TotalParameterCheck extends BaseCheck { +public class OAR023TotalParameterCheck extends AbstractQueryParameterCheck { public static final String KEY = "OAR023"; private static final String MESSAGE = "OAR023.error"; - private static final String DEFAULT_PATH = "/examples"; - private static final String PATH_STRATEGY = "/include"; - private static final String PARAM_NAME = "$total"; - - @RuleProperty( - key = "paths", - description = "List of explicit paths to include/exclude from this rule separated by comma", - defaultValue = DEFAULT_PATH - ) - private String pathsStr = DEFAULT_PATH; - - @RuleProperty( - key = "pathValidationStrategy", - description = "Path validation strategy (include/exclude)", - defaultValue = PATH_STRATEGY - ) - private String pathCheckStrategy = PATH_STRATEGY; @RuleProperty( key = "parameterName", description = "Name of the parameter to be checked", - defaultValue = PARAM_NAME + defaultValue = "$total" ) - private String parameterName = PARAM_NAME; + protected String parameterName = "$total"; - private Set paths; - private JsonNode rootNode; @Override - public Set subscribedKinds() { - return ImmutableSet.of(OpenApi2Grammar.OPERATION, OpenApi3Grammar.OPERATION, OpenApi31Grammar.OPERATION); - } - - @Override - protected void visitFile(JsonNode root) { - this.rootNode = root; - paths = parsePaths(pathsStr); - super.visitFile(root); + protected String getDefaultParameterName() { + return parameterName; } @Override @@ -71,102 +31,8 @@ public void visitNode(JsonNode node) { boolean hasParameter = hasParameterInNode(node); if (shouldIncludePath(path) && !hasParameter) { - addIssue(KEY, translate(MESSAGE, PARAM_NAME), node.key()); - } - } - } - - private boolean hasParameterInNode(JsonNode node) { - JsonNode parametersNode = node.get("parameters"); - if (parametersNode != null) { - - for (JsonNode parameterNode : parametersNode.elements()) { - if (isRefParameter(parameterNode) && hasNamedRefParameter(parameterNode)) { - return true; - } else if (hasDirectParameter(parameterNode)) { - return true; - } + addIssue(KEY, translate(MESSAGE, parameterName), node.key()); } } - return false; - } - - private boolean isRefParameter(JsonNode parameterNode) { - JsonNode refNode = parameterNode.get("$ref"); - if (refNode != null) { - return true; - } - return false; - } - - private boolean hasNamedRefParameter(JsonNode parameterNode) { - String refValue = parameterNode.get("$ref").getTokenValue(); - JsonNode refParameterNode = resolveReference(refValue, rootNode); - if (refParameterNode != null) { - JsonNode nameNode = refParameterNode.get("name"); - JsonNode inNode = refParameterNode.get("in"); - return inNode != null && "query".equals(inNode.getTokenValue()) && nameNode != null && parameterName.equals(nameNode.getTokenValue()); - } - return false; - } - - private boolean hasDirectParameter(JsonNode parameterNode) { - JsonNode nameNode = parameterNode.get("name"); - JsonNode inNode = parameterNode.get("in"); - return inNode != null && "query".equals(inNode.getTokenValue()) && nameNode != null && parameterName.equals(nameNode.getTokenValue()); - } - - private String getPath(JsonNode node) { - StringBuilder pathBuilder = new StringBuilder(); - AstNode pathNode = node.getFirstAncestor(OpenApi2Grammar.PATH, OpenApi3Grammar.PATH, OpenApi31Grammar.PATH); - if (pathNode != null) { - while (pathNode.getType() != OpenApi2Grammar.PATH && pathNode.getType() != OpenApi3Grammar.PATH && pathNode.getType() != OpenApi31Grammar.PATH) { - pathNode = pathNode.getParent(); - } - pathBuilder.append(((JsonNode) pathNode).key().getTokenValue()); - } - return pathBuilder.toString(); - } - - private boolean shouldIncludePath(String path) { - if (pathCheckStrategy.equals("/exclude")) { - return !paths.contains(path); - } else if (pathCheckStrategy.equals("/include")) { - return paths.contains(path); - } - return false; - } - - private Set parsePaths(String pathsStr) { - if (!pathsStr.trim().isEmpty()) { - return Arrays.stream(pathsStr.split(",")) - .map(String::trim) - .collect(Collectors.toSet()); - } else { - return new HashSet<>(); - } - } - - private JsonNode resolveReference(String refValue, JsonNode root) { - if (refValue == null || !refValue.startsWith("#/")) { - return null; - } - - String pathToReference = refValue.substring(2); - String[] pathParts = pathToReference.split("/"); - - JsonNode currentNode = root; - for (String part : pathParts) { - if (currentNode == null) { - return null; - } - currentNode = currentNode.get(part); - } - - if (currentNode == null) { - } else { - } - - return currentNode; } } \ No newline at end of file diff --git a/src/main/java/apiaddicts/sonar/openapi/checks/parameters/OAR024StartParameterCheck.java b/src/main/java/apiaddicts/sonar/openapi/checks/parameters/OAR024StartParameterCheck.java index 7da3eb0c..11ed357f 100644 --- a/src/main/java/apiaddicts/sonar/openapi/checks/parameters/OAR024StartParameterCheck.java +++ b/src/main/java/apiaddicts/sonar/openapi/checks/parameters/OAR024StartParameterCheck.java @@ -1,66 +1,25 @@ package apiaddicts.sonar.openapi.checks.parameters; -import java.util.Set; -import java.util.stream.Collectors; - -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.Rule; import org.sonar.check.RuleProperty; -import apiaddicts.sonar.openapi.checks.BaseCheck; - -import com.google.common.collect.ImmutableSet; -import com.sonar.sslr.api.AstNode; -import com.sonar.sslr.api.AstNodeType; - -import java.util.Arrays; -import java.util.HashSet; @Rule(key = OAR024StartParameterCheck.KEY) -public class OAR024StartParameterCheck extends BaseCheck { +public class OAR024StartParameterCheck extends AbstractQueryParameterCheck { public static final String KEY = "OAR024"; private static final String MESSAGE = "OAR024.error"; - private static final String DEFAULT_PATH = "/examples"; - private static final String PATH_STRATEGY = "/include"; - private static final String PARAM_NAME = "$start"; - - @RuleProperty( - key = "paths", - description = "List of explicit paths to include/exclude from this rule separated by comma", - defaultValue = DEFAULT_PATH - ) - private String pathsStr = DEFAULT_PATH; - - @RuleProperty( - key = "pathValidationStrategy", - description = "Path validation strategy (include/exclude)", - defaultValue = PATH_STRATEGY - ) - private String pathCheckStrategy = PATH_STRATEGY; @RuleProperty( key = "parameterName", description = "Name of the parameter to be checked", - defaultValue = PARAM_NAME + defaultValue = "$start" ) - private String parameterName = PARAM_NAME; - - private Set paths; - private JsonNode rootNode; + private String parameterName = "$start"; @Override - public Set subscribedKinds() { - return ImmutableSet.of(OpenApi2Grammar.OPERATION, OpenApi3Grammar.OPERATION, OpenApi31Grammar.OPERATION); - } - - @Override - protected void visitFile(JsonNode root) { - this.rootNode = root; - paths = parsePaths(pathsStr); - super.visitFile(root); + protected String getDefaultParameterName() { + return parameterName; } @Override @@ -72,102 +31,8 @@ public void visitNode(JsonNode node) { boolean hasParameter = hasParameterInNode(node); if (shouldIncludePath(path) && !hasParameter) { - addIssue(KEY, translate(MESSAGE, PARAM_NAME), node.key()); - } - } - } - - private boolean hasParameterInNode(JsonNode node) { - JsonNode parametersNode = node.get("parameters"); - if (parametersNode != null) { - - for (JsonNode parameterNode : parametersNode.elements()) { - if (isRefParameter(parameterNode) && hasNamedRefParameter(parameterNode)) { - return true; - } else if (hasDirectParameter(parameterNode)) { - return true; - } - } - } - return false; - } - - private boolean isRefParameter(JsonNode parameterNode) { - JsonNode refNode = parameterNode.get("$ref"); - if (refNode != null) { - return true; - } - return false; - } - - private boolean hasNamedRefParameter(JsonNode parameterNode) { - String refValue = parameterNode.get("$ref").getTokenValue(); - JsonNode refParameterNode = resolveReference(refValue, rootNode); - if (refParameterNode != null) { - JsonNode nameNode = refParameterNode.get("name"); - JsonNode inNode = refParameterNode.get("in"); - return inNode != null && "query".equals(inNode.getTokenValue()) && nameNode != null && parameterName.equals(nameNode.getTokenValue()); - } - return false; - } - - private boolean hasDirectParameter(JsonNode parameterNode) { - JsonNode nameNode = parameterNode.get("name"); - JsonNode inNode = parameterNode.get("in"); - return inNode != null && "query".equals(inNode.getTokenValue()) && nameNode != null && parameterName.equals(nameNode.getTokenValue()); - } - - private String getPath(JsonNode node) { - StringBuilder pathBuilder = new StringBuilder(); - AstNode pathNode = node.getFirstAncestor(OpenApi2Grammar.PATH, OpenApi3Grammar.PATH, OpenApi31Grammar.PATH); - if (pathNode != null) { - while (pathNode.getType() != OpenApi2Grammar.PATH && pathNode.getType() != OpenApi3Grammar.PATH && pathNode.getType() != OpenApi31Grammar.PATH) { - pathNode = pathNode.getParent(); + addIssue(KEY, translate(MESSAGE, parameterName), node.key()); } - pathBuilder.append(((JsonNode) pathNode).key().getTokenValue()); - } - return pathBuilder.toString(); - } - - private boolean shouldIncludePath(String path) { - if (pathCheckStrategy.equals("/exclude")) { - return !paths.contains(path); - } else if (pathCheckStrategy.equals("/include")) { - return paths.contains(path); - } - return false; - } - - private Set parsePaths(String pathsStr) { - if (!pathsStr.trim().isEmpty()) { - return Arrays.stream(pathsStr.split(",")) - .map(String::trim) - .collect(Collectors.toSet()); - } else { - return new HashSet<>(); - } - } - - private JsonNode resolveReference(String refValue, JsonNode root) { - if (refValue == null || !refValue.startsWith("#/")) { - return null; - } - - String pathToReference = refValue.substring(2); - String[] pathParts = pathToReference.split("/"); - - JsonNode currentNode = root; - for (String part : pathParts) { - if (currentNode == null) { - return null; - } - currentNode = currentNode.get(part); - } - - if (currentNode == null) { - } else { } - - return currentNode; } } \ No newline at end of file diff --git a/src/main/java/apiaddicts/sonar/openapi/checks/parameters/OAR025LimitParameterCheck.java b/src/main/java/apiaddicts/sonar/openapi/checks/parameters/OAR025LimitParameterCheck.java index 828637f1..eb9c0782 100644 --- a/src/main/java/apiaddicts/sonar/openapi/checks/parameters/OAR025LimitParameterCheck.java +++ b/src/main/java/apiaddicts/sonar/openapi/checks/parameters/OAR025LimitParameterCheck.java @@ -1,63 +1,25 @@ package apiaddicts.sonar.openapi.checks.parameters; -import apiaddicts.sonar.openapi.checks.BaseCheck; -import com.google.common.collect.ImmutableSet; -import com.sonar.sslr.api.AstNode; -import com.sonar.sslr.api.AstNodeType; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; -import java.util.stream.Collectors; -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.Rule; import org.sonar.check.RuleProperty; @Rule(key = OAR025LimitParameterCheck.KEY) -public class OAR025LimitParameterCheck extends BaseCheck { +public class OAR025LimitParameterCheck extends AbstractQueryParameterCheck { public static final String KEY = "OAR025"; private static final String MESSAGE = "OAR025.error"; - private static final String DEFAULT_PATH = "/examples"; - private static final String PATH_STRATEGY = "/include"; - private static final String PARAM_NAME = "$limit"; - - @RuleProperty( - key = "paths", - description = "List of explicit paths to include/exclude from this rule separated by comma", - defaultValue = DEFAULT_PATH - ) - private String pathsStr = DEFAULT_PATH; - - @RuleProperty( - key = "pathValidationStrategy", - description = "Path validation strategy (include/exclude)", - defaultValue = PATH_STRATEGY - ) - private String pathCheckStrategy = PATH_STRATEGY; @RuleProperty( key = "parameterName", description = "Name of the parameter to be checked", - defaultValue = PARAM_NAME + defaultValue = "$limit" ) - private String parameterName = PARAM_NAME; - - private Set paths; - private JsonNode rootNode; - - @Override - public Set subscribedKinds() { - return ImmutableSet.of(OpenApi2Grammar.OPERATION, OpenApi3Grammar.OPERATION, OpenApi31Grammar.OPERATION); - } + protected String parameterName = "$limit"; @Override - protected void visitFile(JsonNode root) { - this.rootNode = root; - paths = parsePaths(pathsStr); - super.visitFile(root); + protected String getDefaultParameterName() { + return parameterName; } @Override @@ -69,105 +31,11 @@ public void visitNode(JsonNode node) { boolean hasParameter = hasParameterInNode(node); if (shouldIncludePath(path) && !hasParameter && !isSingleResourcePath(path)) { - addIssue(KEY, translate(MESSAGE, PARAM_NAME), node.key()); + addIssue(KEY, translate(MESSAGE, parameterName), node.key()); } } } - private boolean hasParameterInNode(JsonNode node) { - JsonNode parametersNode = node.get("parameters"); - if (parametersNode != null) { - - for (JsonNode parameterNode : parametersNode.elements()) { - if (isRefParameter(parameterNode) && hasNamedRefParameter(parameterNode)) { - return true; - } else if (hasDirectParameter(parameterNode)) { - return true; - } - } - } - return false; - } - - private boolean isRefParameter(JsonNode parameterNode) { - JsonNode refNode = parameterNode.get("$ref"); - if (refNode != null) { - return true; - } - return false; - } - - private boolean hasNamedRefParameter(JsonNode parameterNode) { - String refValue = parameterNode.get("$ref").getTokenValue(); - JsonNode refParameterNode = resolveReference(refValue, rootNode); - if (refParameterNode != null) { - JsonNode nameNode = refParameterNode.get("name"); - JsonNode inNode = refParameterNode.get("in"); - return inNode != null && "query".equals(inNode.getTokenValue()) && nameNode != null && parameterName.equals(nameNode.getTokenValue()); - } - return false; - } - - private boolean hasDirectParameter(JsonNode parameterNode) { - JsonNode nameNode = parameterNode.get("name"); - JsonNode inNode = parameterNode.get("in"); - return inNode != null && "query".equals(inNode.getTokenValue()) && nameNode != null && parameterName.equals(nameNode.getTokenValue()); - } - - private String getPath(JsonNode node) { - StringBuilder pathBuilder = new StringBuilder(); - AstNode pathNode = node.getFirstAncestor(OpenApi2Grammar.PATH, OpenApi3Grammar.PATH, OpenApi31Grammar.PATH); - if (pathNode != null) { - while (pathNode.getType() != OpenApi2Grammar.PATH && pathNode.getType() != OpenApi3Grammar.PATH && pathNode.getType() != OpenApi31Grammar.PATH) { - pathNode = pathNode.getParent(); - } - pathBuilder.append(((JsonNode) pathNode).key().getTokenValue()); - } - return pathBuilder.toString(); - } - - private boolean shouldIncludePath(String path) { - if (pathCheckStrategy.equals("/exclude")) { - return !paths.contains(path); - } else if (pathCheckStrategy.equals("/include")) { - return paths.contains(path); - } - return false; - } - - private Set parsePaths(String pathsStr) { - if (!pathsStr.trim().isEmpty()) { - return Arrays.stream(pathsStr.split(",")) - .map(String::trim) - .collect(Collectors.toSet()); - } else { - return new HashSet<>(); - } - } - - private JsonNode resolveReference(String refValue, JsonNode root) { - if (refValue == null || !refValue.startsWith("#/")) { - return null; - } - - String pathToReference = refValue.substring(2); - String[] pathParts = pathToReference.split("/"); - - JsonNode currentNode = root; - for (String part : pathParts) { - if (currentNode == null) { - return null; - } - currentNode = currentNode.get(part); - } - - if (currentNode == null) { - } else { - } - - return currentNode; - } - private boolean isSingleResourcePath(String path) { return path.matches(".*/\\{[^/]+\\}$"); } From e5d7fa6d1fe4bde076ba3bb7b252891b762e6184 Mon Sep 17 00:00:00 2001 From: Melsy Huamani Date: Fri, 2 Jan 2026 15:22:08 -0500 Subject: [PATCH 4/7] refactor: unify visitNode logic in common base --- .../AbstractQueryParameterCheck.java | 74 +++++++++++++------ .../OAR019SelectParameterCheck.java | 36 +++------ .../OAR020ExpandParameterCheck.java | 35 +++------ .../OAR021ExcludeParameterCheck.java | 37 +++------- .../OAR022OrderbyParameterCheck.java | 39 +++------- .../parameters/OAR023TotalParameterCheck.java | 36 +++------ .../parameters/OAR024StartParameterCheck.java | 36 +++------ .../parameters/OAR025LimitParameterCheck.java | 40 +++------- .../OAR019SelectParameterCheckTest.java | 4 +- .../OAR020ExpandParameterCheckTest.java | 3 +- .../OAR021ExcludeParameterCheckTest.java | 3 +- .../OAR022OrderbyParameterCheckTest.java | 3 +- .../OAR023TotalParameterCheckTest.java | 3 +- .../OAR024StartParameterCheckTest.java | 3 +- .../OAR025LimitParameterCheckTest.java | 3 +- 15 files changed, 122 insertions(+), 233 deletions(-) diff --git a/src/main/java/apiaddicts/sonar/openapi/checks/parameters/AbstractQueryParameterCheck.java b/src/main/java/apiaddicts/sonar/openapi/checks/parameters/AbstractQueryParameterCheck.java index 5ae33dcc..92fdda27 100644 --- a/src/main/java/apiaddicts/sonar/openapi/checks/parameters/AbstractQueryParameterCheck.java +++ b/src/main/java/apiaddicts/sonar/openapi/checks/parameters/AbstractQueryParameterCheck.java @@ -18,10 +18,15 @@ public abstract class AbstractQueryParameterCheck extends BaseCheck { protected static final String DEFAULT_PATH = "/examples"; protected static final String PATH_STRATEGY = "/include"; + + protected final String ruleKey; + protected final String messageKey; + protected final String parameterName; + protected final boolean applyToParameterizedPaths; + protected Set paths; protected JsonNode rootNode; - @RuleProperty( key = "paths", description = "List of explicit paths to include/exclude from this rule separated by comma", @@ -36,9 +41,17 @@ public abstract class AbstractQueryParameterCheck extends BaseCheck { ) protected String pathCheckStrategy = PATH_STRATEGY; - protected String parameterName; - - protected abstract String getDefaultParameterName(); + protected AbstractQueryParameterCheck( + String ruleKey, + String messageKey, + String parameterName, + boolean applyToParameterizedPaths + ) { + this.ruleKey = ruleKey; + this.messageKey = messageKey; + this.parameterName = parameterName; + this.applyToParameterizedPaths = applyToParameterizedPaths; + } @Override public Set subscribedKinds() { @@ -47,13 +60,32 @@ public Set subscribedKinds() { @Override protected void visitFile(JsonNode root) { - this.rootNode = root; + this.rootNode = root; paths = parsePaths(pathsStr); + super.visitFile(root); + } - if (parameterName == null || parameterName.isEmpty()) { - parameterName = getDefaultParameterName(); + @Override + public void visitNode(JsonNode node) { + if (!"get".equals(node.key().getTokenValue())) { + return; + } + + String path = getPath(node); + + if (!applyToParameterizedPaths && endsWithPathParam(path)) { + return; + } + + boolean hasParameter = hasParameterInNode(node); + + if (shouldIncludePath(path) && !hasParameter) { + addIssue( + ruleKey, + translate(messageKey, parameterName), + node.key() + ); } - super.visitFile(root); } protected boolean hasParameterInNode(JsonNode node) { @@ -73,11 +105,7 @@ protected boolean hasParameterInNode(JsonNode node) { } protected boolean isRefParameter(JsonNode parameterNode) { - JsonNode refNode = parameterNode.get("$ref"); - if (refNode != null) { - return true; - } - return false; + return parameterNode.get("$ref") != null; } protected boolean hasNamedRefParameter(JsonNode parameterNode) { @@ -112,13 +140,21 @@ protected String getPath(JsonNode node) { protected boolean shouldIncludePath(String path) { if (pathCheckStrategy.equals("/exclude")) { return !paths.contains(path); - } else if (pathCheckStrategy.equals("/include")) { + } else if (pathCheckStrategy.equals(PATH_STRATEGY)) { return paths.contains(path); } return false; } - protected Set parsePaths(String pathsStr) { + protected boolean endsWithPathParam(String path) { + String[] segments = path.split("/"); + if (segments.length == 0) return false; + + String last = segments[segments.length - 1].trim(); + return last.matches("^\\{[^}]+\\}$"); + } + + protected Set parsePaths(String pathsStr) { if (!pathsStr.trim().isEmpty()) { return Arrays.stream(pathsStr.split(",")) .map(String::trim) @@ -146,12 +182,4 @@ protected JsonNode resolveReference(String refValue, JsonNode root) { return currentNode; } - - protected boolean endsWithPathParam(String path) { - String[] segments = path.split("/"); - if (segments.length == 0) return false; - - String last = segments[segments.length - 1].trim(); - return last.matches("^\\{[^}]+\\}$"); - } } \ No newline at end of file diff --git a/src/main/java/apiaddicts/sonar/openapi/checks/parameters/OAR019SelectParameterCheck.java b/src/main/java/apiaddicts/sonar/openapi/checks/parameters/OAR019SelectParameterCheck.java index 978078d7..262bc099 100644 --- a/src/main/java/apiaddicts/sonar/openapi/checks/parameters/OAR019SelectParameterCheck.java +++ b/src/main/java/apiaddicts/sonar/openapi/checks/parameters/OAR019SelectParameterCheck.java @@ -1,39 +1,21 @@ package apiaddicts.sonar.openapi.checks.parameters; -import org.apiaddicts.apitools.dosonarapi.sslr.yaml.grammar.JsonNode; import org.sonar.check.Rule; -import org.sonar.check.RuleProperty; @Rule(key = OAR019SelectParameterCheck.KEY) public class OAR019SelectParameterCheck extends AbstractQueryParameterCheck { public static final String KEY = "OAR019"; private static final String MESSAGE = "OAR019.error"; - - @RuleProperty( - key = "parameterName", - description = "Name of the parameter to be checked", - defaultValue = "$select" - ) - protected String parameterName = "$select"; - - @Override - protected String getDefaultParameterName() { - return parameterName; + private static final String PARAM_NAME = "$select"; + + public OAR019SelectParameterCheck() { + super( + KEY, + MESSAGE, + PARAM_NAME, + false + ); } - @Override - public void visitNode(JsonNode node) { - if ("get".equals(node.key().getTokenValue())) { - - String path = getPath(node); - if (endsWithPathParam(path)) return; - - boolean hasParameter = hasParameterInNode(node); - - if (shouldIncludePath(path) && !hasParameter) { - addIssue(KEY, translate(MESSAGE, parameterName), node.key()); - } - } - } } \ No newline at end of file diff --git a/src/main/java/apiaddicts/sonar/openapi/checks/parameters/OAR020ExpandParameterCheck.java b/src/main/java/apiaddicts/sonar/openapi/checks/parameters/OAR020ExpandParameterCheck.java index af87c022..03033f0a 100644 --- a/src/main/java/apiaddicts/sonar/openapi/checks/parameters/OAR020ExpandParameterCheck.java +++ b/src/main/java/apiaddicts/sonar/openapi/checks/parameters/OAR020ExpandParameterCheck.java @@ -1,38 +1,21 @@ package apiaddicts.sonar.openapi.checks.parameters; -import org.apiaddicts.apitools.dosonarapi.sslr.yaml.grammar.JsonNode; import org.sonar.check.Rule; -import org.sonar.check.RuleProperty; @Rule(key = OAR020ExpandParameterCheck.KEY) public class OAR020ExpandParameterCheck extends AbstractQueryParameterCheck { public static final String KEY = "OAR020"; private static final String MESSAGE = "OAR020.error"; - - @RuleProperty( - key = "parameterName", - description = "Name of the parameter to be checked", - defaultValue = "$expand" - ) - protected String parameterName = "$expand"; - - @Override - protected String getDefaultParameterName() { - return parameterName; + private static final String PARAM_NAME = "$expand"; + + public OAR020ExpandParameterCheck() { + super( + KEY, + MESSAGE, + PARAM_NAME, + true + ); } - @Override - public void visitNode(JsonNode node) { - if ("get".equals(node.key().getTokenValue())) { - - String path = getPath(node); - - boolean hasParameter = hasParameterInNode(node); - - if (shouldIncludePath(path) && !hasParameter) { - addIssue(KEY, translate(MESSAGE, parameterName), node.key()); - } - } - } } \ No newline at end of file diff --git a/src/main/java/apiaddicts/sonar/openapi/checks/parameters/OAR021ExcludeParameterCheck.java b/src/main/java/apiaddicts/sonar/openapi/checks/parameters/OAR021ExcludeParameterCheck.java index 8d75b37d..65d031db 100644 --- a/src/main/java/apiaddicts/sonar/openapi/checks/parameters/OAR021ExcludeParameterCheck.java +++ b/src/main/java/apiaddicts/sonar/openapi/checks/parameters/OAR021ExcludeParameterCheck.java @@ -1,39 +1,20 @@ package apiaddicts.sonar.openapi.checks.parameters; -import org.apiaddicts.apitools.dosonarapi.sslr.yaml.grammar.JsonNode; import org.sonar.check.Rule; -import org.sonar.check.RuleProperty; @Rule(key = OAR021ExcludeParameterCheck.KEY) public class OAR021ExcludeParameterCheck extends AbstractQueryParameterCheck { public static final String KEY = "OAR021"; private static final String MESSAGE = "OAR021.error"; - - @RuleProperty( - key = "parameterName", - description = "Name of the parameter to be checked", - defaultValue = "$exclude" - ) - protected String parameterName = "$exclude"; - - @Override - protected String getDefaultParameterName() { - return parameterName; - } - - @Override - public void visitNode(JsonNode node) { - if ("get".equals(node.key().getTokenValue())) { - - String path = getPath(node); - if (endsWithPathParam(path)) return; - - boolean hasParameter = hasParameterInNode(node); - - if (shouldIncludePath(path) && !hasParameter) { - addIssue(KEY, translate(MESSAGE, parameterName), node.key()); - } - } + private static final String PARAM_NAME = "$exclude"; + + public OAR021ExcludeParameterCheck() { + super( + KEY, + MESSAGE, + PARAM_NAME, + false + ); } } \ No newline at end of file diff --git a/src/main/java/apiaddicts/sonar/openapi/checks/parameters/OAR022OrderbyParameterCheck.java b/src/main/java/apiaddicts/sonar/openapi/checks/parameters/OAR022OrderbyParameterCheck.java index 24dc0a67..0cbe783e 100644 --- a/src/main/java/apiaddicts/sonar/openapi/checks/parameters/OAR022OrderbyParameterCheck.java +++ b/src/main/java/apiaddicts/sonar/openapi/checks/parameters/OAR022OrderbyParameterCheck.java @@ -1,39 +1,20 @@ package apiaddicts.sonar.openapi.checks.parameters; -import org.apiaddicts.apitools.dosonarapi.sslr.yaml.grammar.JsonNode; import org.sonar.check.Rule; -import org.sonar.check.RuleProperty; @Rule(key = OAR022OrderbyParameterCheck.KEY) public class OAR022OrderbyParameterCheck extends AbstractQueryParameterCheck { public static final String KEY = "OAR022"; private static final String MESSAGE = "OAR022.error"; - - @RuleProperty( - key = "parameterName", - description = "Name of the parameter to be checked", - defaultValue = "$orderby" - ) - protected String parameterName = "$orderby"; - - @Override - protected String getDefaultParameterName() { - return parameterName; - } - - @Override - public void visitNode(JsonNode node) { - if ("get".equals(node.key().getTokenValue())) { - - String path = getPath(node); - if (endsWithPathParam(path)) return; - - boolean hasParameter = hasParameterInNode(node); - - if (shouldIncludePath(path) && !hasParameter) { - addIssue(KEY, translate(MESSAGE, parameterName), node.key()); - } - } + private static final String PARAM_NAME = "$orderby"; + + public OAR022OrderbyParameterCheck() { + super( + KEY, + MESSAGE, + PARAM_NAME, + false + ); } -} \ No newline at end of file +} diff --git a/src/main/java/apiaddicts/sonar/openapi/checks/parameters/OAR023TotalParameterCheck.java b/src/main/java/apiaddicts/sonar/openapi/checks/parameters/OAR023TotalParameterCheck.java index 7ef8580f..8337d644 100644 --- a/src/main/java/apiaddicts/sonar/openapi/checks/parameters/OAR023TotalParameterCheck.java +++ b/src/main/java/apiaddicts/sonar/openapi/checks/parameters/OAR023TotalParameterCheck.java @@ -1,38 +1,20 @@ package apiaddicts.sonar.openapi.checks.parameters; -import org.apiaddicts.apitools.dosonarapi.sslr.yaml.grammar.JsonNode; import org.sonar.check.Rule; -import org.sonar.check.RuleProperty; @Rule(key = OAR023TotalParameterCheck.KEY) public class OAR023TotalParameterCheck extends AbstractQueryParameterCheck { public static final String KEY = "OAR023"; private static final String MESSAGE = "OAR023.error"; - - @RuleProperty( - key = "parameterName", - description = "Name of the parameter to be checked", - defaultValue = "$total" - ) - protected String parameterName = "$total"; - - @Override - protected String getDefaultParameterName() { - return parameterName; - } - - @Override - public void visitNode(JsonNode node) { - if ("get".equals(node.key().getTokenValue())) { - - String path = getPath(node); - - boolean hasParameter = hasParameterInNode(node); - - if (shouldIncludePath(path) && !hasParameter) { - addIssue(KEY, translate(MESSAGE, parameterName), node.key()); - } - } + private static final String PARAM_NAME = "$total"; + + public OAR023TotalParameterCheck() { + super( + KEY, + MESSAGE, + PARAM_NAME, + true + ); } } \ No newline at end of file diff --git a/src/main/java/apiaddicts/sonar/openapi/checks/parameters/OAR024StartParameterCheck.java b/src/main/java/apiaddicts/sonar/openapi/checks/parameters/OAR024StartParameterCheck.java index 11ed357f..aedcaafa 100644 --- a/src/main/java/apiaddicts/sonar/openapi/checks/parameters/OAR024StartParameterCheck.java +++ b/src/main/java/apiaddicts/sonar/openapi/checks/parameters/OAR024StartParameterCheck.java @@ -1,38 +1,20 @@ package apiaddicts.sonar.openapi.checks.parameters; -import org.apiaddicts.apitools.dosonarapi.sslr.yaml.grammar.JsonNode; import org.sonar.check.Rule; -import org.sonar.check.RuleProperty; @Rule(key = OAR024StartParameterCheck.KEY) public class OAR024StartParameterCheck extends AbstractQueryParameterCheck { public static final String KEY = "OAR024"; private static final String MESSAGE = "OAR024.error"; - - @RuleProperty( - key = "parameterName", - description = "Name of the parameter to be checked", - defaultValue = "$start" - ) - private String parameterName = "$start"; - - @Override - protected String getDefaultParameterName() { - return parameterName; - } - - @Override - public void visitNode(JsonNode node) { - if ("get".equals(node.key().getTokenValue())) { - - String path = getPath(node); - - boolean hasParameter = hasParameterInNode(node); - - if (shouldIncludePath(path) && !hasParameter) { - addIssue(KEY, translate(MESSAGE, parameterName), node.key()); - } - } + private static final String PARAM_NAME = "$start"; + + public OAR024StartParameterCheck() { + super( + KEY, + MESSAGE, + PARAM_NAME, + true + ); } } \ No newline at end of file diff --git a/src/main/java/apiaddicts/sonar/openapi/checks/parameters/OAR025LimitParameterCheck.java b/src/main/java/apiaddicts/sonar/openapi/checks/parameters/OAR025LimitParameterCheck.java index eb9c0782..2ea5e207 100644 --- a/src/main/java/apiaddicts/sonar/openapi/checks/parameters/OAR025LimitParameterCheck.java +++ b/src/main/java/apiaddicts/sonar/openapi/checks/parameters/OAR025LimitParameterCheck.java @@ -1,42 +1,20 @@ package apiaddicts.sonar.openapi.checks.parameters; -import org.apiaddicts.apitools.dosonarapi.sslr.yaml.grammar.JsonNode; import org.sonar.check.Rule; -import org.sonar.check.RuleProperty; @Rule(key = OAR025LimitParameterCheck.KEY) public class OAR025LimitParameterCheck extends AbstractQueryParameterCheck { public static final String KEY = "OAR025"; private static final String MESSAGE = "OAR025.error"; - - @RuleProperty( - key = "parameterName", - description = "Name of the parameter to be checked", - defaultValue = "$limit" - ) - protected String parameterName = "$limit"; - - @Override - protected String getDefaultParameterName() { - return parameterName; - } - - @Override - public void visitNode(JsonNode node) { - if ("get".equals(node.key().getTokenValue())) { - - String path = getPath(node); - - boolean hasParameter = hasParameterInNode(node); - - if (shouldIncludePath(path) && !hasParameter && !isSingleResourcePath(path)) { - addIssue(KEY, translate(MESSAGE, parameterName), node.key()); - } - } - } - - private boolean isSingleResourcePath(String path) { - return path.matches(".*/\\{[^/]+\\}$"); + private static final String PARAM_NAME = "$limit"; + + public OAR025LimitParameterCheck() { + super( + KEY, + MESSAGE, + PARAM_NAME, + false + ); } } \ No newline at end of file diff --git a/src/test/java/apiaddicts/sonar/openapi/checks/parameters/OAR019SelectParameterCheckTest.java b/src/test/java/apiaddicts/sonar/openapi/checks/parameters/OAR019SelectParameterCheckTest.java index 46999060..8b1fc903 100644 --- a/src/test/java/apiaddicts/sonar/openapi/checks/parameters/OAR019SelectParameterCheckTest.java +++ b/src/test/java/apiaddicts/sonar/openapi/checks/parameters/OAR019SelectParameterCheckTest.java @@ -97,10 +97,8 @@ public void verifyRule() { @Override public void verifyParameters() { - assertNumberOfParameters(3); - assertParameterProperties("parameterName", "$select", RuleParamType.STRING); + assertNumberOfParameters(2); assertParameterProperties("paths", "/examples", RuleParamType.STRING); assertParameterProperties("pathValidationStrategy", "/include", RuleParamType.STRING); - } } \ No newline at end of file diff --git a/src/test/java/apiaddicts/sonar/openapi/checks/parameters/OAR020ExpandParameterCheckTest.java b/src/test/java/apiaddicts/sonar/openapi/checks/parameters/OAR020ExpandParameterCheckTest.java index 81134ce3..2f71e70b 100644 --- a/src/test/java/apiaddicts/sonar/openapi/checks/parameters/OAR020ExpandParameterCheckTest.java +++ b/src/test/java/apiaddicts/sonar/openapi/checks/parameters/OAR020ExpandParameterCheckTest.java @@ -66,9 +66,8 @@ public void verifyRule() { @Override public void verifyParameters() { - assertNumberOfParameters(3); + assertNumberOfParameters(2); assertParameterProperties("paths", "/examples", RuleParamType.STRING); assertParameterProperties("pathValidationStrategy", "/include", RuleParamType.STRING); - assertParameterProperties("parameterName", "$expand", RuleParamType.STRING); } } \ No newline at end of file diff --git a/src/test/java/apiaddicts/sonar/openapi/checks/parameters/OAR021ExcludeParameterCheckTest.java b/src/test/java/apiaddicts/sonar/openapi/checks/parameters/OAR021ExcludeParameterCheckTest.java index 688a53f3..5d559cd3 100644 --- a/src/test/java/apiaddicts/sonar/openapi/checks/parameters/OAR021ExcludeParameterCheckTest.java +++ b/src/test/java/apiaddicts/sonar/openapi/checks/parameters/OAR021ExcludeParameterCheckTest.java @@ -76,9 +76,8 @@ public void verifyRule() { @Override public void verifyParameters() { - assertNumberOfParameters(3); + assertNumberOfParameters(2); assertParameterProperties("paths", "/examples", RuleParamType.STRING); assertParameterProperties("pathValidationStrategy", "/include", RuleParamType.STRING); - assertParameterProperties("parameterName", "$exclude", RuleParamType.STRING); } } \ No newline at end of file diff --git a/src/test/java/apiaddicts/sonar/openapi/checks/parameters/OAR022OrderbyParameterCheckTest.java b/src/test/java/apiaddicts/sonar/openapi/checks/parameters/OAR022OrderbyParameterCheckTest.java index 40981cbe..cae16c7a 100644 --- a/src/test/java/apiaddicts/sonar/openapi/checks/parameters/OAR022OrderbyParameterCheckTest.java +++ b/src/test/java/apiaddicts/sonar/openapi/checks/parameters/OAR022OrderbyParameterCheckTest.java @@ -55,9 +55,8 @@ public void verifyRule() { @Override public void verifyParameters() { - assertNumberOfParameters(3); + assertNumberOfParameters(2); assertParameterProperties("paths", "/examples", RuleParamType.STRING); assertParameterProperties("pathValidationStrategy", "/include", RuleParamType.STRING); - assertParameterProperties("parameterName", "$orderby", RuleParamType.STRING); } } \ No newline at end of file diff --git a/src/test/java/apiaddicts/sonar/openapi/checks/parameters/OAR023TotalParameterCheckTest.java b/src/test/java/apiaddicts/sonar/openapi/checks/parameters/OAR023TotalParameterCheckTest.java index 2bc638e3..7b6ab8a1 100644 --- a/src/test/java/apiaddicts/sonar/openapi/checks/parameters/OAR023TotalParameterCheckTest.java +++ b/src/test/java/apiaddicts/sonar/openapi/checks/parameters/OAR023TotalParameterCheckTest.java @@ -55,9 +55,8 @@ public void verifyRule() { @Override public void verifyParameters() { - assertNumberOfParameters(3); + assertNumberOfParameters(2); assertParameterProperties("paths", "/examples", RuleParamType.STRING); assertParameterProperties("pathValidationStrategy", "/include", RuleParamType.STRING); - assertParameterProperties("parameterName", "$total", RuleParamType.STRING); } } \ No newline at end of file diff --git a/src/test/java/apiaddicts/sonar/openapi/checks/parameters/OAR024StartParameterCheckTest.java b/src/test/java/apiaddicts/sonar/openapi/checks/parameters/OAR024StartParameterCheckTest.java index 3322bec2..b978f505 100644 --- a/src/test/java/apiaddicts/sonar/openapi/checks/parameters/OAR024StartParameterCheckTest.java +++ b/src/test/java/apiaddicts/sonar/openapi/checks/parameters/OAR024StartParameterCheckTest.java @@ -55,9 +55,8 @@ public void verifyRule() { @Override public void verifyParameters() { - assertNumberOfParameters(3); + assertNumberOfParameters(2); assertParameterProperties("paths", "/examples", RuleParamType.STRING); assertParameterProperties("pathValidationStrategy", "/include", RuleParamType.STRING); - assertParameterProperties("parameterName", "$start", RuleParamType.STRING); } } \ No newline at end of file diff --git a/src/test/java/apiaddicts/sonar/openapi/checks/parameters/OAR025LimitParameterCheckTest.java b/src/test/java/apiaddicts/sonar/openapi/checks/parameters/OAR025LimitParameterCheckTest.java index ad29af88..12a0cf26 100644 --- a/src/test/java/apiaddicts/sonar/openapi/checks/parameters/OAR025LimitParameterCheckTest.java +++ b/src/test/java/apiaddicts/sonar/openapi/checks/parameters/OAR025LimitParameterCheckTest.java @@ -55,9 +55,8 @@ public void verifyRule() { @Override public void verifyParameters() { - assertNumberOfParameters(3); + assertNumberOfParameters(2); assertParameterProperties("paths", "/examples", RuleParamType.STRING); assertParameterProperties("pathValidationStrategy", "/include", RuleParamType.STRING); - assertParameterProperties("parameterName", "$limit", RuleParamType.STRING); } } \ No newline at end of file From 40b9c50ba047e5d1f66e7559180daeb49182965c Mon Sep 17 00:00:00 2001 From: Melsy Huamani Date: Fri, 2 Jan 2026 23:18:52 -0500 Subject: [PATCH 5/7] fix prevent oar020 for paths ending with params and tests --- CHANGELOG.md | 3 ++- .../OAR020ExpandParameterCheck.java | 2 +- .../OAR020ExpandParameterCheckTest.java | 10 +++++++ .../v2/parameters/OAR020/with-param.json | 27 +++++++++++++++++++ .../v2/parameters/OAR020/with-param.yaml | 15 +++++++++++ .../v3/parameters/OAR020/with-param.json | 27 +++++++++++++++++++ .../v3/parameters/OAR020/with-param.yaml | 15 +++++++++++ 7 files changed, 97 insertions(+), 2 deletions(-) create mode 100644 src/test/resources/checks/v2/parameters/OAR020/with-param.json create mode 100644 src/test/resources/checks/v2/parameters/OAR020/with-param.yaml create mode 100644 src/test/resources/checks/v3/parameters/OAR020/with-param.json create mode 100644 src/test/resources/checks/v3/parameters/OAR020/with-param.yaml diff --git a/CHANGELOG.md b/CHANGELOG.md index fd0f39b6..cb9b5d6e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,10 +5,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [1.2.6] - 2025-12-31 +## [1.2.6] - 2026-01-02 ### Fixed - OAR019 - SelectParameterCheck + - OAR020 - ExpandParameterCheck ## [1.2.5] - 2025-12-31 diff --git a/src/main/java/apiaddicts/sonar/openapi/checks/parameters/OAR020ExpandParameterCheck.java b/src/main/java/apiaddicts/sonar/openapi/checks/parameters/OAR020ExpandParameterCheck.java index 03033f0a..355ff4d4 100644 --- a/src/main/java/apiaddicts/sonar/openapi/checks/parameters/OAR020ExpandParameterCheck.java +++ b/src/main/java/apiaddicts/sonar/openapi/checks/parameters/OAR020ExpandParameterCheck.java @@ -14,7 +14,7 @@ public OAR020ExpandParameterCheck() { KEY, MESSAGE, PARAM_NAME, - true + false ); } diff --git a/src/test/java/apiaddicts/sonar/openapi/checks/parameters/OAR020ExpandParameterCheckTest.java b/src/test/java/apiaddicts/sonar/openapi/checks/parameters/OAR020ExpandParameterCheckTest.java index 2f71e70b..6e9c358b 100644 --- a/src/test/java/apiaddicts/sonar/openapi/checks/parameters/OAR020ExpandParameterCheckTest.java +++ b/src/test/java/apiaddicts/sonar/openapi/checks/parameters/OAR020ExpandParameterCheckTest.java @@ -39,6 +39,11 @@ public void verifyInV2WithRef() { verifyV2("with-ref"); } + @Test + public void verifyInV2PathEndingWithParam() { + verifyV3("with-param"); + } + @Test public void verifyInV3() { verifyV3("plain2"); @@ -59,6 +64,11 @@ public void verifyInV3WithRef() { verifyV3("with-ref"); } + @Test + public void verifyInV3PathEndingWithParam() { + verifyV3("with-param"); + } + @Override public void verifyRule() { assertRuleProperties("OAR020 - ExpandParameter - the chosen parameter must be defined in this operation", RuleType.BUG, Severity.MINOR, tags("parameters")); diff --git a/src/test/resources/checks/v2/parameters/OAR020/with-param.json b/src/test/resources/checks/v2/parameters/OAR020/with-param.json new file mode 100644 index 00000000..799521d9 --- /dev/null +++ b/src/test/resources/checks/v2/parameters/OAR020/with-param.json @@ -0,0 +1,27 @@ +{ + "swagger": "2.0", + "info": { + "title": "Swagger Petstore", + "version": "1.0.0" + }, + "paths": { + "/examples/{id}": { + "get": { + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/examples/{id}/items/{id}": { + "get": { + "responses": { + "200": { + "description": "OK" + } + } + } + } + } +} \ No newline at end of file diff --git a/src/test/resources/checks/v2/parameters/OAR020/with-param.yaml b/src/test/resources/checks/v2/parameters/OAR020/with-param.yaml new file mode 100644 index 00000000..3f4a3407 --- /dev/null +++ b/src/test/resources/checks/v2/parameters/OAR020/with-param.yaml @@ -0,0 +1,15 @@ +swagger: "2.0" +info: + title: Swagger Petstore + version: "1.0.0" +paths: + /examples/{id}: + get: + responses: + 200: + description: OK + /examples/{id}/items/{id}: + get: + responses: + 200: + description: OK diff --git a/src/test/resources/checks/v3/parameters/OAR020/with-param.json b/src/test/resources/checks/v3/parameters/OAR020/with-param.json new file mode 100644 index 00000000..1097b436 --- /dev/null +++ b/src/test/resources/checks/v3/parameters/OAR020/with-param.json @@ -0,0 +1,27 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "Swagger Petstore", + "version": "1.0.0" + }, + "paths": { + "/examples/{id}": { + "get": { + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/examples/{id}/items/{id}": { + "get": { + "responses": { + "200": { + "description": "OK" + } + } + } + } + } +} \ No newline at end of file diff --git a/src/test/resources/checks/v3/parameters/OAR020/with-param.yaml b/src/test/resources/checks/v3/parameters/OAR020/with-param.yaml new file mode 100644 index 00000000..8b012753 --- /dev/null +++ b/src/test/resources/checks/v3/parameters/OAR020/with-param.yaml @@ -0,0 +1,15 @@ +openapi: 3.0.0 +info: + title: Swagger Petstore + version: 1.0.0 +paths: + /examples/{id}: + get: + responses: + '200': + description: OK + /examples/{id}/items/{id}: + get: + responses: + '200': + description: OK \ No newline at end of file From fb42e05efcd7d7b1a7c65cb73cbe2669aea4a7bb Mon Sep 17 00:00:00 2001 From: Melsy Huamani Date: Mon, 5 Jan 2026 15:10:52 -0500 Subject: [PATCH 6/7] feat: add search to default excluded path patterns in oar104 --- CHANGELOG.md | 5 ++++- .../OAR104ResourcesByPostVerbCheck.java | 10 +++++----- .../OAR104ResourcesByPostVerbCheckTest.java | 8 ++++---- .../checks/v2/operations/OAR104/plain.yaml | 13 +++++++++---- .../checks/v3/operations/OAR104/plain.yaml | 17 +++++++++++------ 5 files changed, 33 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fd0f39b6..6cd99e92 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [1.2.6] - 2025-12-31 +## [1.2.6] - 2026-01-05 + +### Changed + - OAR104 - ResourcesByPostVerbCheck ### Fixed - OAR019 - SelectParameterCheck diff --git a/src/main/java/apiaddicts/sonar/openapi/checks/operations/OAR104ResourcesByPostVerbCheck.java b/src/main/java/apiaddicts/sonar/openapi/checks/operations/OAR104ResourcesByPostVerbCheck.java index 449f2f7a..8c48753a 100644 --- a/src/main/java/apiaddicts/sonar/openapi/checks/operations/OAR104ResourcesByPostVerbCheck.java +++ b/src/main/java/apiaddicts/sonar/openapi/checks/operations/OAR104ResourcesByPostVerbCheck.java @@ -20,7 +20,7 @@ public class OAR104ResourcesByPostVerbCheck extends BaseCheck { public static final String KEY = "OAR104"; private static final String MESSAGE = "OAR104.error"; - private static final String RESERVED_WORDS = "me"; + private static final String RESERVED_WORDS = "me,search"; @RuleProperty( key = "words-to-exclude", @@ -74,22 +74,22 @@ private boolean isCorrect(String path) { .filter(p -> !p.trim().isEmpty()) .toArray(String[]::new); if (parts.length == 0) return true; - + for (int i = 0; i < parts.length - 1; i++) { if (!isVariable(parts[i]) && !isSpecialVariable(parts[i]) && !isVariable(parts[i + 1]) && !isSpecialVariable(parts[i + 1])) { return false; } } - + return true; } - + private boolean isVariable(String part) { return part.startsWith("{") && part.endsWith("}"); } private boolean isSpecialVariable(String part) { - return "me".equalsIgnoreCase(part); + return reservedWords.contains(part.toLowerCase()); } private String formatMessage(String path) { diff --git a/src/test/java/apiaddicts/sonar/openapi/checks/operations/OAR104ResourcesByPostVerbCheckTest.java b/src/test/java/apiaddicts/sonar/openapi/checks/operations/OAR104ResourcesByPostVerbCheckTest.java index bb1063fd..54c621d7 100644 --- a/src/test/java/apiaddicts/sonar/openapi/checks/operations/OAR104ResourcesByPostVerbCheckTest.java +++ b/src/test/java/apiaddicts/sonar/openapi/checks/operations/OAR104ResourcesByPostVerbCheckTest.java @@ -19,10 +19,10 @@ public void init() { v3Path = getV3Path("operations"); } - /*@Test + @Test public void verifyInV2() { - verifyV2("plain"); - }*/ + verifyV2("plain.yaml"); + } @Test public void verifyInV3() { @@ -37,6 +37,6 @@ public void verifyRule() { @Override public void verifyParameters() { assertNumberOfParameters(1); - assertParameterProperties("words-to-exclude", "me", RuleParamType.STRING); + assertParameterProperties("words-to-exclude", "me,search", RuleParamType.STRING); } } \ No newline at end of file diff --git a/src/test/resources/checks/v2/operations/OAR104/plain.yaml b/src/test/resources/checks/v2/operations/OAR104/plain.yaml index 81f57f1d..24178247 100644 --- a/src/test/resources/checks/v2/operations/OAR104/plain.yaml +++ b/src/test/resources/checks/v2/operations/OAR104/plain.yaml @@ -9,7 +9,7 @@ paths: 200: description: Ok /resources/{r_id}: - post: # Noncompliant {{OAR104: Operation not recommended for resource path: resources/{r_id}}} + post: parameters: - name: r_id in: path @@ -19,12 +19,12 @@ paths: 200: description: Ok /resources/get: - post: + post: # Noncompliant {{OAR104: Operation not recommended for resource path: resources/get}} responses: 200: description: Ok /resources/delete: - post: + post: # Noncompliant {{OAR104: Operation not recommended for resource path: resources/delete}} parameters: - name: delete in: path @@ -44,7 +44,12 @@ paths: 200: description: Ok /resources/me: - post: # Noncompliant {{OAR104: Operation not recommended for resource path: resources/me}} + post: + responses: + 200: + description: Ok + /resources/search: + post: responses: 200: description: Ok \ No newline at end of file diff --git a/src/test/resources/checks/v3/operations/OAR104/plain.yaml b/src/test/resources/checks/v3/operations/OAR104/plain.yaml index 2d5e946b..521a52ca 100644 --- a/src/test/resources/checks/v3/operations/OAR104/plain.yaml +++ b/src/test/resources/checks/v3/operations/OAR104/plain.yaml @@ -9,17 +9,17 @@ paths: '200': description: Ok /resources/me/cars: - post: + post: responses: '200': description: Ok - /resources/get: + /resources/get: post: # Noncompliant {{OAR104: Operation not recommended for resource path: resources/get}} responses: '200': description: Ok - /resources/hola: - post: # Noncompliant {{OAR104: Operation not recommended for resource path: resources/hola}} + /resources/hello: + post: # Noncompliant {{OAR104: Operation not recommended for resource path: resources/hello}} responses: '200': description: Ok @@ -28,8 +28,13 @@ paths: responses: '200': description: Ok - /resources/me: - post: + /resources/me: + post: + responses: + '200': + description: Ok + /resources/search: + post: responses: '200': description: Ok \ No newline at end of file From be6f0991021331ae7aea03015aeee92eb2b0fe31 Mon Sep 17 00:00:00 2001 From: Melsy Huamani Date: Thu, 8 Jan 2026 11:39:07 -0500 Subject: [PATCH 7/7] adding new version 1.3.0 --- CHANGELOG.md | 2 +- pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 940aa601..081a0b9b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [1.2.6] - 2026-01-05 +## [1.3.0] - 2026-01-05 ### Changed - OAR104 - ResourcesByPostVerbCheck diff --git a/pom.xml b/pom.xml index a69ffed0..a89ab7ec 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 org.apiaddicts.apitools.dosonarapi sonaropenapi-rules-community - 1.2.6 + 1.3.0 sonar-plugin SonarQube OpenAPI Community Rules