diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3f50109..d97cfab 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,48 @@ 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.1] - 2026-06-04
+
+### Added
+
+- Add OpenAPI language support without YAML and JSON conflicts.
+
+### Security
+
+- Update `sslr-yaml-parser` to resolve transitive `guava` vulnerabilities.
+
+### Fixed
+
+- Resolve language suffix conflict between the plugin's custom YAML/JSON support and SonarQube's built-in language detection.
+
+## [1.2.1-beta-2] - 2026-05-28
+
+### Added
+
+- Add OpenAPI language support without YAML and JSON conflicts.
+
+## [1.2.1-beta-1] - 2024-05-22
+
+### Security
+
+- Update `sslr-yaml-parser` to resolve transitive `guava` vulnerabilities.
+
+### Fixed
+
+- Resolve language suffix conflict between the plugin's custom YAML/JSON support and SonarQube's built-in language detection.
+
+## [1.2.0] - 2024-05-05
+
+## Added
+
+### Now, support for OpenAPI 3.2 is included. These are some of the new changes:
+
+- Full implementation of the grammar and parser for the new specification.
+- Full coverage for the OpenAPI 3.2 grammar components and API visitors.
+- Completed and adjusted pending validation checks to ensure full compatibility for both OpenAPI 3.1 and 3.2.
+- Refactored error handling and loading states to improve frontend stability.
+
+
## [1.1.2] - 2026-03-06
## Fixed
diff --git a/README.md b/README.md
index fcfc996..852982a 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,5 @@
-# 🛠️ Sonar OpenApi (plugin)    [](https://www.gnu.org/licenses/lgpl-3.0)
+# 🛠️ Sonar OpenApi (plugin)    [](https://www.gnu.org/licenses/lgpl-3.0)
Sonar OpenApi (plugin) is a code analyzer for OpenAPI specifications, is the spiritual successor of [SonarOpenApi](https://github.com/societe-generale/sonar-openapi), carrying on from the point where it left off with support of Apiaddicts community.
@@ -32,7 +32,7 @@ Feel free to drop by and greet us on our GitHub discussion or Discord chat. You
## ⚙️ Features
-* Full compatibility with OpenAPI v2.0, v3.0.0, v3.0.1, v3.0.2, v3.0.3 and v3.1.0
+* Full compatibility with OpenAPI v2.0, v3.0.0, v3.0.1, v3.0.2, v3.0.3, v3.1.0 and v3.2.0

diff --git a/its/pom.xml b/its/pom.xml
index 12c0dd4..49f6740 100644
--- a/its/pom.xml
+++ b/its/pom.xml
@@ -5,7 +5,7 @@
org.apiaddicts.apitools.dosonarapi
dosonarapi
- 1.1.2
+ 1.2.1
../pom.xml
4.0.0
diff --git a/openapi-checks/pom.xml b/openapi-checks/pom.xml
index 19de1cc..4ae3c61 100644
--- a/openapi-checks/pom.xml
+++ b/openapi-checks/pom.xml
@@ -5,7 +5,7 @@
org.apiaddicts.apitools.dosonarapi
dosonarapi
- 1.1.2
+ 1.2.1
../pom.xml
diff --git a/openapi-checks/src/main/java/org/apiaddicts/apitools/dosonarapi/checks/CheckList.java b/openapi-checks/src/main/java/org/apiaddicts/apitools/dosonarapi/checks/CheckList.java
index 83e30d6..f2346ac 100644
--- a/openapi-checks/src/main/java/org/apiaddicts/apitools/dosonarapi/checks/CheckList.java
+++ b/openapi-checks/src/main/java/org/apiaddicts/apitools/dosonarapi/checks/CheckList.java
@@ -23,7 +23,12 @@
import java.util.List;
public final class CheckList {
- public static final String REPOSITORY_KEY = "openapi";
+ public static final String YAML_REPOSITORY_KEY = "openapi-yaml";
+ public static final String JSON_REPOSITORY_KEY = "openapi-json";
+ public static final String OPENAPI_REPOSITORY_KEY = "openapi";
+ public static final String YAML_LANGUAGE = "yaml";
+ public static final String JSON_LANGUAGE = "json";
+ public static final String OPENAPI_LANGUAGE = "openapi";
private CheckList() {
}
diff --git a/openapi-checks/src/main/java/org/apiaddicts/apitools/dosonarapi/checks/ContactValidEmailCheck.java b/openapi-checks/src/main/java/org/apiaddicts/apitools/dosonarapi/checks/ContactValidEmailCheck.java
index 9d17f9f..7b0bec5 100644
--- a/openapi-checks/src/main/java/org/apiaddicts/apitools/dosonarapi/checks/ContactValidEmailCheck.java
+++ b/openapi-checks/src/main/java/org/apiaddicts/apitools/dosonarapi/checks/ContactValidEmailCheck.java
@@ -26,6 +26,8 @@
import org.apiaddicts.apitools.dosonarapi.api.OpenApiCheck;
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.api.v32.OpenApi32Grammar;
import org.apiaddicts.apitools.dosonarapi.sslr.yaml.grammar.JsonNode;
import com.google.common.collect.ImmutableSet;
@@ -39,7 +41,7 @@ public class ContactValidEmailCheck extends OpenApiCheck {
@Override
public Set subscribedKinds() {
- return ImmutableSet.of(OpenApi2Grammar.CONTACT, OpenApi3Grammar.CONTACT);
+ return ImmutableSet.of(OpenApi2Grammar.CONTACT, OpenApi3Grammar.CONTACT, OpenApi31Grammar.CONTACT, OpenApi32Grammar.CONTACT);
}
@Override
diff --git a/openapi-checks/src/main/java/org/apiaddicts/apitools/dosonarapi/checks/DeclaredTagCheck.java b/openapi-checks/src/main/java/org/apiaddicts/apitools/dosonarapi/checks/DeclaredTagCheck.java
index 154f956..13880d1 100644
--- a/openapi-checks/src/main/java/org/apiaddicts/apitools/dosonarapi/checks/DeclaredTagCheck.java
+++ b/openapi-checks/src/main/java/org/apiaddicts/apitools/dosonarapi/checks/DeclaredTagCheck.java
@@ -26,6 +26,8 @@
import org.apiaddicts.apitools.dosonarapi.api.OpenApiCheck;
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.api.v32.OpenApi32Grammar;
import org.apiaddicts.apitools.dosonarapi.sslr.yaml.grammar.JsonNode;
@Rule(key = DeclaredTagCheck.CHECK_KEY)
@@ -34,7 +36,7 @@ public class DeclaredTagCheck extends OpenApiCheck {
@Override
public Set subscribedKinds() {
- return Sets.newHashSet(OpenApi2Grammar.OPERATION, OpenApi3Grammar.OPERATION);
+ return Sets.newHashSet(OpenApi2Grammar.OPERATION, OpenApi3Grammar.OPERATION, OpenApi31Grammar.OPERATION, OpenApi32Grammar.OPERATION);
}
@Override
diff --git a/openapi-checks/src/main/java/org/apiaddicts/apitools/dosonarapi/checks/DefaultResponseCheck.java b/openapi-checks/src/main/java/org/apiaddicts/apitools/dosonarapi/checks/DefaultResponseCheck.java
index caf2a66..16e1b5c 100644
--- a/openapi-checks/src/main/java/org/apiaddicts/apitools/dosonarapi/checks/DefaultResponseCheck.java
+++ b/openapi-checks/src/main/java/org/apiaddicts/apitools/dosonarapi/checks/DefaultResponseCheck.java
@@ -26,6 +26,8 @@
import org.apiaddicts.apitools.dosonarapi.api.OpenApiCheck;
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.api.v32.OpenApi32Grammar;
import org.apiaddicts.apitools.dosonarapi.sslr.yaml.grammar.impl.MissingNode;
import org.apiaddicts.apitools.dosonarapi.sslr.yaml.grammar.JsonNode;
@@ -36,7 +38,7 @@ public class DefaultResponseCheck extends OpenApiCheck {
@Override
public Set subscribedKinds() {
- return Sets.newHashSet(OpenApi2Grammar.RESPONSES, OpenApi3Grammar.RESPONSES);
+ return Sets.newHashSet(OpenApi2Grammar.RESPONSES, OpenApi3Grammar.RESPONSES, OpenApi31Grammar.RESPONSES, OpenApi32Grammar.RESPONSES);
}
@Override
diff --git a/openapi-checks/src/main/java/org/apiaddicts/apitools/dosonarapi/checks/DefinedResponseCheck.java b/openapi-checks/src/main/java/org/apiaddicts/apitools/dosonarapi/checks/DefinedResponseCheck.java
index be2008a..20d39ff 100644
--- a/openapi-checks/src/main/java/org/apiaddicts/apitools/dosonarapi/checks/DefinedResponseCheck.java
+++ b/openapi-checks/src/main/java/org/apiaddicts/apitools/dosonarapi/checks/DefinedResponseCheck.java
@@ -29,6 +29,8 @@
import org.apiaddicts.apitools.dosonarapi.api.OpenApiCheck;
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.api.v32.OpenApi32Grammar;
import org.apiaddicts.apitools.dosonarapi.sslr.yaml.grammar.JsonNode;
@Rule(key = DefinedResponseCheck.CHECK_KEY)
@@ -40,7 +42,7 @@ public class DefinedResponseCheck extends OpenApiCheck {
@Override
public Set subscribedKinds() {
return Sets.newHashSet(
- OpenApi2Grammar.RESPONSES, OpenApi3Grammar.RESPONSES);
+ OpenApi2Grammar.RESPONSES, OpenApi3Grammar.RESPONSES, OpenApi31Grammar.RESPONSES, OpenApi32Grammar.RESPONSES);
}
@Override
diff --git a/openapi-checks/src/main/java/org/apiaddicts/apitools/dosonarapi/checks/DescriptionDiffersSummaryCheck.java b/openapi-checks/src/main/java/org/apiaddicts/apitools/dosonarapi/checks/DescriptionDiffersSummaryCheck.java
index efef053..45fa664 100644
--- a/openapi-checks/src/main/java/org/apiaddicts/apitools/dosonarapi/checks/DescriptionDiffersSummaryCheck.java
+++ b/openapi-checks/src/main/java/org/apiaddicts/apitools/dosonarapi/checks/DescriptionDiffersSummaryCheck.java
@@ -26,6 +26,8 @@
import org.apiaddicts.apitools.dosonarapi.api.OpenApiCheck;
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.api.v32.OpenApi32Grammar;
import org.apiaddicts.apitools.dosonarapi.sslr.yaml.grammar.JsonNode;
@Rule(key = DescriptionDiffersSummaryCheck.CHECK_KEY)
@@ -34,7 +36,7 @@ public class DescriptionDiffersSummaryCheck extends OpenApiCheck {
@Override
public Set subscribedKinds() {
- return Sets.newHashSet(OpenApi2Grammar.OPERATION, OpenApi3Grammar.OPERATION);
+ return Sets.newHashSet(OpenApi2Grammar.OPERATION, OpenApi3Grammar.OPERATION, OpenApi31Grammar.OPERATION, OpenApi32Grammar.OPERATION);
}
@Override
diff --git a/openapi-checks/src/main/java/org/apiaddicts/apitools/dosonarapi/checks/DocumentedTagCheck.java b/openapi-checks/src/main/java/org/apiaddicts/apitools/dosonarapi/checks/DocumentedTagCheck.java
index d78d50b..2534ae8 100644
--- a/openapi-checks/src/main/java/org/apiaddicts/apitools/dosonarapi/checks/DocumentedTagCheck.java
+++ b/openapi-checks/src/main/java/org/apiaddicts/apitools/dosonarapi/checks/DocumentedTagCheck.java
@@ -29,6 +29,8 @@
import org.apiaddicts.apitools.dosonarapi.api.PreciseIssue;
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.api.v32.OpenApi32Grammar;
import org.apiaddicts.apitools.dosonarapi.sslr.yaml.grammar.JsonNode;
import org.apiaddicts.apitools.dosonarapi.sslr.yaml.grammar.impl.MissingNode;
@@ -39,7 +41,7 @@ public class DocumentedTagCheck extends OpenApiCheck {
@Override
public Set subscribedKinds() {
- return Sets.newHashSet(OpenApi2Grammar.TAG, OpenApi2Grammar.OPERATION, OpenApi3Grammar.TAG, OpenApi3Grammar.OPERATION);
+ return Sets.newHashSet(OpenApi2Grammar.TAG, OpenApi2Grammar.OPERATION, OpenApi3Grammar.TAG, OpenApi3Grammar.OPERATION, OpenApi31Grammar.TAG, OpenApi31Grammar.OPERATION, OpenApi32Grammar.TAG, OpenApi32Grammar.OPERATION);
}
@Override
@@ -61,7 +63,7 @@ public void visitFile(JsonNode root) {
@Override
protected void visitNode(JsonNode node) {
AstNodeType nodeType = node.getType();
- if (nodeType == OpenApi2Grammar.TAG || nodeType == OpenApi3Grammar.TAG) {
+ if (nodeType == OpenApi2Grammar.TAG || nodeType == OpenApi3Grammar.TAG || nodeType == OpenApi31Grammar.TAG || nodeType == OpenApi32Grammar.TAG) {
visitTag(node);
} else {
visitOperation(node);
diff --git a/openapi-checks/src/main/java/org/apiaddicts/apitools/dosonarapi/checks/MediaTypeCheck.java b/openapi-checks/src/main/java/org/apiaddicts/apitools/dosonarapi/checks/MediaTypeCheck.java
index d665d70..6329079 100644
--- a/openapi-checks/src/main/java/org/apiaddicts/apitools/dosonarapi/checks/MediaTypeCheck.java
+++ b/openapi-checks/src/main/java/org/apiaddicts/apitools/dosonarapi/checks/MediaTypeCheck.java
@@ -29,6 +29,8 @@
import org.apiaddicts.apitools.dosonarapi.api.OpenApiCheck;
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.api.v32.OpenApi32Grammar;
import org.apiaddicts.apitools.dosonarapi.sslr.yaml.grammar.JsonNode;
@Rule(key = MediaTypeCheck.CHECK_KEY)
@@ -44,7 +46,7 @@ public class MediaTypeCheck extends OpenApiCheck {
@Override
public Set subscribedKinds() {
- return Sets.newHashSet(OpenApi2Grammar.ROOT, OpenApi2Grammar.OPERATION, OpenApi3Grammar.RESPONSE, OpenApi3Grammar.REQUEST_BODY, OpenApi3Grammar.PARAMETER);
+ return Sets.newHashSet(OpenApi2Grammar.ROOT, OpenApi2Grammar.OPERATION, OpenApi3Grammar.RESPONSE, OpenApi3Grammar.REQUEST_BODY, OpenApi3Grammar.PARAMETER, OpenApi31Grammar.RESPONSE, OpenApi31Grammar.REQUEST_BODY, OpenApi31Grammar.PARAMETER, OpenApi32Grammar.RESPONSE, OpenApi32Grammar.REQUEST_BODY, OpenApi32Grammar.PARAMETER);
}
@Override
@@ -70,7 +72,8 @@ private void verifyMimeTypeArray(JsonNode node) {
}
private void visitOpenApi3(JsonNode node) {
- if (node.getType() == OpenApi3Grammar.PARAMETER) {
+ AstNodeType type = node.getType();
+ if (type == OpenApi3Grammar.PARAMETER || type == OpenApi31Grammar.PARAMETER || type == OpenApi32Grammar.PARAMETER) {
verifyParameterContent(node);
} else {
verifyContent(node);
diff --git a/openapi-checks/src/main/java/org/apiaddicts/apitools/dosonarapi/checks/NoContentIn204Check.java b/openapi-checks/src/main/java/org/apiaddicts/apitools/dosonarapi/checks/NoContentIn204Check.java
index cbd7df9..c8059ac 100644
--- a/openapi-checks/src/main/java/org/apiaddicts/apitools/dosonarapi/checks/NoContentIn204Check.java
+++ b/openapi-checks/src/main/java/org/apiaddicts/apitools/dosonarapi/checks/NoContentIn204Check.java
@@ -27,6 +27,8 @@
import org.apiaddicts.apitools.dosonarapi.api.OpenApiCheck;
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.api.v32.OpenApi32Grammar;
import org.apiaddicts.apitools.dosonarapi.sslr.yaml.grammar.JsonNode;
@Rule(key = NoContentIn204Check.CHECK_KEY)
@@ -35,7 +37,7 @@ public class NoContentIn204Check extends OpenApiCheck {
@Override
public Set subscribedKinds() {
- return Sets.newHashSet(OpenApi2Grammar.OPERATION, OpenApi3Grammar.OPERATION);
+ return Sets.newHashSet(OpenApi2Grammar.OPERATION, OpenApi3Grammar.OPERATION, OpenApi31Grammar.OPERATION, OpenApi32Grammar.OPERATION);
}
@Override
@@ -57,7 +59,8 @@ private void checkNoContent(JsonNode response) {
}
private static boolean hasContent(JsonNode effective) {
- return effective.getType() instanceof OpenApi2Grammar && !effective.get("schema").isMissing()
- || effective.getType() instanceof OpenApi3Grammar && ! effective.get("content").isMissing();
+ AstNodeType type = effective.getType();
+ return type instanceof OpenApi2Grammar && !effective.get("schema").isMissing()
+ || (type instanceof OpenApi3Grammar || type instanceof OpenApi31Grammar || type instanceof OpenApi32Grammar) && !effective.get("content").isMissing();
}
}
diff --git a/openapi-checks/src/main/java/org/apiaddicts/apitools/dosonarapi/checks/NoUnusedDefinitionCheck.java b/openapi-checks/src/main/java/org/apiaddicts/apitools/dosonarapi/checks/NoUnusedDefinitionCheck.java
index a948da0..2812e34 100644
--- a/openapi-checks/src/main/java/org/apiaddicts/apitools/dosonarapi/checks/NoUnusedDefinitionCheck.java
+++ b/openapi-checks/src/main/java/org/apiaddicts/apitools/dosonarapi/checks/NoUnusedDefinitionCheck.java
@@ -34,6 +34,8 @@
import org.apiaddicts.apitools.dosonarapi.api.OpenApiCheck;
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.api.v32.OpenApi32Grammar;
import org.apiaddicts.apitools.dosonarapi.sslr.yaml.grammar.JsonNode;
import org.apiaddicts.apitools.dosonarapi.sslr.yaml.grammar.Utils;
import org.apiaddicts.apitools.dosonarapi.sslr.yaml.grammar.impl.MissingNode;
@@ -46,7 +48,7 @@ public class NoUnusedDefinitionCheck extends OpenApiCheck {
@Override
public Set subscribedKinds() {
- return Sets.newHashSet(OpenApi2Grammar.OPERATION, OpenApi3Grammar.OPERATION);
+ return Sets.newHashSet(OpenApi2Grammar.OPERATION, OpenApi3Grammar.OPERATION, OpenApi31Grammar.OPERATION, OpenApi32Grammar.OPERATION);
}
@Override
@@ -165,7 +167,11 @@ private static Set usedReferences(JsonNode node, Function getReference(JsonNode node) {
if (node.isRef()) {
- return Collections.singletonList(JsonPointer.compile(node.at("/$ref").getTokenValue().substring(1)));
+ String refValue = node.at("/$ref").getTokenValue();
+ if (!refValue.startsWith("#")) {
+ return Collections.emptyList();
+ }
+ return Collections.singletonList(JsonPointer.compile(refValue.substring(1)));
} else {
return Collections.emptyList();
}
diff --git a/openapi-checks/src/main/java/org/apiaddicts/apitools/dosonarapi/checks/PathMaskeradingCheck.java b/openapi-checks/src/main/java/org/apiaddicts/apitools/dosonarapi/checks/PathMaskeradingCheck.java
index d400811..0fba6bf 100644
--- a/openapi-checks/src/main/java/org/apiaddicts/apitools/dosonarapi/checks/PathMaskeradingCheck.java
+++ b/openapi-checks/src/main/java/org/apiaddicts/apitools/dosonarapi/checks/PathMaskeradingCheck.java
@@ -32,6 +32,8 @@
import org.apiaddicts.apitools.dosonarapi.api.PreciseIssue;
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.api.v32.OpenApi32Grammar;
import org.apiaddicts.apitools.dosonarapi.sslr.yaml.grammar.JsonNode;
import static org.apiaddicts.apitools.dosonarapi.checks.PathMaskeradingCheck.ConflictMode.AMBIGUOUS;
@@ -78,7 +80,7 @@ private static void ensureSize(List> list, int size) {
@Override
public Set subscribedKinds() {
- return ImmutableSet.of(OpenApi2Grammar.PATHS, OpenApi3Grammar.PATHS);
+ return ImmutableSet.of(OpenApi2Grammar.PATHS, OpenApi3Grammar.PATHS, OpenApi31Grammar.PATHS, OpenApi32Grammar.PATHS);
}
@Override
diff --git a/openapi-checks/src/main/java/org/apiaddicts/apitools/dosonarapi/checks/ProvideOpSummaryCheck.java b/openapi-checks/src/main/java/org/apiaddicts/apitools/dosonarapi/checks/ProvideOpSummaryCheck.java
index c7e382c..60cccd0 100644
--- a/openapi-checks/src/main/java/org/apiaddicts/apitools/dosonarapi/checks/ProvideOpSummaryCheck.java
+++ b/openapi-checks/src/main/java/org/apiaddicts/apitools/dosonarapi/checks/ProvideOpSummaryCheck.java
@@ -26,6 +26,8 @@
import org.apiaddicts.apitools.dosonarapi.api.OpenApiCheck;
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.api.v32.OpenApi32Grammar;
import org.apiaddicts.apitools.dosonarapi.sslr.yaml.grammar.JsonNode;
@Rule(key = ProvideOpSummaryCheck.CHECK_KEY)
@@ -34,7 +36,7 @@ public class ProvideOpSummaryCheck extends OpenApiCheck {
@Override
public Set subscribedKinds() {
- return Sets.newHashSet(OpenApi2Grammar.OPERATION, OpenApi3Grammar.OPERATION);
+ return Sets.newHashSet(OpenApi2Grammar.OPERATION, OpenApi3Grammar.OPERATION, OpenApi31Grammar.OPERATION, OpenApi32Grammar.OPERATION);
}
@Override
diff --git a/openapi-checks/src/test/java/org/apiaddicts/apitools/dosonarapi/checks/CheckListTest.java b/openapi-checks/src/test/java/org/apiaddicts/apitools/dosonarapi/checks/CheckListTest.java
new file mode 100644
index 0000000..8a31509
--- /dev/null
+++ b/openapi-checks/src/test/java/org/apiaddicts/apitools/dosonarapi/checks/CheckListTest.java
@@ -0,0 +1,56 @@
+/*
+ * doSonarAPI: SonarQube OpenAPI Plugin
+ * Copyright (C) 2021-2022 Apiaddicts
+ * contacta AT apiaddicts DOT org
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.apiaddicts.apitools.dosonarapi.checks;
+
+import java.util.List;
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class CheckListTest {
+
+ @Test
+ public void returns_all_check_classes() {
+ List> checks = CheckList.getChecks();
+ assertThat(checks).isNotEmpty().contains(
+ PathMaskeradingCheck.class,
+ MediaTypeCheck.class,
+ ParsingErrorCheck.class,
+ DefaultResponseCheck.class,
+ DefinedResponseCheck.class,
+ DeclaredTagCheck.class,
+ DocumentedTagCheck.class,
+ AtMostOneBodyParameterCheck.class,
+ NoUnusedDefinitionCheck.class,
+ NoContentIn204Check.class,
+ ProvideOpSummaryCheck.class,
+ ContactValidEmailCheck.class,
+ DescriptionDiffersSummaryCheck.class
+ );
+ }
+
+ @Test
+ public void constants_have_expected_values() {
+ assertThat(CheckList.YAML_REPOSITORY_KEY).isEqualTo("openapi-yaml");
+ assertThat(CheckList.JSON_REPOSITORY_KEY).isEqualTo("openapi-json");
+ assertThat(CheckList.YAML_LANGUAGE).isEqualTo("yaml");
+ assertThat(CheckList.JSON_LANGUAGE).isEqualTo("json");
+ }
+}
diff --git a/openapi-checks/src/test/java/org/apiaddicts/apitools/dosonarapi/checks/DocumentedTagCheckTest.java b/openapi-checks/src/test/java/org/apiaddicts/apitools/dosonarapi/checks/DocumentedTagCheckTest.java
index acbb4ae..80fc0b5 100644
--- a/openapi-checks/src/test/java/org/apiaddicts/apitools/dosonarapi/checks/DocumentedTagCheckTest.java
+++ b/openapi-checks/src/test/java/org/apiaddicts/apitools/dosonarapi/checks/DocumentedTagCheckTest.java
@@ -24,12 +24,12 @@
public class DocumentedTagCheckTest {
@Test
- public void verify_media_type_in_v2() {
- OpenApiCheckVerifier.verify("src/test/resources/checks/v2/declared-tag.yaml", new DeclaredTagCheck(), true, false, false);
+ public void verify_documented_tag_in_v2() {
+ OpenApiCheckVerifier.verify("src/test/resources/checks/v2/documented-tag.yaml", new DocumentedTagCheck(), true, false, false);
}
@Test
- public void verify_media_type_in_v3() {
- OpenApiCheckVerifier.verify("src/test/resources/checks/v3/declared-tag.yaml", new DeclaredTagCheck(), false, true, false);
+ public void verify_documented_tag_in_v3() {
+ OpenApiCheckVerifier.verify("src/test/resources/checks/v3/documented-tag.yaml", new DocumentedTagCheck(), false, true, false);
}
}
diff --git a/openapi-checks/src/test/java/org/apiaddicts/apitools/dosonarapi/checks/ParsingErrorCheckTest.java b/openapi-checks/src/test/java/org/apiaddicts/apitools/dosonarapi/checks/ParsingErrorCheckTest.java
index 0aa785d..7e07428 100644
--- a/openapi-checks/src/test/java/org/apiaddicts/apitools/dosonarapi/checks/ParsingErrorCheckTest.java
+++ b/openapi-checks/src/test/java/org/apiaddicts/apitools/dosonarapi/checks/ParsingErrorCheckTest.java
@@ -20,11 +20,16 @@
package org.apiaddicts.apitools.dosonarapi.checks;
import com.sonar.sslr.api.RecognitionException;
+import java.nio.charset.StandardCharsets;
import java.util.List;
import org.junit.Test;
import org.apiaddicts.apitools.dosonarapi.api.OpenApiFile;
import org.apiaddicts.apitools.dosonarapi.api.OpenApiVisitorContext;
import org.apiaddicts.apitools.dosonarapi.api.PreciseIssue;
+import org.apiaddicts.apitools.dosonarapi.openapi.OpenApiConfiguration;
+import org.apiaddicts.apitools.dosonarapi.openapi.parser.OpenApiParser;
+import org.apiaddicts.apitools.dosonarapi.sslr.yaml.grammar.ValidationException;
+import org.apiaddicts.apitools.dosonarapi.sslr.yaml.grammar.YamlParser;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.tuple;
@@ -43,6 +48,33 @@ public void reports_parsing_errors() {
.contains(tuple(3, "Parsing exception message"));
}
+ @Test
+ public void reports_validation_exception_causes_as_issues() {
+ String invalidYaml =
+ "openapi: \"3.0.0\"\n" +
+ "info:\n" +
+ " title: Test API\n" +
+ "paths:\n" +
+ " /pets:\n" +
+ " post:\n" +
+ " description: missing responses\n";
+
+ OpenApiConfiguration config = new OpenApiConfiguration(StandardCharsets.UTF_8, true);
+ YamlParser parser = OpenApiParser.createV3(config);
+ OpenApiVisitorContext context;
+ try {
+ parser.parse(invalidYaml);
+ context = new OpenApiVisitorContext(new TestFile(), (RecognitionException) null);
+ } catch (ValidationException e) {
+ context = new OpenApiVisitorContext(new TestFile(), e);
+ }
+
+ ParsingErrorCheck check = new ParsingErrorCheck();
+ List issues = check.scanFileForIssues(context);
+
+ assertThat(issues).isNotEmpty();
+ }
+
private static class TestFile implements OpenApiFile {
@Override
diff --git a/openapi-front-end/pom.xml b/openapi-front-end/pom.xml
index a9e84d9..9c28b76 100644
--- a/openapi-front-end/pom.xml
+++ b/openapi-front-end/pom.xml
@@ -5,7 +5,7 @@
org.apiaddicts.apitools.dosonarapi
dosonarapi
- 1.1.2
+ 1.2.1
../pom.xml
diff --git a/openapi-front-end/src/main/java/org/apiaddicts/apitools/dosonarapi/api/OpenApiGrammar.java b/openapi-front-end/src/main/java/org/apiaddicts/apitools/dosonarapi/api/OpenApiGrammar.java
new file mode 100644
index 0000000..fd50b07
--- /dev/null
+++ b/openapi-front-end/src/main/java/org/apiaddicts/apitools/dosonarapi/api/OpenApiGrammar.java
@@ -0,0 +1,340 @@
+/*
+ * doSonarAPI: SonarQube OpenAPI Plugin
+ * Copyright (C) 2021-2022 Apiaddicts
+ * contacta AT apiaddicts DOT org
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.apiaddicts.apitools.dosonarapi.api;
+
+import org.sonar.sslr.grammar.GrammarRuleKey;
+import org.apiaddicts.apitools.dosonarapi.sslr.yaml.grammar.YamlGrammarBuilder;
+
+public final class OpenApiGrammar {
+
+ static final String EXTENSION_PATTERN = "^x-.*";
+
+ private static final String PROP_DESCRIPTION = "description";
+ private static final String PROP_SUMMARY = "summary";
+ private static final String PROP_REQUIRED = "required";
+ private static final String PROP_DEPRECATED = "deprecated";
+ private static final String PROP_PARAMETERS = "parameters";
+ private static final String PROP_CONTENT = "content";
+ private static final String PROP_SCHEMA = "schema";
+ private static final String PROP_EXAMPLE = "example";
+ private static final String PROP_EXAMPLES = "examples";
+ private static final String PROP_STYLE = "style";
+ private static final String STYLE_SIMPLE = "simple";
+ private static final String PROP_EXPLODE = "explode";
+ private static final String PROP_ALLOW_RESERVED = "allowReserved";
+ private static final String PROP_REFRESH_URL = "refreshUrl";
+ private static final String PROP_SCOPES = "scopes";
+ private static final String PROP_TOKEN_URL = "tokenUrl";
+
+ private OpenApiGrammar() {}
+
+ public static void buildCommonSecuritySchemes(YamlGrammarBuilder b,
+ GrammarRuleKey httpScheme, GrammarRuleKey apiKeyScheme,
+ GrammarRuleKey oauth2Scheme, GrammarRuleKey openIdScheme,
+ GrammarRuleKey flows, GrammarRuleKey description) {
+ b.rule(httpScheme).is(b.object(
+ b.discriminant("type", "http"),
+ b.property(PROP_DESCRIPTION, description),
+ b.mandatoryProperty("scheme", b.string()),
+ b.property("bearerFormat", b.string()),
+ b.patternProperty(EXTENSION_PATTERN, b.anything()))).skip();
+ b.rule(apiKeyScheme).is(b.object(
+ b.discriminant("type", "apiKey"),
+ b.property(PROP_DESCRIPTION, description),
+ b.mandatoryProperty("name", b.string()),
+ b.mandatoryProperty("in", b.firstOf("query", "header", "cookie")),
+ b.patternProperty(EXTENSION_PATTERN, b.anything()))).skip();
+ b.rule(oauth2Scheme).is(b.object(
+ b.discriminant("type", "oauth2"),
+ b.property(PROP_DESCRIPTION, description),
+ b.mandatoryProperty("flows", flows),
+ b.patternProperty(EXTENSION_PATTERN, b.anything()))).skip();
+ b.rule(openIdScheme).is(b.object(
+ b.discriminant("type", "openIdConnect"),
+ b.property(PROP_DESCRIPTION, description),
+ b.mandatoryProperty("openIdConnectUrl", b.string()),
+ b.patternProperty(EXTENSION_PATTERN, b.anything()))).skip();
+ }
+
+ public static void buildSecurityFlows(YamlGrammarBuilder b,
+ GrammarRuleKey flows, GrammarRuleKey implicitFlow, GrammarRuleKey passwordFlow,
+ GrammarRuleKey credentialsFlow, GrammarRuleKey authFlow, GrammarRuleKey securityRequirement) {
+ b.rule(flows).is(b.object(
+ b.property("implicit", implicitFlow),
+ b.property("password", passwordFlow),
+ b.property("clientCredentials", credentialsFlow),
+ b.property("authorizationCode", authFlow),
+ b.patternProperty(EXTENSION_PATTERN, b.anything())));
+ b.rule(implicitFlow).is(b.object(
+ b.mandatoryProperty("authorizationUrl", b.string()),
+ b.property(PROP_REFRESH_URL, b.string()),
+ b.property(PROP_SCOPES, b.object(b.patternProperty(".*", b.string()))),
+ b.patternProperty(EXTENSION_PATTERN, b.anything())));
+ b.rule(passwordFlow).is(b.object(
+ b.mandatoryProperty(PROP_TOKEN_URL, b.string()),
+ b.property(PROP_REFRESH_URL, b.string()),
+ b.property(PROP_SCOPES, b.object(b.patternProperty(".*", b.string()))),
+ b.patternProperty(EXTENSION_PATTERN, b.anything())));
+ b.rule(credentialsFlow).is(b.object(
+ b.mandatoryProperty(PROP_TOKEN_URL, b.string()),
+ b.property(PROP_REFRESH_URL, b.string()),
+ b.property(PROP_SCOPES, b.object(b.patternProperty(".*", b.string()))),
+ b.patternProperty(EXTENSION_PATTERN, b.anything())));
+ b.rule(authFlow).is(b.object(
+ b.mandatoryProperty("authorizationUrl", b.string()),
+ b.mandatoryProperty(PROP_TOKEN_URL, b.string()),
+ b.property(PROP_REFRESH_URL, b.string()),
+ b.property(PROP_SCOPES, b.object(b.patternProperty(".*", b.string()))),
+ b.patternProperty(EXTENSION_PATTERN, b.anything())));
+ b.rule(securityRequirement).is(b.object(
+ b.patternProperty(".*", b.array(b.string()))));
+ }
+
+ public static void buildCallbacks(YamlGrammarBuilder b,
+ GrammarRuleKey callback, GrammarRuleKey link,
+ GrammarRuleKey path, GrammarRuleKey server, GrammarRuleKey description) {
+ b.rule(callback).is(b.object(
+ b.patternProperty("^[^x].*", path),
+ b.patternProperty(EXTENSION_PATTERN, b.anything())));
+ b.rule(link).is(b.object(
+ b.property("operationRef", b.string()),
+ b.property("operationId", b.string()),
+ b.property(PROP_PARAMETERS, b.object(b.patternProperty(".*", b.anything()))),
+ b.property("requestBody", b.anything()),
+ b.property(PROP_DESCRIPTION, description),
+ b.property("server", server),
+ b.patternProperty(EXTENSION_PATTERN, b.anything())));
+ }
+
+ @SuppressWarnings("java:S107")
+ public static void buildResponsesAndHeader(YamlGrammarBuilder b,
+ GrammarRuleKey responses, GrammarRuleKey response, GrammarRuleKey ref,
+ GrammarRuleKey header, GrammarRuleKey schema, GrammarRuleKey example,
+ GrammarRuleKey mediaType, GrammarRuleKey link, GrammarRuleKey description) {
+ b.rule(responses).is(b.object(
+ b.property("default", b.firstOf(response, ref)),
+ b.patternProperty("^[0-9xX]+", b.firstOf(response, ref)),
+ b.patternProperty(EXTENSION_PATTERN, b.anything())));
+ b.rule(response).is(b.object(
+ b.mandatoryProperty(PROP_DESCRIPTION, description),
+ b.property("headers", b.object(b.patternProperty(".*", b.firstOf(ref, header)))),
+ b.property(PROP_CONTENT, b.object(b.patternProperty(".*", mediaType))),
+ b.property("links", b.object(b.patternProperty(".*", b.firstOf(ref, link)))),
+ b.patternProperty(EXTENSION_PATTERN, b.anything())));
+ buildHeader(b, header, ref, schema, example, mediaType, description);
+ }
+
+ public static void buildHeader(YamlGrammarBuilder b,
+ GrammarRuleKey header, GrammarRuleKey ref, GrammarRuleKey schema,
+ GrammarRuleKey example, GrammarRuleKey mediaType, GrammarRuleKey description) {
+ b.rule(header).is(b.object(
+ b.property(PROP_DESCRIPTION, description),
+ b.property(PROP_REQUIRED, b.bool()),
+ b.property(PROP_DEPRECATED, b.bool()),
+ b.property("allowEmptyValue", b.bool()),
+ b.property(PROP_STYLE, STYLE_SIMPLE),
+ b.property(PROP_EXPLODE, b.bool()),
+ b.property(PROP_ALLOW_RESERVED, b.bool()),
+ b.property(PROP_SCHEMA, b.firstOf(ref, schema)),
+ b.property(PROP_EXAMPLE, b.anything()),
+ b.property(PROP_EXAMPLES, b.object(b.patternProperty(".*", b.firstOf(ref, example)))),
+ b.property(PROP_CONTENT, b.object(b.patternProperty(".*", mediaType))),
+ b.patternProperty(EXTENSION_PATTERN, b.anything())));
+ }
+
+ @SuppressWarnings("java:S107")
+ public static void buildBaseComponentRules(YamlGrammarBuilder b,
+ GrammarRuleKey schemasComponent, GrammarRuleKey responsesComponent,
+ GrammarRuleKey parametersComponent, GrammarRuleKey examplesComponent,
+ GrammarRuleKey bodiesComponent, GrammarRuleKey headersComponent,
+ GrammarRuleKey securitySchemes, GrammarRuleKey linksComponent,
+ GrammarRuleKey callbacksComponent,
+ GrammarRuleKey ref, GrammarRuleKey schema, GrammarRuleKey response,
+ GrammarRuleKey parameter, GrammarRuleKey example, GrammarRuleKey requestBody,
+ GrammarRuleKey header, GrammarRuleKey securityScheme, GrammarRuleKey link,
+ GrammarRuleKey callback) {
+ b.rule(schemasComponent).is(b.object(b.patternProperty(".*", b.firstOf(ref, schema))));
+ b.rule(responsesComponent).is(b.object(b.patternProperty(".*", b.firstOf(ref, response))));
+ b.rule(parametersComponent).is(b.object(b.patternProperty(".*", b.firstOf(ref, parameter))));
+ b.rule(examplesComponent).is(b.object(b.patternProperty(".*", b.firstOf(ref, example))));
+ b.rule(bodiesComponent).is(b.object(b.patternProperty(".*", b.firstOf(ref, requestBody))));
+ b.rule(headersComponent).is(b.object(b.patternProperty(".*", b.firstOf(ref, header))));
+ b.rule(securitySchemes).is(b.object(b.patternProperty(".*", b.firstOf(ref, securityScheme))));
+ b.rule(linksComponent).is(b.object(b.patternProperty(".*", b.firstOf(ref, link))));
+ b.rule(callbacksComponent).is(b.object(b.patternProperty(".*", b.firstOf(ref, callback))));
+ }
+
+ @SuppressWarnings("java:S107")
+ public static void buildOperation(YamlGrammarBuilder b,
+ GrammarRuleKey operation, GrammarRuleKey ref, GrammarRuleKey parameter,
+ GrammarRuleKey requestBody, GrammarRuleKey responses,
+ GrammarRuleKey callback, GrammarRuleKey externalDoc,
+ GrammarRuleKey securityRequirement, GrammarRuleKey server,
+ GrammarRuleKey description) {
+ b.rule(operation).is(b.object(
+ b.property("tags", b.array(b.string())),
+ b.property(PROP_SUMMARY, b.string()),
+ b.property(PROP_DESCRIPTION, description),
+ b.property("externalDocs", externalDoc),
+ b.property("operationId", b.string()),
+ b.property(PROP_PARAMETERS, b.array(b.firstOf(ref, parameter))),
+ b.property("requestBody", b.firstOf(ref, requestBody)),
+ b.mandatoryProperty("responses", responses),
+ b.property("callbacks", b.object(
+ b.patternProperty(".*", b.firstOf(ref, callback)))),
+ b.property(PROP_DEPRECATED, b.bool()),
+ b.property("security", b.array(securityRequirement)),
+ b.property("servers", b.array(server)),
+ b.patternProperty(EXTENSION_PATTERN, b.anything())));
+ }
+
+ public static void buildParameter(YamlGrammarBuilder b,
+ GrammarRuleKey parameter, GrammarRuleKey ref, GrammarRuleKey schema,
+ GrammarRuleKey example, GrammarRuleKey mediaType, GrammarRuleKey description) {
+ b.rule(parameter).is(b.object(
+ b.mandatoryProperty("name", b.string()),
+ b.mandatoryProperty("in", b.firstOf("path", "query", "header", "cookie")),
+ b.property(PROP_DESCRIPTION, description),
+ b.property(PROP_REQUIRED, b.bool()),
+ b.property(PROP_DEPRECATED, b.bool()),
+ b.property("allowEmptyValue", b.bool()),
+ b.property(PROP_STYLE, b.firstOf("matrix", "label", "form", STYLE_SIMPLE, "spaceDelimited", "pipeDelimited", "deepObject")),
+ b.property(PROP_EXPLODE, b.bool()),
+ b.property(PROP_ALLOW_RESERVED, b.bool()),
+ b.property(PROP_SCHEMA, b.firstOf(ref, schema)),
+ b.property(PROP_EXAMPLE, b.anything()),
+ b.property(PROP_EXAMPLES, b.object(
+ b.patternProperty(".*", b.firstOf(ref, example)))),
+ b.property(PROP_CONTENT, b.object(
+ b.patternProperty(".*", mediaType))),
+ b.patternProperty(EXTENSION_PATTERN, b.anything())));
+ }
+
+ public static void buildRequestBody(YamlGrammarBuilder b,
+ GrammarRuleKey requestBody, GrammarRuleKey ref, GrammarRuleKey mediaType,
+ GrammarRuleKey description) {
+ b.rule(requestBody).is(b.object(
+ b.property(PROP_DESCRIPTION, description),
+ b.property(PROP_REQUIRED, b.bool()),
+ b.property(PROP_CONTENT, b.object(
+ b.patternProperty(".*", b.firstOf(ref, mediaType)))),
+ b.patternProperty(EXTENSION_PATTERN, b.anything())));
+ }
+
+ public static void buildMediaType(YamlGrammarBuilder b,
+ GrammarRuleKey mediaType, GrammarRuleKey ref, GrammarRuleKey schema,
+ GrammarRuleKey example, GrammarRuleKey encoding) {
+ b.rule(mediaType).is(b.object(
+ b.property(PROP_SCHEMA, b.firstOf(ref, schema)),
+ b.property(PROP_EXAMPLE, b.anything()),
+ b.property(PROP_EXAMPLES, b.object(
+ b.patternProperty(".*", b.firstOf(ref, example)))),
+ b.property("encoding", b.object(
+ b.patternProperty(".*", encoding))),
+ b.patternProperty(EXTENSION_PATTERN, b.anything())));
+ }
+
+ public static void buildEncoding(YamlGrammarBuilder b,
+ GrammarRuleKey encoding, GrammarRuleKey ref, GrammarRuleKey header) {
+ b.rule(encoding).is(b.object(
+ b.property("contentType", b.string()),
+ b.property("headers", b.object(
+ b.patternProperty(".*", b.firstOf(ref, header)))),
+ b.property(PROP_STYLE, b.firstOf("matrix", "label", "form", STYLE_SIMPLE, "spaceDelimited", "pipeDelimited", "deepObject")),
+ b.property(PROP_EXPLODE, b.bool()),
+ b.property(PROP_ALLOW_RESERVED, b.bool()),
+ b.patternProperty(EXTENSION_PATTERN, b.anything())));
+ }
+
+ public static void buildPathRule(YamlGrammarBuilder b,
+ GrammarRuleKey path, GrammarRuleKey operation, GrammarRuleKey ref,
+ GrammarRuleKey parameter, GrammarRuleKey server, GrammarRuleKey description) {
+ b.rule(path).is(b.object(
+ b.property("$ref", b.string()),
+ b.property(PROP_SUMMARY, b.string()),
+ b.property(PROP_DESCRIPTION, description),
+ b.property("get", operation),
+ b.property("put", operation),
+ b.property("post", operation),
+ b.property("delete", operation),
+ b.property("options", operation),
+ b.property("head", operation),
+ b.property("patch", operation),
+ b.property("trace", operation),
+ b.property("servers", b.array(server)),
+ b.property(PROP_PARAMETERS, b.array(b.firstOf(ref, parameter))),
+ b.patternProperty(EXTENSION_PATTERN, b.anything())));
+ }
+
+ public static void buildServerAndVariable(YamlGrammarBuilder b,
+ GrammarRuleKey server, GrammarRuleKey serverVariable, GrammarRuleKey description) {
+ b.rule(server).is(b.object(
+ b.mandatoryProperty("url", b.string()),
+ b.property(PROP_DESCRIPTION, description),
+ b.property("variables", b.object(
+ b.patternProperty(".*", serverVariable))),
+ b.patternProperty(EXTENSION_PATTERN, b.anything())));
+ b.rule(serverVariable).is(b.object(
+ b.property("enum", b.array(b.string())),
+ b.mandatoryProperty("default", b.string()),
+ b.property(PROP_DESCRIPTION, description),
+ b.patternProperty(EXTENSION_PATTERN, b.anything())));
+ }
+
+ public static void buildTags(YamlGrammarBuilder b,
+ GrammarRuleKey tag, GrammarRuleKey description, GrammarRuleKey externalDoc) {
+ b.rule(tag).is(b.object(
+ b.mandatoryProperty("name", b.string()),
+ b.property(PROP_DESCRIPTION, description),
+ b.property("externalDocs", externalDoc),
+ b.patternProperty(EXTENSION_PATTERN, b.anything())));
+ }
+
+ @SuppressWarnings("java:S107")
+ public static void buildMutualTlsSecuritySetup(YamlGrammarBuilder b,
+ GrammarRuleKey securityScheme, GrammarRuleKey httpScheme, GrammarRuleKey apiKeyScheme,
+ GrammarRuleKey oauth2Scheme, GrammarRuleKey openIdScheme, GrammarRuleKey mutualTlsScheme,
+ GrammarRuleKey flows, GrammarRuleKey description) {
+ b.rule(securityScheme).is(
+ b.firstOf(httpScheme, apiKeyScheme, oauth2Scheme, openIdScheme, mutualTlsScheme));
+ buildCommonSecuritySchemes(b, httpScheme, apiKeyScheme, oauth2Scheme, openIdScheme, flows, description);
+ }
+
+ public static void buildStandardExample(YamlGrammarBuilder b,
+ GrammarRuleKey example, GrammarRuleKey description) {
+ b.rule(example).is(b.object(
+ b.property(PROP_SUMMARY, b.string()),
+ b.property(PROP_DESCRIPTION, description),
+ b.property("value", b.anything()),
+ b.property("externalValue", b.string()),
+ b.patternProperty(EXTENSION_PATTERN, b.anything())));
+ }
+
+ @SuppressWarnings("java:S107")
+ public static void buildStandardParameters(YamlGrammarBuilder b,
+ GrammarRuleKey parameter, GrammarRuleKey requestBody, GrammarRuleKey mediaType,
+ GrammarRuleKey encoding, GrammarRuleKey ref, GrammarRuleKey schema,
+ GrammarRuleKey example, GrammarRuleKey header, GrammarRuleKey description) {
+ buildParameter(b, parameter, ref, schema, example, mediaType, description);
+ buildRequestBody(b, requestBody, ref, mediaType, description);
+ buildMediaType(b, mediaType, ref, schema, example, encoding);
+ buildEncoding(b, encoding, ref, header);
+ }
+}
diff --git a/openapi-front-end/src/main/java/org/apiaddicts/apitools/dosonarapi/api/TestOpenApiVisitorRunner.java b/openapi-front-end/src/main/java/org/apiaddicts/apitools/dosonarapi/api/TestOpenApiVisitorRunner.java
index f03d31f..a2796fc 100644
--- a/openapi-front-end/src/main/java/org/apiaddicts/apitools/dosonarapi/api/TestOpenApiVisitorRunner.java
+++ b/openapi-front-end/src/main/java/org/apiaddicts/apitools/dosonarapi/api/TestOpenApiVisitorRunner.java
@@ -44,25 +44,36 @@ public static void scanFile(File file, OpenApiVisitor... visitors) {
}
public static void scanFileForComments(File file, boolean isV2, boolean isV3, boolean isV31, OpenApiVisitor... visitors) {
- OpenApiVisitorContext context = createContext(file, isV2, isV3, isV31);
+ scanFileForComments(file, isV2, isV3, isV31, false, visitors);
+ }
+
+ public static void scanFileForComments(File file, boolean isV2, boolean isV3, boolean isV31, boolean isV32, OpenApiVisitor... visitors) {
+ OpenApiVisitorContext context = createContext(file, isV2, isV3, isV31, isV32);
for (OpenApiVisitor visitor : visitors) {
visitor.scanFile(context);
}
}
public static OpenApiVisitorContext createContext(File file) {
- return createContext(file, false, false, false);
+ return createContext(file, false, false, false, false);
}
public static OpenApiVisitorContext createContext(File file, boolean isV2) {
- return createContext(file, isV2, false, false);
+ return createContext(file, isV2, false, false, false);
}
public static OpenApiVisitorContext createContext(File file, boolean isV2, boolean isV3, boolean isV31) {
+ return createContext(file, isV2, isV3, isV31, false);
+ }
+
+ @SuppressWarnings("java:S1172")
+ public static OpenApiVisitorContext createContext(File file, boolean isV2, boolean isV3, boolean isV31, boolean isV32) {
OpenApiConfiguration configuration = new OpenApiConfiguration(StandardCharsets.UTF_8, true);
YamlParser parser;
if (isV2) {
parser = OpenApiParser.createV2(configuration);
+ } else if (isV32) {
+ parser = OpenApiParser.createV32(configuration);
} else if (isV31) {
parser = OpenApiParser.createV31(configuration);
} else {
@@ -86,7 +97,6 @@ public static OpenApiVisitorContext createContext(File file, YamlParser parser)
* This method is required to avoid a parsing issue with yaml,
* sometimes, when an empty line is followed by a comment, it breaks the parser
*
- * FIXME: Try to solve in the yaml parser lib
*/
private static String getContent(File file) throws IOException {
String[] lines = new String(Files.readAllBytes(Paths.get(file.getPath()))).split("\n");
diff --git a/openapi-front-end/src/main/java/org/apiaddicts/apitools/dosonarapi/api/v3/OpenApi3Grammar.java b/openapi-front-end/src/main/java/org/apiaddicts/apitools/dosonarapi/api/v3/OpenApi3Grammar.java
index d70c90b..d275bc8 100644
--- a/openapi-front-end/src/main/java/org/apiaddicts/apitools/dosonarapi/api/v3/OpenApi3Grammar.java
+++ b/openapi-front-end/src/main/java/org/apiaddicts/apitools/dosonarapi/api/v3/OpenApi3Grammar.java
@@ -20,6 +20,7 @@
package org.apiaddicts.apitools.dosonarapi.api.v3;
import org.sonar.sslr.grammar.GrammarRuleKey;
+import org.apiaddicts.apitools.dosonarapi.api.OpenApiGrammar;
import org.apiaddicts.apitools.dosonarapi.sslr.yaml.grammar.YamlGrammarBuilder;
@java.lang.SuppressWarnings("squid:S1192") // Voluntarily ignoring string constants redefinitions in this file
@@ -110,170 +111,28 @@ public static YamlGrammarBuilder create() {
}
private static void buildTags(YamlGrammarBuilder b) {
- b.rule(TAG).is(b.object(
- b.mandatoryProperty("name", b.string()),
- b.property("description", DESCRIPTION),
- b.property("externalDocs", EXTERNAL_DOC),
- b.patternProperty(EXTENSION_PATTERN, b.anything())));
+ OpenApiGrammar.buildTags(b, TAG, DESCRIPTION, EXTERNAL_DOC);
}
private static void buildSecurityDefinitions(YamlGrammarBuilder b) {
b.rule(SECURITY_SCHEME).is(
b.firstOf(HTTP_SECURITY_SCHEME, API_KEY_SECURITY_SCHEME, OAUTH2_SECURITY_SCHEME, OPENID_SECURITY_SCHEME));
- b.rule(HTTP_SECURITY_SCHEME).is(b.object(
- b.discriminant("type", "http"),
- b.property("description", DESCRIPTION),
- b.mandatoryProperty("scheme", b.string()),
- b.property("bearerFormat", b.string()),
- b.patternProperty(EXTENSION_PATTERN, b.anything()))).skip();
- b.rule(API_KEY_SECURITY_SCHEME).is(b.object(
- b.discriminant("type", "apiKey"),
- b.property("description", DESCRIPTION),
- b.mandatoryProperty("name", b.string()),
- b.mandatoryProperty("in", b.firstOf("query", "header", "cookie")),
- b.patternProperty(EXTENSION_PATTERN, b.anything()))).skip();
- b.rule(OAUTH2_SECURITY_SCHEME).is(b.object(
- b.discriminant("type", "oauth2"),
- b.property("description", DESCRIPTION),
- b.mandatoryProperty("flows", FLOWS),
- b.patternProperty(EXTENSION_PATTERN, b.anything()))).skip();
- b.rule(OPENID_SECURITY_SCHEME).is(b.object(
- b.discriminant("type", "openIdConnect"),
- b.property("description", DESCRIPTION),
- b.mandatoryProperty("openIdConnectUrl", b.string()),
- b.patternProperty(EXTENSION_PATTERN, b.anything()))).skip();
- b.rule(FLOWS).is(b.object(
- b.property("implicit", IMPLICIT_FLOW),
- b.property("password", PASSWORD_FLOW),
- b.property("clientCredentials", CREDENTIALS_FLOW),
- b.property("authorizationCode", AUTH_FLOW),
- b.patternProperty(EXTENSION_PATTERN, b.anything())));
- b.rule(IMPLICIT_FLOW).is(b.object(
- b.mandatoryProperty("authorizationUrl", b.string()),
- b.property("refreshUrl", b.string()),
- b.property("scopes", b.object(
- b.patternProperty(".*", b.string()))),
- b.patternProperty(EXTENSION_PATTERN, b.anything())));
- b.rule(PASSWORD_FLOW).is(b.object(
- b.mandatoryProperty("tokenUrl", b.string()),
- b.property("refreshUrl", b.string()),
- b.property("scopes", b.object(
- b.patternProperty(".*", b.string()))),
- b.patternProperty(EXTENSION_PATTERN, b.anything())));
- b.rule(CREDENTIALS_FLOW).is(b.object(
- b.mandatoryProperty("tokenUrl", b.string()),
- b.property("refreshUrl", b.string()),
- b.property("scopes", b.object(
- b.patternProperty(".*", b.string()))),
- b.patternProperty(EXTENSION_PATTERN, b.anything())));
- b.rule(AUTH_FLOW).is(b.object(
- b.mandatoryProperty("authorizationUrl", b.string()),
- b.mandatoryProperty("tokenUrl", b.string()),
- b.property("refreshUrl", b.string()),
- b.property("scopes", b.object(
- b.patternProperty(".*", b.string()))),
- b.patternProperty(EXTENSION_PATTERN, b.anything())));
- b.rule(SECURITY_REQUIREMENT).is(b.object(
- b.patternProperty(".*", b.array(b.string()))));
- }
-
- private static void buildCallbacks(YamlGrammarBuilder b) {
- b.rule(CALLBACK).is(b.object(
- b.patternProperty("^[^x].*", PATH),
- b.patternProperty(EXTENSION_PATTERN, b.anything())));
- b.rule(LINK).is(b.object(
- b.property("operationRef", b.string()),
- b.property("operationId", b.string()),
- b.property("parameters", b.object(
- b.patternProperty(".*", b.anything()))),
- b.property("requestBody", b.anything()),
- b.property("description", DESCRIPTION),
- b.property("server", SERVER),
- b.patternProperty(EXTENSION_PATTERN, b.anything())));
+ OpenApiGrammar.buildCommonSecuritySchemes(b,
+ HTTP_SECURITY_SCHEME, API_KEY_SECURITY_SCHEME, OAUTH2_SECURITY_SCHEME, OPENID_SECURITY_SCHEME,
+ FLOWS, DESCRIPTION);
+ OpenApiGrammar.buildSecurityFlows(b,
+ FLOWS, IMPLICIT_FLOW, PASSWORD_FLOW, CREDENTIALS_FLOW, AUTH_FLOW, SECURITY_REQUIREMENT);
}
private static void buildResponses(YamlGrammarBuilder b) {
- b.rule(RESPONSES).is(b.object(
- b.property("default", b.firstOf(RESPONSE, REF)),
- b.patternProperty("^[0-9xX]+", b.firstOf(RESPONSE, REF)),
- b.patternProperty(EXTENSION_PATTERN, b.anything())));
- b.rule(RESPONSE).is(b.object(
- b.mandatoryProperty("description", DESCRIPTION),
- b.property("headers", b.object(
- b.patternProperty(".*", b.firstOf(REF, HEADER)))),
- b.property("content", b.object(
- b.patternProperty(".*", MEDIA_TYPE))),
- b.property("links", b.object(
- b.patternProperty(".*", b.firstOf(REF, LINK)))),
- b.patternProperty(EXTENSION_PATTERN, b.anything())));
- b.rule(HEADER).is(b.object(
- b.property("description", DESCRIPTION),
- b.property("required", b.bool()),
- b.property("deprecated", b.bool()),
- b.property("allowEmptyValue", b.bool()),
-
- b.property("style", "simple"),
- b.property("explode", b.bool()),
- b.property("allowReserved", b.bool()),
- b.property("schema", b.firstOf(REF, SCHEMA)),
- b.property("example", b.anything()),
- b.property("examples", b.object(
- b.patternProperty(".*", b.firstOf(REF, EXAMPLE)))),
- b.property("content", b.object(
- b.patternProperty(".*", MEDIA_TYPE))),
-
- b.patternProperty(EXTENSION_PATTERN, b.anything())));
- b.rule(EXAMPLE).is(b.object(
- b.property("summary", b.string()),
- b.property("description", DESCRIPTION),
- b.property("value", b.anything()),
- b.property("externalValue", b.string()),
- b.patternProperty(EXTENSION_PATTERN, b.anything())));
+ OpenApiGrammar.buildResponsesAndHeader(b,
+ RESPONSES, RESPONSE, REF, HEADER, SCHEMA, EXAMPLE, MEDIA_TYPE, LINK, DESCRIPTION);
+ OpenApiGrammar.buildStandardExample(b, EXAMPLE, DESCRIPTION);
}
private static void buildParameters(YamlGrammarBuilder b) {
- b.rule(PARAMETER).is(b.object(
- b.mandatoryProperty("name", b.string()),
- b.mandatoryProperty("in", b.firstOf("path", "query", "header", "cookie")),
- b.property("description", DESCRIPTION),
- b.property("required", b.bool()),
- b.property("deprecated", b.bool()),
- b.property("allowEmptyValue", b.bool()),
-
- b.property("style", b.firstOf("matrix", "label", "form", "simple", "spaceDelimited", "pipeDelimited", "deepObject")),
- b.property("explode", b.bool()),
- b.property("allowReserved", b.bool()),
- b.property("schema", b.firstOf(REF, SCHEMA)),
- b.property("example", b.anything()),
- b.property("examples", b.object(
- b.patternProperty(".*", b.firstOf(REF, EXAMPLE)))),
- b.property("content", b.object(
- b.patternProperty(".*", MEDIA_TYPE))),
-
- b.patternProperty(EXTENSION_PATTERN, b.anything())));
- b.rule(REQUEST_BODY).is(b.object(
- b.property("description", DESCRIPTION),
- b.property("required", b.bool()),
- b.property("content", b.object(
- b.patternProperty(".*", b.firstOf(REF, MEDIA_TYPE)))),
-
- b.patternProperty(EXTENSION_PATTERN, b.anything())));
- b.rule(MEDIA_TYPE).is(b.object(
- b.property("schema", b.firstOf(REF, SCHEMA)),
- b.property("example", b.anything()),
- b.property("examples", b.object(
- b.patternProperty(".*", b.firstOf(REF, EXAMPLE)))),
- b.property("encoding", b.object(
- b.patternProperty(".*", ENCODING))),
- b.patternProperty(EXTENSION_PATTERN, b.anything())));
- b.rule(ENCODING).is(b.object(
- b.property("contentType", b.string()),
- b.property("headers", b.object(
- b.patternProperty(".*", b.firstOf(REF, HEADER)))),
- b.property("style", b.firstOf("matrix", "label", "form", "simple", "spaceDelimited", "pipeDelimited", "deepObject")),
- b.property("explode", b.bool()),
- b.property("allowReserved", b.bool()),
- b.patternProperty(EXTENSION_PATTERN, b.anything())));
+ OpenApiGrammar.buildStandardParameters(b, PARAMETER, REQUEST_BODY, MEDIA_TYPE, ENCODING,
+ REF, SCHEMA, EXAMPLE, HEADER, DESCRIPTION);
}
private static void buildComponents(YamlGrammarBuilder b) {
@@ -288,20 +147,15 @@ private static void buildComponents(YamlGrammarBuilder b) {
b.property("links", LINKS_COMPONENT),
b.property("callbacks", CALLBACKS_COMPONENT),
b.patternProperty(EXTENSION_PATTERN, b.anything())));
- b.rule(SCHEMAS_COMPONENT).is(b.object(b.patternProperty(".*", b.firstOf(REF, SCHEMA))));
- b.rule(RESPONSES_COMPONENT).is(b.object(b.patternProperty(".*", b.firstOf(REF, RESPONSE))));
- b.rule(PARAMETERS_COMPONENT).is(b.object(b.patternProperty(".*", b.firstOf(REF, PARAMETER))));
- b.rule(EXAMPLES_COMPONENT).is(b.object(b.patternProperty(".*", b.firstOf(REF, EXAMPLE))));
- b.rule(BODIES_COMPONENT).is(b.object(b.patternProperty(".*", b.firstOf(REF, REQUEST_BODY))));
- b.rule(HEADERS_COMPONENT).is(b.object(b.patternProperty(".*", b.firstOf(REF, HEADER))));
- b.rule(SECURITY_SCHEMES).is(b.object(b.patternProperty(".*", b.firstOf(REF, SECURITY_SCHEME))));
- b.rule(LINKS_COMPONENT).is(b.object(b.patternProperty(".*", b.firstOf(REF, LINK))));
- b.rule(CALLBACKS_COMPONENT).is(b.object(b.patternProperty(".*", b.firstOf(REF, CALLBACK))));
+ OpenApiGrammar.buildBaseComponentRules(b,
+ SCHEMAS_COMPONENT, RESPONSES_COMPONENT, PARAMETERS_COMPONENT, EXAMPLES_COMPONENT,
+ BODIES_COMPONENT, HEADERS_COMPONENT, SECURITY_SCHEMES, LINKS_COMPONENT, CALLBACKS_COMPONENT,
+ REF, SCHEMA, RESPONSE, PARAMETER, EXAMPLE, REQUEST_BODY, HEADER, SECURITY_SCHEME, LINK, CALLBACK);
buildParameters(b);
buildResponses(b);
buildSchema(b);
- buildCallbacks(b);
+ OpenApiGrammar.buildCallbacks(b, CALLBACK, LINK, PATH, SERVER, DESCRIPTION);
}
private static void buildSchema(YamlGrammarBuilder b) {
@@ -362,52 +216,13 @@ private static void buildPaths(YamlGrammarBuilder b) {
b.rule(PATHS).is(b.object(
b.patternProperty("^/.*", PATH),
b.patternProperty(EXTENSION_PATTERN, b.anything())));
- b.rule(PATH).is(b.object(
- b.property("$ref", b.string()),
- b.property("summary", b.string()),
- b.property("description", DESCRIPTION),
- b.property("get", OPERATION),
- b.property("put", OPERATION),
- b.property("post", OPERATION),
- b.property("delete", OPERATION),
- b.property("options", OPERATION),
- b.property("head", OPERATION),
- b.property("patch", OPERATION),
- b.property("trace", OPERATION),
- b.property("servers", b.array(SERVER)),
- b.property("parameters", b.array(b.firstOf(REF, PARAMETER))),
- b.patternProperty(EXTENSION_PATTERN, b.anything())));
- b.rule(OPERATION).is(b.object(
- b.property("tags", b.array(b.string())),
- b.property("summary", b.string()),
- b.property("description", DESCRIPTION),
- b.property("externalDocs", EXTERNAL_DOC),
- b.property("operationId", b.string()),
- b.property("parameters", b.array(b.firstOf(REF, PARAMETER))),
- b.property("requestBody", b.firstOf(REF, REQUEST_BODY)),
- b.mandatoryProperty("responses", RESPONSES),
- b.property("callbacks", b.object(
- b.patternProperty(".*", b.firstOf(REF, CALLBACK)))),
- b.property("deprecated", b.bool()),
- b.property("security", b.array(SECURITY_REQUIREMENT)),
- b.property("servers", b.array(SERVER)),
- b.patternProperty(EXTENSION_PATTERN, b.anything())));
+ OpenApiGrammar.buildPathRule(b, PATH, OPERATION, REF, PARAMETER, SERVER, DESCRIPTION);
+ OpenApiGrammar.buildOperation(b, OPERATION, REF, PARAMETER, REQUEST_BODY, RESPONSES,
+ CALLBACK, EXTERNAL_DOC, SECURITY_REQUIREMENT, SERVER, DESCRIPTION);
}
private static void buildServer(YamlGrammarBuilder b) {
- b.rule(SERVER).is(b.object(
- b.mandatoryProperty("url", b.string()),
- b.property("description", DESCRIPTION),
- b.property("variables", b.object(
- b.patternProperty(".*", SERVER_VARIABLE))),
- b.patternProperty(EXTENSION_PATTERN, b.anything())));
-
- b.rule(SERVER_VARIABLE).is(b.object(
- b.property("enum", b.array(b.string())),
- b.mandatoryProperty("default", b.string()),
- b.property("description", DESCRIPTION),
- b.patternProperty(EXTENSION_PATTERN, b.anything())));
-
+ OpenApiGrammar.buildServerAndVariable(b, SERVER, SERVER_VARIABLE, DESCRIPTION);
}
private static void buildInfo(YamlGrammarBuilder b) {
diff --git a/openapi-front-end/src/main/java/org/apiaddicts/apitools/dosonarapi/api/v31/OpenApi31Grammar.java b/openapi-front-end/src/main/java/org/apiaddicts/apitools/dosonarapi/api/v31/OpenApi31Grammar.java
index f1a8e56..b359e92 100644
--- a/openapi-front-end/src/main/java/org/apiaddicts/apitools/dosonarapi/api/v31/OpenApi31Grammar.java
+++ b/openapi-front-end/src/main/java/org/apiaddicts/apitools/dosonarapi/api/v31/OpenApi31Grammar.java
@@ -20,6 +20,7 @@
package org.apiaddicts.apitools.dosonarapi.api.v31;
import org.sonar.sslr.grammar.GrammarRuleKey;
+import org.apiaddicts.apitools.dosonarapi.api.OpenApiGrammar;
import org.apiaddicts.apitools.dosonarapi.sslr.yaml.grammar.YamlGrammarBuilder;
@java.lang.SuppressWarnings("squid:S1192") // Voluntarily ignoring string constants redefinitions in this file
@@ -111,179 +112,31 @@ public static YamlGrammarBuilder create() {
buildWebhooks(b);
buildComponents(b);
buildSecurityDefinitions(b);
- buildTags(b);
+ OpenApiGrammar.buildTags(b, TAG, DESCRIPTION, EXTERNAL_DOC);
return b;
}
- private static void buildTags(YamlGrammarBuilder b) {
- b.rule(TAG).is(b.object(
- b.mandatoryProperty("name", b.string()),
- b.property("description", DESCRIPTION),
- b.property("externalDocs", EXTERNAL_DOC),
- b.patternProperty(EXTENSION_PATTERN, b.anything())));
- }
-
private static void buildSecurityDefinitions(YamlGrammarBuilder b) {
- b.rule(SECURITY_SCHEME).is(
- b.firstOf(HTTP_SECURITY_SCHEME, API_KEY_SECURITY_SCHEME, OAUTH2_SECURITY_SCHEME, OPENID_SECURITY_SCHEME, MUTUALTLS_SECURITY_SCHEME));
- b.rule(HTTP_SECURITY_SCHEME).is(b.object(
- b.discriminant("type", "http"),
- b.property("description", DESCRIPTION),
- b.mandatoryProperty("scheme", b.string()),
- b.property("bearerFormat", b.string()),
- b.patternProperty(EXTENSION_PATTERN, b.anything()))).skip();
- b.rule(API_KEY_SECURITY_SCHEME).is(b.object(
- b.discriminant("type", "apiKey"),
- b.property("description", DESCRIPTION),
- b.mandatoryProperty("name", b.string()),
- b.mandatoryProperty("in", b.firstOf("query", "header", "cookie")),
- b.patternProperty(EXTENSION_PATTERN, b.anything()))).skip();
+ OpenApiGrammar.buildMutualTlsSecuritySetup(b, SECURITY_SCHEME,
+ HTTP_SECURITY_SCHEME, API_KEY_SECURITY_SCHEME, OAUTH2_SECURITY_SCHEME, OPENID_SECURITY_SCHEME,
+ MUTUALTLS_SECURITY_SCHEME, FLOWS, DESCRIPTION);
b.rule(MUTUALTLS_SECURITY_SCHEME).is(b.object(
b.discriminant("type", "mutualTLS"),
- b.patternProperty(EXTENSION_PATTERN, b.anything()))).skip();
- b.rule(OAUTH2_SECURITY_SCHEME).is(b.object(
- b.discriminant("type", "oauth2"),
- b.property("description", DESCRIPTION),
- b.mandatoryProperty("flows", FLOWS),
- b.patternProperty(EXTENSION_PATTERN, b.anything()))).skip();
- b.rule(OPENID_SECURITY_SCHEME).is(b.object(
- b.discriminant("type", "openIdConnect"),
- b.property("description", DESCRIPTION),
- b.mandatoryProperty("openIdConnectUrl", b.string()),
b.patternProperty(EXTENSION_PATTERN, b.anything()))).skip();
- b.rule(FLOWS).is(b.object(
- b.property("implicit", IMPLICIT_FLOW),
- b.property("password", PASSWORD_FLOW),
- b.property("clientCredentials", CREDENTIALS_FLOW),
- b.property("authorizationCode", AUTH_FLOW),
- b.patternProperty(EXTENSION_PATTERN, b.anything())));
- b.rule(IMPLICIT_FLOW).is(b.object(
- b.mandatoryProperty("authorizationUrl", b.string()),
- b.property("refreshUrl", b.string()),
- b.property("scopes", b.object(
- b.patternProperty(".*", b.string()))),
- b.patternProperty(EXTENSION_PATTERN, b.anything())));
- b.rule(PASSWORD_FLOW).is(b.object(
- b.mandatoryProperty("tokenUrl", b.string()),
- b.property("refreshUrl", b.string()),
- b.property("scopes", b.object(
- b.patternProperty(".*", b.string()))),
- b.patternProperty(EXTENSION_PATTERN, b.anything())));
- b.rule(CREDENTIALS_FLOW).is(b.object(
- b.mandatoryProperty("tokenUrl", b.string()),
- b.property("refreshUrl", b.string()),
- b.property("scopes", b.object(
- b.patternProperty(".*", b.string()))),
- b.patternProperty(EXTENSION_PATTERN, b.anything())));
- b.rule(AUTH_FLOW).is(b.object(
- b.mandatoryProperty("authorizationUrl", b.string()),
- b.mandatoryProperty("tokenUrl", b.string()),
- b.property("refreshUrl", b.string()),
- b.property("scopes", b.object(
- b.patternProperty(".*", b.string()))),
- b.patternProperty(EXTENSION_PATTERN, b.anything())));
- b.rule(SECURITY_REQUIREMENT).is(b.object(
- b.patternProperty(".*", b.array(b.string()))));
- }
-
- private static void buildCallbacks(YamlGrammarBuilder b) {
- b.rule(CALLBACK).is(b.object(
- b.patternProperty("^[^x].*", PATH),
- b.patternProperty(EXTENSION_PATTERN, b.anything())));
- b.rule(LINK).is(b.object(
- b.property("operationRef", b.string()),
- b.property("operationId", b.string()),
- b.property("parameters", b.object(
- b.patternProperty(".*", b.anything()))),
- b.property("requestBody", b.anything()),
- b.property("description", DESCRIPTION),
- b.property("server", SERVER),
- b.patternProperty(EXTENSION_PATTERN, b.anything())));
+ OpenApiGrammar.buildSecurityFlows(b,
+ FLOWS, IMPLICIT_FLOW, PASSWORD_FLOW, CREDENTIALS_FLOW, AUTH_FLOW, SECURITY_REQUIREMENT);
}
private static void buildResponses(YamlGrammarBuilder b) {
- b.rule(RESPONSES).is(b.object(
- b.property("default", b.firstOf(RESPONSE, REF)),
- b.patternProperty("^[0-9xX]+", b.firstOf(RESPONSE, REF)),
- b.patternProperty(EXTENSION_PATTERN, b.anything())));
- b.rule(RESPONSE).is(b.object(
- b.mandatoryProperty("description", DESCRIPTION),
- b.property("headers", b.object(
- b.patternProperty(".*", b.firstOf(REF, HEADER)))),
- b.property("content", b.object(
- b.patternProperty(".*", MEDIA_TYPE))),
- b.property("links", b.object(
- b.patternProperty(".*", b.firstOf(REF, LINK)))),
- b.patternProperty(EXTENSION_PATTERN, b.anything())));
- b.rule(HEADER).is(b.object(
- b.property("description", DESCRIPTION),
- b.property("required", b.bool()),
- b.property("deprecated", b.bool()),
- b.property("allowEmptyValue", b.bool()),
-
- b.property("style", "simple"),
- b.property("explode", b.bool()),
- b.property("allowReserved", b.bool()),
- b.property("schema", b.firstOf(REF, SCHEMA)),
- b.property("example", b.anything()),
- b.property("examples", b.object(
- b.patternProperty(".*", b.firstOf(REF, EXAMPLE)))),
- b.property("content", b.object(
- b.patternProperty(".*", MEDIA_TYPE))),
-
- b.patternProperty(EXTENSION_PATTERN, b.anything())));
- b.rule(EXAMPLE).is(b.object(
- b.property("summary", b.string()),
- b.property("description", DESCRIPTION),
- b.property("value", b.anything()),
- b.property("externalValue", b.string()),
- b.patternProperty(EXTENSION_PATTERN, b.anything())));
+ OpenApiGrammar.buildResponsesAndHeader(b,
+ RESPONSES, RESPONSE, REF, HEADER, SCHEMA, EXAMPLE, MEDIA_TYPE, LINK, DESCRIPTION);
+ OpenApiGrammar.buildStandardExample(b, EXAMPLE, DESCRIPTION);
}
private static void buildParameters(YamlGrammarBuilder b) {
- b.rule(PARAMETER).is(b.object(
- b.mandatoryProperty("name", b.string()),
- b.mandatoryProperty("in", b.firstOf("path", "query", "header", "cookie")),
- b.property("description", DESCRIPTION),
- b.property("required", b.bool()),
- b.property("deprecated", b.bool()),
- b.property("allowEmptyValue", b.bool()),
-
- b.property("style", b.firstOf("matrix", "label", "form", "simple", "spaceDelimited", "pipeDelimited", "deepObject")),
- b.property("explode", b.bool()),
- b.property("allowReserved", b.bool()),
- b.property("schema", b.firstOf(REF, SCHEMA)),
- b.property("example", b.anything()),
- b.property("examples", b.object(
- b.patternProperty(".*", b.firstOf(REF, EXAMPLE)))),
- b.property("content", b.object(
- b.patternProperty(".*", MEDIA_TYPE))),
-
- b.patternProperty(EXTENSION_PATTERN, b.anything())));
- b.rule(REQUEST_BODY).is(b.object(
- b.property("description", DESCRIPTION),
- b.property("required", b.bool()),
- b.property("content", b.object(
- b.patternProperty(".*", b.firstOf(REF, MEDIA_TYPE)))),
-
- b.patternProperty(EXTENSION_PATTERN, b.anything())));
- b.rule(MEDIA_TYPE).is(b.object(
- b.property("schema", b.firstOf(REF, SCHEMA)),
- b.property("example", b.anything()),
- b.property("examples", b.object(
- b.patternProperty(".*", b.firstOf(REF, EXAMPLE)))),
- b.property("encoding", b.object(
- b.patternProperty(".*", ENCODING))),
- b.patternProperty(EXTENSION_PATTERN, b.anything())));
- b.rule(ENCODING).is(b.object(
- b.property("contentType", b.string()),
- b.property("headers", b.object(
- b.patternProperty(".*", b.firstOf(REF, HEADER)))),
- b.property("style", b.firstOf("matrix", "label", "form", "simple", "spaceDelimited", "pipeDelimited", "deepObject")),
- b.property("explode", b.bool()),
- b.property("allowReserved", b.bool()),
- b.patternProperty(EXTENSION_PATTERN, b.anything())));
+ OpenApiGrammar.buildStandardParameters(b, PARAMETER, REQUEST_BODY, MEDIA_TYPE, ENCODING,
+ REF, SCHEMA, EXAMPLE, HEADER, DESCRIPTION);
}
private static void buildComponents(YamlGrammarBuilder b) {
@@ -299,27 +152,24 @@ private static void buildComponents(YamlGrammarBuilder b) {
b.property("links", LINKS_COMPONENT),
b.property("callbacks", CALLBACKS_COMPONENT),
b.patternProperty(EXTENSION_PATTERN, b.anything())));
- b.rule(SCHEMAS_COMPONENT).is(b.object(b.patternProperty(".*", b.firstOf(REF, SCHEMA))));
+ OpenApiGrammar.buildBaseComponentRules(b,
+ SCHEMAS_COMPONENT, RESPONSES_COMPONENT, PARAMETERS_COMPONENT, EXAMPLES_COMPONENT,
+ BODIES_COMPONENT, HEADERS_COMPONENT, SECURITY_SCHEMES, LINKS_COMPONENT, CALLBACKS_COMPONENT,
+ REF, SCHEMA, RESPONSE, PARAMETER, EXAMPLE, REQUEST_BODY, HEADER, SECURITY_SCHEME, LINK, CALLBACK);
b.rule(WEBHOOKS_COMPONENT).is(b.object(b.patternProperty(".*", b.firstOf(REF, WEBHOOK))));
- b.rule(RESPONSES_COMPONENT).is(b.object(b.patternProperty(".*", b.firstOf(REF, RESPONSE))));
- b.rule(PARAMETERS_COMPONENT).is(b.object(b.patternProperty(".*", b.firstOf(REF, PARAMETER))));
- b.rule(EXAMPLES_COMPONENT).is(b.object(b.patternProperty(".*", b.firstOf(REF, EXAMPLE))));
- b.rule(BODIES_COMPONENT).is(b.object(b.patternProperty(".*", b.firstOf(REF, REQUEST_BODY))));
- b.rule(HEADERS_COMPONENT).is(b.object(b.patternProperty(".*", b.firstOf(REF, HEADER))));
- b.rule(SECURITY_SCHEMES).is(b.object(b.patternProperty(".*", b.firstOf(REF, SECURITY_SCHEME))));
- b.rule(LINKS_COMPONENT).is(b.object(b.patternProperty(".*", b.firstOf(REF, LINK))));
- b.rule(CALLBACKS_COMPONENT).is(b.object(b.patternProperty(".*", b.firstOf(REF, CALLBACK))));
buildParameters(b);
buildResponses(b);
buildSchema(b);
- buildCallbacks(b);
+ OpenApiGrammar.buildCallbacks(b, CALLBACK, LINK, PATH, SERVER, DESCRIPTION);
}
private static void buildSchema(YamlGrammarBuilder b) {
b.rule(SCHEMA).is(b.object(
b.property("title", b.string()),
b.property("multipleOf", b.firstOf(b.integer(), b.floating())),
+ b.property("maximum", b.firstOf(b.integer(), b.floating())),
+ b.property("minimum", b.firstOf(b.integer(), b.floating())),
b.property("exclusiveMaximum", b.firstOf(b.integer(), b.floating())),
b.property("exclusiveMinimum", b.firstOf(b.integer(), b.floating())),
b.property("maxLength", b.integer()),
@@ -391,22 +241,9 @@ private static void buildWebhooks(YamlGrammarBuilder b) {
b.property("servers", b.array(SERVER)),
b.property("parameters", b.array(b.firstOf(REF, PARAMETER))),
b.patternProperty(EXTENSION_PATTERN, b.anything())));
- b.rule(OPERATION_WEBHOOKS).is(b.object(
- b.property("tags", b.array(b.string())),
- b.property("summary", b.string()),
- b.property("description", DESCRIPTION),
- b.property("externalDocs", EXTERNAL_DOC),
- b.property("operationId", b.string()),
- b.property("parameters", b.array(b.firstOf(REF, PARAMETER))),
- b.property("requestBody", b.firstOf(REF, REQUEST_BODY)),
- b.mandatoryProperty("responses", RESPONSES),
- b.property("callbacks", b.object(
- b.patternProperty(".*", b.firstOf(REF, CALLBACK)))),
- b.property("deprecated", b.bool()),
- b.property("security", b.array(SECURITY_REQUIREMENT)),
- b.property("servers", b.array(SERVER)),
- b.patternProperty(EXTENSION_PATTERN, b.anything())));
-}
+ OpenApiGrammar.buildOperation(b, OPERATION_WEBHOOKS, REF, PARAMETER, REQUEST_BODY, RESPONSES,
+ CALLBACK, EXTERNAL_DOC, SECURITY_REQUIREMENT, SERVER, DESCRIPTION);
+ }
@@ -414,57 +251,19 @@ private static void buildPaths(YamlGrammarBuilder b) {
b.rule(PATHS).is(b.object(
b.patternProperty("^/.*", PATH),
b.patternProperty(EXTENSION_PATTERN, b.anything())));
- b.rule(PATH).is(b.object(
- b.property("$ref", b.string()),
- b.property("summary", b.string()),
- b.property("description", DESCRIPTION),
- b.property("get", OPERATION),
- b.property("put", OPERATION),
- b.property("post", OPERATION),
- b.property("delete", OPERATION),
- b.property("options", OPERATION),
- b.property("head", OPERATION),
- b.property("patch", OPERATION),
- b.property("trace", OPERATION),
- b.property("servers", b.array(SERVER)),
- b.property("parameters", b.array(b.firstOf(REF, PARAMETER))),
- b.patternProperty(EXTENSION_PATTERN, b.anything())));
- b.rule(OPERATION).is(b.object(
- b.property("tags", b.array(b.string())),
- b.property("summary", b.string()),
- b.property("description", DESCRIPTION),
- b.property("externalDocs", EXTERNAL_DOC),
- b.property("operationId", b.string()),
- b.property("parameters", b.array(b.firstOf(REF, PARAMETER))),
- b.property("requestBody", b.firstOf(REF, REQUEST_BODY)),
- b.mandatoryProperty("responses", RESPONSES),
- b.property("callbacks", b.object(
- b.patternProperty(".*", b.firstOf(REF, CALLBACK)))),
- b.property("deprecated", b.bool()),
- b.property("security", b.array(SECURITY_REQUIREMENT)),
- b.property("servers", b.array(SERVER)),
- b.patternProperty(EXTENSION_PATTERN, b.anything())));
+ OpenApiGrammar.buildPathRule(b, PATH, OPERATION, REF, PARAMETER, SERVER, DESCRIPTION);
+ OpenApiGrammar.buildOperation(b, OPERATION, REF, PARAMETER, REQUEST_BODY, RESPONSES,
+ CALLBACK, EXTERNAL_DOC, SECURITY_REQUIREMENT, SERVER, DESCRIPTION);
}
private static void buildServer(YamlGrammarBuilder b) {
- b.rule(SERVER).is(b.object(
- b.mandatoryProperty("url", b.string()),
- b.property("description", DESCRIPTION),
- b.property("variables", b.object(
- b.patternProperty(".*", SERVER_VARIABLE))),
- b.patternProperty(EXTENSION_PATTERN, b.anything())));
-
- b.rule(SERVER_VARIABLE).is(b.object(
- b.property("enum", b.array(b.string())),
- b.mandatoryProperty("default", b.string()),
- b.property("description", DESCRIPTION),
- b.patternProperty(EXTENSION_PATTERN, b.anything())));
-
+ OpenApiGrammar.buildServerAndVariable(b, SERVER, SERVER_VARIABLE, DESCRIPTION);
}
private static void buildInfo(YamlGrammarBuilder b) {
b.rule(INFO).is(b.object(
b.mandatoryProperty("title", b.string()),
+ b.property("summary", b.string()),
b.property("description", DESCRIPTION),
b.property("termsOfService", b.string()),
b.property("contact", CONTACT),
diff --git a/openapi-front-end/src/main/java/org/apiaddicts/apitools/dosonarapi/api/v32/OpenApi32Grammar.java b/openapi-front-end/src/main/java/org/apiaddicts/apitools/dosonarapi/api/v32/OpenApi32Grammar.java
new file mode 100644
index 0000000..7a6827c
--- /dev/null
+++ b/openapi-front-end/src/main/java/org/apiaddicts/apitools/dosonarapi/api/v32/OpenApi32Grammar.java
@@ -0,0 +1,380 @@
+/*
+ * doSonarAPI: SonarQube OpenAPI Plugin
+ * Copyright (C) 2021-2022 Apiaddicts
+ * contacta AT apiaddicts DOT org
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.apiaddicts.apitools.dosonarapi.api.v32;
+
+import org.sonar.sslr.grammar.GrammarRuleKey;
+import org.apiaddicts.apitools.dosonarapi.api.OpenApiGrammar;
+import org.apiaddicts.apitools.dosonarapi.sslr.yaml.grammar.YamlGrammarBuilder;
+
+@java.lang.SuppressWarnings("squid:S1192")
+public enum OpenApi32Grammar implements GrammarRuleKey {
+ ROOT,
+ INFO,
+ PATHS,
+ COMPONENTS,
+ PARAMETER,
+ RESPONSE,
+ SECURITY_SCHEME,
+ SECURITY_REQUIREMENT,
+ TAG,
+ REF,
+ EXTERNAL_DOC,
+ CONTACT,
+ LICENSE,
+
+ PATH,
+ OPERATION,
+ OPERATION_WEBHOOKS,
+ LINK,
+ CALLBACK,
+ RESPONSES,
+ REQUEST_BODY,
+
+ SCHEMA,
+ WEBHOOK,
+ WEBHOOKS,
+ DISCRIMINATOR,
+ HEADER,
+ EXAMPLE,
+ XML,
+ SERVER,
+ SERVER_VARIABLE,
+ HTTP_SECURITY_SCHEME,
+ API_KEY_SECURITY_SCHEME,
+ MUTUALTLS_SECURITY_SCHEME,
+ OAUTH2_SECURITY_SCHEME,
+ OPENID_SECURITY_SCHEME,
+ MEDIA_TYPE,
+ ENCODING,
+ FLOWS,
+ IMPLICIT_FLOW,
+ PASSWORD_FLOW,
+ CREDENTIALS_FLOW,
+ AUTH_FLOW,
+ SCHEMAS_COMPONENT,
+ WEBHOOKS_COMPONENT,
+ RESPONSES_COMPONENT,
+ PARAMETERS_COMPONENT,
+ EXAMPLES_COMPONENT,
+ BODIES_COMPONENT,
+ HEADERS_COMPONENT,
+ SECURITY_SCHEMES,
+ LINKS_COMPONENT,
+ CALLBACKS_COMPONENT,
+ PATH_ITEMS_COMPONENT,
+ MEDIA_TYPES_COMPONENT,
+ SCHEMA_PROPERTIES,
+ DESCRIPTION;
+
+ private static final String EXTENSION_PATTERN = "^x-.*";
+
+ public static YamlGrammarBuilder create() {
+ YamlGrammarBuilder b = new YamlGrammarBuilder();
+ b.setRootRule(ROOT);
+
+ b.rule(ROOT).is(b.object(
+ b.mandatoryProperty("openapi", "3.2.0"),
+ b.mandatoryProperty("info", INFO),
+ b.property("$self", b.string()),
+ b.property("jsonSchemaDialect", b.string()),
+ b.property("servers", b.array(SERVER)),
+ b.property("paths", PATHS),
+ b.property("webhooks", WEBHOOKS),
+ b.property("components", COMPONENTS),
+ b.property("security", b.array(SECURITY_REQUIREMENT)),
+ b.property("tags", b.array(TAG)),
+ b.property("externalDocs", EXTERNAL_DOC),
+ b.patternProperty(EXTENSION_PATTERN, b.anything())));
+
+ b.rule(REF).is(b.object(
+ b.mandatoryProperty("$ref", b.string()),
+ b.property("summary", b.string()),
+ b.property("description", DESCRIPTION)));
+
+ b.rule(EXTERNAL_DOC).is(b.object(
+ b.property("description", DESCRIPTION),
+ b.mandatoryProperty("url", b.string()),
+ b.patternProperty(EXTENSION_PATTERN, b.anything())));
+
+ b.rule(DESCRIPTION).is(b.string()).skip();
+ buildInfo(b);
+ buildServer(b);
+ buildPaths(b);
+ buildWebhooks(b);
+ buildComponents(b);
+ buildSecurityDefinitions(b);
+ OpenApiGrammar.buildTags(b, TAG, DESCRIPTION, EXTERNAL_DOC);
+
+ return b;
+ }
+
+ private static void buildSecurityDefinitions(YamlGrammarBuilder b) {
+ OpenApiGrammar.buildMutualTlsSecuritySetup(b, SECURITY_SCHEME,
+ HTTP_SECURITY_SCHEME, API_KEY_SECURITY_SCHEME, OAUTH2_SECURITY_SCHEME, OPENID_SECURITY_SCHEME,
+ MUTUALTLS_SECURITY_SCHEME, FLOWS, DESCRIPTION);
+ b.rule(MUTUALTLS_SECURITY_SCHEME).is(b.object(
+ b.discriminant("type", "mutualTLS"),
+ b.property("description", DESCRIPTION),
+ b.patternProperty(EXTENSION_PATTERN, b.anything()))).skip();
+ OpenApiGrammar.buildSecurityFlows(b,
+ FLOWS, IMPLICIT_FLOW, PASSWORD_FLOW, CREDENTIALS_FLOW, AUTH_FLOW, SECURITY_REQUIREMENT);
+ }
+
+ private static void buildCallbacks(YamlGrammarBuilder b) {
+ OpenApiGrammar.buildCallbacks(b, CALLBACK, LINK, PATH, SERVER, DESCRIPTION);
+ }
+
+ private static void buildResponses(YamlGrammarBuilder b) {
+ OpenApiGrammar.buildResponsesAndHeader(b,
+ RESPONSES, RESPONSE, REF, HEADER, SCHEMA, EXAMPLE, MEDIA_TYPE, LINK, DESCRIPTION);
+ b.rule(EXAMPLE).is(b.object(
+ b.property("summary", b.string()),
+ b.property("description", DESCRIPTION),
+ b.property("value", b.anything()),
+ b.property("externalValue", b.string()),
+ b.property("dataValue", b.anything()),
+ b.property("serializedValue", b.string()),
+ b.patternProperty(EXTENSION_PATTERN, b.anything())));
+ }
+
+ private static void buildParameters(YamlGrammarBuilder b) {
+ b.rule(PARAMETER).is(b.object(
+ b.mandatoryProperty("name", b.string()),
+ b.mandatoryProperty("in", b.firstOf("path", "query", "querystring", "header", "cookie")),
+ b.property("description", DESCRIPTION),
+ b.property("required", b.bool()),
+ b.property("deprecated", b.bool()),
+ b.property("allowEmptyValue", b.bool()),
+ b.property("style", b.firstOf("matrix", "label", "form", "simple", "spaceDelimited", "pipeDelimited", "deepObject")),
+ b.property("explode", b.bool()),
+ b.property("allowReserved", b.bool()),
+ b.property("schema", b.firstOf(REF, SCHEMA)),
+ b.property("example", b.anything()),
+ b.property("examples", b.object(
+ b.patternProperty(".*", b.firstOf(REF, EXAMPLE)))),
+ b.property("content", b.object(
+ b.patternProperty(".*", MEDIA_TYPE))),
+ b.patternProperty(EXTENSION_PATTERN, b.anything())));
+ OpenApiGrammar.buildRequestBody(b, REQUEST_BODY, REF, MEDIA_TYPE, DESCRIPTION);
+ b.rule(MEDIA_TYPE).is(b.object(
+ b.property("schema", b.firstOf(REF, SCHEMA)),
+ b.property("example", b.anything()),
+ b.property("examples", b.object(
+ b.patternProperty(".*", b.firstOf(REF, EXAMPLE)))),
+ b.property("encoding", b.object(
+ b.patternProperty(".*", ENCODING))),
+ b.property("headers", b.object(
+ b.patternProperty(".*", b.firstOf(REF, HEADER)))),
+ b.patternProperty(EXTENSION_PATTERN, b.anything())));
+ OpenApiGrammar.buildEncoding(b, ENCODING, REF, HEADER);
+ }
+
+ private static void buildComponents(YamlGrammarBuilder b) {
+ b.rule(COMPONENTS).is(b.object(
+ b.property("schemas", SCHEMAS_COMPONENT),
+ b.property("webhooks", WEBHOOKS_COMPONENT),
+ b.property("responses", RESPONSES_COMPONENT),
+ b.property("parameters", PARAMETERS_COMPONENT),
+ b.property("examples", EXAMPLES_COMPONENT),
+ b.property("requestBodies", BODIES_COMPONENT),
+ b.property("headers", HEADERS_COMPONENT),
+ b.property("securitySchemes", SECURITY_SCHEMES),
+ b.property("links", LINKS_COMPONENT),
+ b.property("callbacks", CALLBACKS_COMPONENT),
+ b.property("pathItems", PATH_ITEMS_COMPONENT),
+ b.property("mediaTypes", MEDIA_TYPES_COMPONENT),
+ b.patternProperty(EXTENSION_PATTERN, b.anything())));
+ OpenApiGrammar.buildBaseComponentRules(b,
+ SCHEMAS_COMPONENT, RESPONSES_COMPONENT, PARAMETERS_COMPONENT, EXAMPLES_COMPONENT,
+ BODIES_COMPONENT, HEADERS_COMPONENT, SECURITY_SCHEMES, LINKS_COMPONENT, CALLBACKS_COMPONENT,
+ REF, SCHEMA, RESPONSE, PARAMETER, EXAMPLE, REQUEST_BODY, HEADER, SECURITY_SCHEME, LINK, CALLBACK);
+ b.rule(WEBHOOKS_COMPONENT).is(b.object(b.patternProperty(".*", b.firstOf(REF, WEBHOOK))));
+ b.rule(PATH_ITEMS_COMPONENT).is(b.object(b.patternProperty(".*", b.firstOf(REF, PATH))));
+ b.rule(MEDIA_TYPES_COMPONENT).is(b.object(b.patternProperty(".*", b.firstOf(REF, MEDIA_TYPE))));
+
+ buildParameters(b);
+ buildResponses(b);
+ buildSchema(b);
+ buildCallbacks(b);
+ }
+
+ private static void buildSchema(YamlGrammarBuilder b) {
+ b.rule(SCHEMA).is(b.object(
+ b.property("title", b.string()),
+ b.property("multipleOf", b.firstOf(b.integer(), b.floating())),
+ b.property("maximum", b.firstOf(b.integer(), b.floating())),
+ b.property("minimum", b.firstOf(b.integer(), b.floating())),
+ b.property("exclusiveMaximum", b.firstOf(b.integer(), b.floating())),
+ b.property("exclusiveMinimum", b.firstOf(b.integer(), b.floating())),
+ b.property("maxLength", b.integer()),
+ b.property("minLength", b.integer()),
+ b.property("pattern", b.string()),
+ b.property("maxItems", b.integer()),
+ b.property("minItems", b.integer()),
+ b.property("uniqueItems", b.bool()),
+ b.property("maxProperties", b.integer()),
+ b.property("minProperties", b.integer()),
+ b.property("required", b.array(b.string())),
+ b.property("enum", b.array(b.anything())),
+ b.property("type", b.firstOf(b.string(), b.array(b.string()))),
+ b.property("contentMediaType", b.string()),
+ b.property("contentEncoding", b.string()),
+ b.property("allOf", b.array(b.firstOf(REF, SCHEMA))),
+ b.property("oneOf", b.array(b.firstOf(REF, SCHEMA))),
+ b.property("anyOf", b.array(b.firstOf(REF, SCHEMA))),
+ b.property("not", b.firstOf(REF, SCHEMA)),
+ b.property("if", b.firstOf(REF, SCHEMA)),
+ b.property("then", b.firstOf(REF, SCHEMA)),
+ b.property("else", b.firstOf(REF, SCHEMA)),
+ b.property("prefixItems", b.array(b.firstOf(REF, SCHEMA))),
+ b.property("items", b.firstOf(REF, SCHEMA)),
+ b.property("contains", b.firstOf(REF, SCHEMA)),
+ b.property("minContains", b.integer()),
+ b.property("maxContains", b.integer()),
+ b.property("unevaluatedItems", b.firstOf(b.bool(), REF, SCHEMA)),
+ b.property("properties", SCHEMA_PROPERTIES),
+ b.property("patternProperties", SCHEMA_PROPERTIES),
+ b.property("propertyNames", b.firstOf(REF, SCHEMA)),
+ b.property("dependentSchemas", b.object(b.patternProperty(".*", b.firstOf(REF, SCHEMA)))),
+ b.property("dependentRequired", b.object(b.patternProperty(".*", b.array(b.string())))),
+ b.property("$schema", b.string()),
+ b.property("$anchor", b.string()),
+ b.property("$defs", b.object(b.patternProperty(".*", b.firstOf(REF, SCHEMA)))),
+ b.property("$dynamicRef", b.string()),
+ b.property("$dynamicAnchor", b.string()),
+ b.property("$comment", b.string()),
+ b.property("additionalProperties", b.firstOf(b.bool(), REF, SCHEMA)),
+ b.property("description", DESCRIPTION),
+ b.property("unevaluatedProperties", b.firstOf(b.bool(), REF, SCHEMA)),
+ b.property("format", b.string()),
+ b.property("default", b.anything()),
+ b.property("nullable", b.bool()),
+ b.property("discriminator", DISCRIMINATOR),
+ b.property("const", b.anything()),
+ b.property("readOnly", b.bool()),
+ b.property("writeOnly", b.bool()),
+ b.property("xml", XML),
+ b.property("externalDocs", EXTERNAL_DOC),
+ b.property("examples", b.array(b.anything())),
+ b.property("example", b.anything()),
+ b.property("deprecated", b.bool()),
+ b.patternProperty(EXTENSION_PATTERN, b.anything())));
+ b.rule(SCHEMA_PROPERTIES).is(b.object(b.patternProperty(".*", b.firstOf(REF, SCHEMA))));
+ b.rule(DISCRIMINATOR).is(b.object(
+ b.property("propertyName", b.string()),
+ b.property("mapping", b.object(
+ b.patternProperty(".*", b.string())))));
+ b.rule(XML).is(b.object(
+ b.property("name", b.string()),
+ b.property("namespace", b.string()),
+ b.property("prefix", b.string()),
+ b.property("attribute", b.bool()),
+ b.property("wrapped", b.bool()),
+ b.property("text", b.bool()),
+ b.patternProperty(EXTENSION_PATTERN, b.anything())));
+ }
+
+ private static void buildWebhooks(YamlGrammarBuilder b) {
+ b.rule(WEBHOOKS).is(b.object(
+ b.patternProperty("^.*", WEBHOOK),
+ b.patternProperty(EXTENSION_PATTERN, b.anything())));
+ b.rule(WEBHOOK).is(b.object(
+ b.property("$ref", b.string()),
+ b.property("summary", b.string()),
+ b.property("description", DESCRIPTION),
+ b.property("get", OPERATION),
+ b.property("put", OPERATION),
+ b.property("post", OPERATION),
+ b.property("delete", OPERATION),
+ b.property("options", OPERATION),
+ b.property("head", OPERATION),
+ b.property("patch", OPERATION),
+ b.property("trace", OPERATION),
+ b.property("query", OPERATION),
+ b.property("additionalOperations", b.object(b.patternProperty(".*", OPERATION))),
+ b.property("servers", b.array(SERVER)),
+ b.property("parameters", b.array(b.firstOf(REF, PARAMETER))),
+ b.patternProperty(EXTENSION_PATTERN, b.anything())));
+ OpenApiGrammar.buildOperation(b, OPERATION_WEBHOOKS, REF, PARAMETER, REQUEST_BODY, RESPONSES,
+ CALLBACK, EXTERNAL_DOC, SECURITY_REQUIREMENT, SERVER, DESCRIPTION);
+ }
+
+ private static void buildPaths(YamlGrammarBuilder b) {
+ b.rule(PATHS).is(b.object(
+ b.patternProperty("^/.*", PATH),
+ b.patternProperty(EXTENSION_PATTERN, b.anything())));
+ b.rule(PATH).is(b.object(
+ b.property("$ref", b.string()),
+ b.property("summary", b.string()),
+ b.property("description", DESCRIPTION),
+ b.property("get", OPERATION),
+ b.property("put", OPERATION),
+ b.property("post", OPERATION),
+ b.property("delete", OPERATION),
+ b.property("options", OPERATION),
+ b.property("head", OPERATION),
+ b.property("patch", OPERATION),
+ b.property("trace", OPERATION),
+ b.property("query", OPERATION),
+ b.property("additionalOperations", b.object(b.patternProperty(".*", OPERATION))),
+ b.property("servers", b.array(SERVER)),
+ b.property("parameters", b.array(b.firstOf(REF, PARAMETER))),
+ b.patternProperty(EXTENSION_PATTERN, b.anything())));
+ OpenApiGrammar.buildOperation(b, OPERATION, REF, PARAMETER, REQUEST_BODY, RESPONSES,
+ CALLBACK, EXTERNAL_DOC, SECURITY_REQUIREMENT, SERVER, DESCRIPTION);
+ }
+
+ private static void buildServer(YamlGrammarBuilder b) {
+ b.rule(SERVER).is(b.object(
+ b.mandatoryProperty("url", b.string()),
+ b.property("name", b.string()),
+ b.property("description", DESCRIPTION),
+ b.property("variables", b.object(
+ b.patternProperty(".*", SERVER_VARIABLE))),
+ b.patternProperty(EXTENSION_PATTERN, b.anything())));
+ b.rule(SERVER_VARIABLE).is(b.object(
+ b.property("enum", b.array(b.string())),
+ b.mandatoryProperty("default", b.string()),
+ b.property("description", DESCRIPTION),
+ b.patternProperty(EXTENSION_PATTERN, b.anything())));
+ }
+
+ private static void buildInfo(YamlGrammarBuilder b) {
+ b.rule(INFO).is(b.object(
+ b.mandatoryProperty("title", b.string()),
+ b.property("summary", b.string()),
+ b.property("description", DESCRIPTION),
+ b.property("termsOfService", b.string()),
+ b.property("contact", CONTACT),
+ b.property("license", LICENSE),
+ b.mandatoryProperty("version", b.string()),
+ b.patternProperty(EXTENSION_PATTERN, b.anything())));
+ b.rule(CONTACT).is(b.object(
+ b.property("name", b.string()),
+ b.property("url", b.string()),
+ b.property("email", b.string()),
+ b.patternProperty(EXTENSION_PATTERN, b.anything())));
+ b.rule(LICENSE).is(b.object(
+ b.mandatoryProperty("name", b.string()),
+ b.property("url", b.string()),
+ b.property("identifier", b.string()),
+ b.patternProperty(EXTENSION_PATTERN, b.anything())));
+ }
+}
diff --git a/openapi-front-end/src/main/java/org/apiaddicts/apitools/dosonarapi/openapi/parser/OpenApiParser.java b/openapi-front-end/src/main/java/org/apiaddicts/apitools/dosonarapi/openapi/parser/OpenApiParser.java
index 96b578d..7b97ac2 100644
--- a/openapi-front-end/src/main/java/org/apiaddicts/apitools/dosonarapi/openapi/parser/OpenApiParser.java
+++ b/openapi-front-end/src/main/java/org/apiaddicts/apitools/dosonarapi/openapi/parser/OpenApiParser.java
@@ -22,6 +22,7 @@
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.api.v32.OpenApi32Grammar;
import org.apiaddicts.apitools.dosonarapi.openapi.OpenApiConfiguration;
import org.apiaddicts.apitools.dosonarapi.sslr.yaml.grammar.YamlParser;
@@ -42,6 +43,10 @@ public static YamlParser createV31(OpenApiConfiguration configuration) {
return YamlParser.builder().withCharset(configuration.getCharset()).withGrammar(OpenApi31Grammar.create()).withStrictValidation(configuration.isStrict()).build();
}
+ public static YamlParser createV32(OpenApiConfiguration configuration) {
+ return YamlParser.builder().withCharset(configuration.getCharset()).withGrammar(OpenApi32Grammar.create()).withStrictValidation(configuration.isStrict()).build();
+ }
+
public static YamlParser createGeneric(OpenApiConfiguration configuration) {
return YamlParser.builder().withCharset(configuration.getCharset()).withStrictValidation(configuration.isStrict()).build();
}
diff --git a/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/api/IssueLocationTest.java b/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/api/IssueLocationTest.java
index 6ab9038..a91e9d9 100644
--- a/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/api/IssueLocationTest.java
+++ b/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/api/IssueLocationTest.java
@@ -175,5 +175,44 @@ public void compare_precise_location_differents_objects(){
assertThat(issueLocation1.equals(issueLocation2)).isFalse();
}
+ @Test
+ public void both_null_messages_are_equal() {
+ IssueLocation loc1 = IssueLocation.atLineLevel(null, 10);
+ IssueLocation loc2 = IssueLocation.atLineLevel(null, 10);
+ assertThat(loc1.equals(loc2)).isTrue();
+ assertThat(loc1).hasSameHashCodeAs(loc2);
+ }
+
+ @Test
+ public void null_message_not_equal_to_non_null_message() {
+ IssueLocation loc1 = IssueLocation.atLineLevel(null, 10);
+ IssueLocation loc2 = IssueLocation.atLineLevel(MESSAGE, 10);
+ assertThat(loc1.equals(loc2)).isFalse();
+ assertThat(loc2.equals(loc1)).isFalse();
+ }
+
+ @Test
+ public void hashcode_with_null_message() {
+ IssueLocation loc = IssueLocation.atLineLevel(null, 5);
+ int hash = loc.hashCode();
+ assertThat(hash).isEqualTo(IssueLocation.atLineLevel(null, 5).hashCode());
+ }
+
+ @Test
+ public void precise_location_with_multiline_value() {
+ JsonNode root = parser.parse("swagger: \"2.0\"\n" +
+ "info:\n" +
+ " version: 1.0.0\n" +
+ " title: T\n" +
+ " description: |\n" +
+ " line one\n" +
+ " line two\n" +
+ "paths:\n" +
+ " /pets: {}");
+ JsonNode descNode = root.at("/info/description").value();
+ IssueLocation loc = IssueLocation.preciseLocation(MESSAGE, descNode);
+ assertThat(loc.startLine()).isGreaterThan(0);
+ assertThat(loc.endLine()).isGreaterThanOrEqualTo(loc.startLine());
+ }
}
diff --git a/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/api/OpenApiCheckTest.java b/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/api/OpenApiCheckTest.java
index bd46711..6eadf0d 100644
--- a/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/api/OpenApiCheckTest.java
+++ b/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/api/OpenApiCheckTest.java
@@ -22,13 +22,19 @@
import com.google.common.collect.Sets;
import com.sonar.sslr.api.AstNodeType;
import java.io.File;
+import java.nio.charset.StandardCharsets;
+import java.util.Collections;
import java.util.HashSet;
+import java.util.List;
import java.util.Set;
import org.junit.Test;
import org.sonar.check.Rule;
import org.apiaddicts.apitools.dosonarapi.api.v3.OpenApi3Grammar;
+import org.apiaddicts.apitools.dosonarapi.openapi.OpenApiConfiguration;
+import org.apiaddicts.apitools.dosonarapi.openapi.parser.OpenApiParser;
import org.apiaddicts.apitools.dosonarapi.sslr.yaml.grammar.JsonNode;
+import org.apiaddicts.apitools.dosonarapi.sslr.yaml.grammar.YamlParser;
import static org.assertj.core.api.Assertions.assertThat;
@@ -76,4 +82,40 @@ public void skips_rules_on_x_nosonar() {
assertThat(rule2.visitedNodes).isEmpty();
assertThat(rule3.visitedNodes).containsOnly("/paths/~1pets/get", "/paths/~1pets/get/parameters/0");
}
+
+ private static class LineIssueCheck extends OpenApiCheck {
+ @Override
+ public Set subscribedKinds() {
+ return Collections.emptySet();
+ }
+
+ @Override
+ protected void visitFile(JsonNode root) {
+ addLineIssue("line problem", 3);
+ }
+ }
+
+ private static class NoAnnotationCheck extends OpenApiCheck {}
+
+ @Test
+ public void add_line_issue_creates_issue_on_given_line() {
+ OpenApiConfiguration config = new OpenApiConfiguration(StandardCharsets.UTF_8, false);
+ YamlParser parser = OpenApiParser.createV3(config);
+ JsonNode root = parser.parse("openapi: \"3.0.0\"\ninfo:\n title: T\n version: 1.0\npaths: {}");
+ OpenApiFile file = new OpenApiFile() {
+ @Override public String content() { return ""; }
+ @Override public String fileName() { return "test.yaml"; }
+ };
+ OpenApiVisitorContext ctx = new OpenApiVisitorContext(root, parser.getIssues(), file);
+
+ LineIssueCheck check = new LineIssueCheck();
+ List issues = check.scanFileForIssues(ctx);
+ assertThat(issues).hasSize(1);
+ assertThat(issues.get(0).primaryLocation().startLine()).isEqualTo(3);
+ }
+
+ @Test
+ public void no_rule_annotation_returns_empty_rule_id() {
+ assertThat(new NoAnnotationCheck().getRuleId()).isEmpty();
+ }
}
diff --git a/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/api/PreciseIssueTest.java b/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/api/PreciseIssueTest.java
index 668c04e..720ec0a 100644
--- a/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/api/PreciseIssueTest.java
+++ b/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/api/PreciseIssueTest.java
@@ -19,22 +19,92 @@
*/
package org.apiaddicts.apitools.dosonarapi.api;
-import org.apiaddicts.apitools.dosonarapi.api.IssueLocation;
-import org.apiaddicts.apitools.dosonarapi.api.PreciseIssue;
import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatObject;
public class PreciseIssueTest {
- private static final String MESSAGE = "Test Message";
-
@Test
- public void compare_equals_objects(){
+ public void compare_equals_objects() {
PreciseIssue preciseIssue1 = new PreciseIssue(IssueLocation.atLineLevel(null, 42000)).withCost(5);
PreciseIssue preciseIssue2 = new PreciseIssue(IssueLocation.atLineLevel(null, 42000)).withCost(5);
assertThat(preciseIssue1.equals(preciseIssue2)).isTrue();
assertThat(preciseIssue1.hashCode() == preciseIssue2.hashCode()).isTrue();
}
+ @Test
+ public void not_equal_to_non_precise_issue() {
+ PreciseIssue issue = new PreciseIssue(IssueLocation.atLineLevel("msg", 1));
+ assertThatObject(issue).isNotEqualTo("not an issue");
+ assertThatObject(issue).isNotEqualTo(null);
+ }
+
+ @Test
+ public void not_equal_when_costs_differ() {
+ PreciseIssue issue1 = new PreciseIssue(IssueLocation.atLineLevel("msg", 1)).withCost(1);
+ PreciseIssue issue2 = new PreciseIssue(IssueLocation.atLineLevel("msg", 1)).withCost(2);
+ assertThat(issue1.equals(issue2)).isFalse();
+ }
+
+ @Test
+ public void not_equal_when_primary_location_differs() {
+ PreciseIssue issue1 = new PreciseIssue(IssueLocation.atLineLevel("msg", 1));
+ PreciseIssue issue2 = new PreciseIssue(IssueLocation.atLineLevel("msg", 2));
+ assertThat(issue1.equals(issue2)).isFalse();
+ }
+
+ @Test
+ public void secondary_location_via_issue_location() {
+ IssueLocation loc = IssueLocation.atLineLevel("secondary", 5);
+ PreciseIssue issue = new PreciseIssue(IssueLocation.atLineLevel("primary", 1));
+ issue.secondary(loc);
+ assertThat(issue.secondaryLocations()).hasSize(1);
+ assertThat(issue.secondaryLocations().get(0)).isEqualTo(loc);
+ }
+
+ @Test
+ public void cost_is_null_by_default() {
+ PreciseIssue issue = new PreciseIssue(IssueLocation.atLineLevel("msg", 1));
+ assertThat(issue.cost()).isNull();
+ }
+
+ @Test
+ public void hashcode_without_cost() {
+ PreciseIssue issue1 = new PreciseIssue(IssueLocation.atLineLevel("msg", 1));
+ PreciseIssue issue2 = new PreciseIssue(IssueLocation.atLineLevel("msg", 1));
+ assertThat(issue1).hasSameHashCodeAs(issue2);
+ }
+
+ @Test
+ public void equals_with_null_primary_location() {
+ PreciseIssue issue1 = new PreciseIssue(null);
+ PreciseIssue issue2 = new PreciseIssue(null);
+ assertThat(issue1.equals(issue2)).isTrue();
+ assertThat(issue1).hasSameHashCodeAs(issue2);
+ }
+
+ @Test
+ public void null_primary_location_not_equal_to_non_null() {
+ PreciseIssue issue1 = new PreciseIssue(null);
+ PreciseIssue issue2 = new PreciseIssue(IssueLocation.atLineLevel("msg", 1));
+ assertThat(issue1.equals(issue2)).isFalse();
+ assertThat(issue2.equals(issue1)).isFalse();
+ }
+
+ @Test
+ public void secondary_location_via_node() {
+ java.nio.charset.Charset utf8 = java.nio.charset.StandardCharsets.UTF_8;
+ org.apiaddicts.apitools.dosonarapi.openapi.OpenApiConfiguration config =
+ new org.apiaddicts.apitools.dosonarapi.openapi.OpenApiConfiguration(utf8, true);
+ org.apiaddicts.apitools.dosonarapi.sslr.yaml.grammar.YamlParser parser =
+ org.apiaddicts.apitools.dosonarapi.openapi.parser.OpenApiParser.createV2(config);
+ org.apiaddicts.apitools.dosonarapi.sslr.yaml.grammar.JsonNode root =
+ parser.parse("swagger: \"2.0\"\ninfo:\n version: 1.0.0\n title: T\npaths:\n /pets: {}");
+ org.apiaddicts.apitools.dosonarapi.sslr.yaml.grammar.JsonNode node = root.at("/paths/~1pets").value();
+ PreciseIssue issue = new PreciseIssue(IssueLocation.atLineLevel("primary", 1));
+ issue.secondary(node, "secondary message");
+ assertThat(issue.secondaryLocations()).hasSize(1);
+ }
}
diff --git a/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/api/ResourceCheckTest.java b/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/api/ResourceCheckTest.java
new file mode 100644
index 0000000..11bf77c
--- /dev/null
+++ b/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/api/ResourceCheckTest.java
@@ -0,0 +1,179 @@
+/*
+ * doSonarAPI: SonarQube OpenAPI Plugin
+ * Copyright (C) 2021-2022 Apiaddicts
+ * contacta AT apiaddicts DOT org
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.apiaddicts.apitools.dosonarapi.api;
+
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Test;
+import org.junit.experimental.theories.DataPoints;
+import org.junit.experimental.theories.Theories;
+import org.junit.experimental.theories.Theory;
+import org.junit.runner.RunWith;
+import org.apiaddicts.apitools.dosonarapi.openapi.OpenApiConfiguration;
+import org.apiaddicts.apitools.dosonarapi.openapi.parser.OpenApiParser;
+import org.apiaddicts.apitools.dosonarapi.sslr.yaml.grammar.JsonNode;
+import org.apiaddicts.apitools.dosonarapi.sslr.yaml.grammar.YamlParser;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@RunWith(Theories.class)
+public class ResourceCheckTest {
+
+ private static class RecordingResourceCheck extends ResourceCheck {
+ final List visitedPaths = new ArrayList<>();
+
+ @Override
+ protected void visitResource(JsonNode node) {
+ visitedPaths.add(node.key().getTokenValue());
+ }
+ }
+
+ private static OpenApiVisitorContext createV2Context(String yaml) {
+ OpenApiConfiguration config = new OpenApiConfiguration(StandardCharsets.UTF_8, true);
+ YamlParser parser = OpenApiParser.createV2(config);
+ JsonNode root = parser.parse(yaml);
+ OpenApiFile file = new OpenApiFile() {
+ @Override public String content() { return yaml; }
+ @Override public String fileName() { return "test.yaml"; }
+ };
+ return new OpenApiVisitorContext(root, parser.getIssues(), file);
+ }
+
+ @DataPoints
+ public static String[] yamlsWithNoResourcePaths() {
+ return new String[] {
+ "swagger: \"2.0\"\n" +
+ "info:\n" +
+ " version: 1.0.0\n" +
+ " title: Test\n" +
+ "paths:\n" +
+ " /pets/{petId}:\n" +
+ " get:\n" +
+ " responses:\n" +
+ " '200':\n" +
+ " description: ok\n",
+
+ "swagger: \"2.0\"\n" +
+ "info:\n" +
+ " version: 1.0.0\n" +
+ " title: Test\n" +
+ "paths:\n" +
+ " /pets/:\n" +
+ " get:\n" +
+ " responses:\n" +
+ " '200':\n" +
+ " description: ok\n",
+
+ "swagger: \"2.0\"\n" +
+ "info:\n" +
+ " version: 1.0.0\n" +
+ " title: Test\n" +
+ "paths:\n" +
+ " /{entity}:\n" +
+ " get:\n" +
+ " responses:\n" +
+ " '200':\n" +
+ " description: ok\n"
+ };
+ }
+
+ @Theory
+ public void does_not_visit_non_resource_paths(String yaml) {
+ RecordingResourceCheck check = new RecordingResourceCheck();
+ check.scanFileForIssues(createV2Context(yaml));
+ assertThat(check.visitedPaths).isEmpty();
+ }
+
+ @Test
+ public void visits_resource_paths_only() {
+ String yaml =
+ "swagger: \"2.0\"\n" +
+ "info:\n" +
+ " version: 1.0.0\n" +
+ " title: Test\n" +
+ "paths:\n" +
+ " /pets:\n" +
+ " get:\n" +
+ " responses:\n" +
+ " '200':\n" +
+ " description: ok\n" +
+ " /pets/{petId}:\n" +
+ " get:\n" +
+ " responses:\n" +
+ " '200':\n" +
+ " description: ok\n";
+
+ RecordingResourceCheck check = new RecordingResourceCheck();
+ check.scanFileForIssues(createV2Context(yaml));
+
+ assertThat(check.visitedPaths).contains("/pets").doesNotContain("/pets/{petId}");
+ }
+
+ @Test
+ public void visits_sub_resource_paths() {
+ String yaml =
+ "swagger: \"2.0\"\n" +
+ "info:\n" +
+ " version: 1.0.0\n" +
+ " title: Test\n" +
+ "paths:\n" +
+ " /pets/{petId}/tags:\n" +
+ " get:\n" +
+ " responses:\n" +
+ " '200':\n" +
+ " description: ok\n" +
+ " /pets/{petId}/tags/{tagId}:\n" +
+ " get:\n" +
+ " responses:\n" +
+ " '200':\n" +
+ " description: ok\n";
+
+ RecordingResourceCheck check = new RecordingResourceCheck();
+ check.scanFileForIssues(createV2Context(yaml));
+
+ assertThat(check.visitedPaths).contains("/pets/{petId}/tags");
+ }
+
+ @Test
+ public void path_is_last_without_following_variable_child_is_not_resource() {
+ String yaml =
+ "swagger: \"2.0\"\n" +
+ "info:\n" +
+ " version: 1.0.0\n" +
+ " title: Test\n" +
+ "paths:\n" +
+ " /pets:\n" +
+ " get:\n" +
+ " responses:\n" +
+ " '200':\n" +
+ " description: ok\n" +
+ " /other:\n" +
+ " get:\n" +
+ " responses:\n" +
+ " '200':\n" +
+ " description: ok\n";
+
+ RecordingResourceCheck check = new RecordingResourceCheck();
+ check.scanFileForIssues(createV2Context(yaml));
+
+ assertThat(check.visitedPaths).doesNotContain("/pets", "/other");
+ }
+}
diff --git a/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/api/TestOpenApiVisitorRunnerTest.java b/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/api/TestOpenApiVisitorRunnerTest.java
new file mode 100644
index 0000000..16f3a00
--- /dev/null
+++ b/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/api/TestOpenApiVisitorRunnerTest.java
@@ -0,0 +1,86 @@
+/*
+ * doSonarAPI: SonarQube OpenAPI Plugin
+ * Copyright (C) 2021-2022 Apiaddicts
+ * contacta AT apiaddicts DOT org
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.apiaddicts.apitools.dosonarapi.api;
+
+import java.io.File;
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatCode;
+
+public class TestOpenApiVisitorRunnerTest {
+
+ private static final File V3_FILE = new File(
+ TestOpenApiVisitorRunnerTest.class.getResource("/petstore.yaml").getFile());
+ private static final File V2_FILE = new File(
+ TestOpenApiVisitorRunnerTest.class.getResource("/models/v2/pet-store.yaml").getFile());
+ private static final File V31_FILE = new File(
+ TestOpenApiVisitorRunnerTest.class.getResource("/petstore-v31.yaml").getFile());
+ private static final File V32_FILE = new File(
+ TestOpenApiVisitorRunnerTest.class.getResource("/petstore-v32.yaml").getFile());
+
+ @Test
+ public void create_context_with_default_args_uses_v3_parser() {
+ OpenApiVisitorContext ctx = TestOpenApiVisitorRunner.createContext(V3_FILE);
+ assertThat(ctx.rootTree()).isNotNull();
+ }
+
+ @Test
+ public void create_context_with_v2_flag_uses_v2_parser() {
+ OpenApiVisitorContext ctx = TestOpenApiVisitorRunner.createContext(V2_FILE, true);
+ assertThat(ctx.rootTree()).isNotNull();
+ }
+
+ @Test
+ public void create_context_with_three_flags_false_uses_v3_parser() {
+ OpenApiVisitorContext ctx = TestOpenApiVisitorRunner.createContext(V3_FILE, false, false, false);
+ assertThat(ctx.rootTree()).isNotNull();
+ }
+
+ @Test
+ public void create_context_with_v31_flag_uses_v31_parser() {
+ OpenApiVisitorContext ctx = TestOpenApiVisitorRunner.createContext(V31_FILE, false, false, true);
+ assertThat(ctx.rootTree()).isNotNull();
+ }
+
+ @Test
+ public void create_context_with_v32_flag_uses_v32_parser() {
+ OpenApiVisitorContext ctx = TestOpenApiVisitorRunner.createContext(V32_FILE, false, false, false, true);
+ assertThat(ctx.rootTree()).isNotNull();
+ }
+
+ @Test
+ public void scan_file_for_comments_with_three_flags() {
+ assertThatCode(() -> TestOpenApiVisitorRunner.scanFileForComments(V3_FILE, false, false, false))
+ .doesNotThrowAnyException();
+ }
+
+ @Test
+ public void scan_file_for_comments_with_v32_flag() {
+ assertThatCode(() -> TestOpenApiVisitorRunner.scanFileForComments(V32_FILE, false, false, false, true))
+ .doesNotThrowAnyException();
+ }
+
+ @Test
+ public void scan_file_with_visitors() {
+ assertThatCode(() -> TestOpenApiVisitorRunner.scanFile(V3_FILE, new OpenApiVisitor()))
+ .doesNotThrowAnyException();
+ }
+}
diff --git a/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/api/v31/InfoTest.java b/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/api/v31/InfoTest.java
new file mode 100644
index 0000000..356dfec
--- /dev/null
+++ b/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/api/v31/InfoTest.java
@@ -0,0 +1,47 @@
+/*
+ * doSonarAPI: SonarQube OpenAPI Plugin
+ * Copyright (C) 2021-2022 Apiaddicts
+ * contacta AT apiaddicts DOT org
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.apiaddicts.apitools.dosonarapi.api.v31;
+
+import org.apiaddicts.apitools.dosonarapi.openapi.BaseNodeTest;
+import org.junit.Test;
+import org.apiaddicts.apitools.dosonarapi.sslr.yaml.grammar.JsonNode;
+
+
+public class InfoTest extends BaseNodeTest {
+
+ @Test
+ public void can_parse_info_with_summary() {
+ JsonNode node = parseResource(OpenApi31Grammar.INFO, "/models/v31/info_with_summary.yaml");
+
+ assertEquals("My API", node, "/title");
+ assertEquals("A short summary of the API", node, "/summary");
+ assertEquals("1.0.0", node, "/version");
+ assertMissing(node.at("/description"));
+ }
+
+ @Test
+ public void can_parse_info_without_summary() {
+ JsonNode node = parseResource(OpenApi31Grammar.INFO, "/models/shared/info/minimal.yaml");
+
+ assertMissing(node.at("/summary"));
+ assertEquals("simple model", node, "/title");
+ assertEquals("1.0.0", node, "/version");
+ }
+}
diff --git a/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/api/v32/JsonSchemaTest.java b/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/api/v32/JsonSchemaTest.java
new file mode 100644
index 0000000..f29166a
--- /dev/null
+++ b/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/api/v32/JsonSchemaTest.java
@@ -0,0 +1,69 @@
+/*
+ * doSonarAPI: SonarQube OpenAPI Plugin
+ * Copyright (C) 2021-2022 Apiaddicts
+ * contacta AT apiaddicts DOT org
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.apiaddicts.apitools.dosonarapi.api.v32;
+
+import org.apiaddicts.apitools.dosonarapi.openapi.BaseNodeTest;
+import org.junit.Test;
+import org.apiaddicts.apitools.dosonarapi.sslr.yaml.grammar.JsonNode;
+
+public class JsonSchemaTest extends BaseNodeTest {
+
+ @Test
+ public void can_parse_if_then_else() {
+ JsonNode node = parseResource(OpenApi32Grammar.SCHEMA, "/models/v32/jsonschema.yaml");
+
+ assertPropertyKeys(node).contains("if", "then", "else");
+ }
+
+ @Test
+ public void can_parse_prefix_items_and_contains() {
+ JsonNode node = parseResource(OpenApi32Grammar.SCHEMA, "/models/v32/jsonschema.yaml");
+
+ assertPropertyKeys(node).contains("prefixItems", "contains", "minContains", "maxContains");
+ }
+
+ @Test
+ public void can_parse_property_names_and_pattern_properties() {
+ JsonNode node = parseResource(OpenApi32Grammar.SCHEMA, "/models/v32/jsonschema.yaml");
+
+ assertPropertyKeys(node).contains("propertyNames", "patternProperties");
+ }
+
+ @Test
+ public void can_parse_dependent_required() {
+ JsonNode node = parseResource(OpenApi32Grammar.SCHEMA, "/models/v32/jsonschema.yaml");
+
+ assertPropertyKeys(node).contains("dependentRequired");
+ }
+
+ @Test
+ public void can_parse_defs_and_anchor() {
+ JsonNode node = parseResource(OpenApi32Grammar.SCHEMA, "/models/v32/jsonschema.yaml");
+
+ assertPropertyKeys(node).contains("$defs", "$anchor", "$comment");
+ }
+
+ @Test
+ public void can_parse_unevaluated_keywords() {
+ JsonNode node = parseResource(OpenApi32Grammar.SCHEMA, "/models/v32/jsonschema.yaml");
+
+ assertPropertyKeys(node).contains("unevaluatedItems", "unevaluatedProperties");
+ }
+}
diff --git a/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/api/v32/SimpleTest.java b/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/api/v32/SimpleTest.java
new file mode 100644
index 0000000..2f903b7
--- /dev/null
+++ b/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/api/v32/SimpleTest.java
@@ -0,0 +1,63 @@
+/*
+ * doSonarAPI: SonarQube OpenAPI Plugin
+ * Copyright (C) 2021-2022 Apiaddicts
+ * contacta AT apiaddicts DOT org
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.apiaddicts.apitools.dosonarapi.api.v32;
+
+import org.apiaddicts.apitools.dosonarapi.openapi.BaseNodeTest;
+import org.junit.Test;
+import org.apiaddicts.apitools.dosonarapi.sslr.yaml.grammar.JsonNode;
+
+public class SimpleTest extends BaseNodeTest {
+
+ @Test
+ public void can_parse_paths() {
+ JsonNode node = parseResource(OpenApi32Grammar.PATHS, "/models/v32/paths.yaml");
+
+ assertPropertyKeys(node).containsOnly("/pets");
+ }
+
+ @Test
+ public void path_supports_query_method() {
+ JsonNode node = parseResource(OpenApi32Grammar.PATH, "/models/v32/path-item.yaml");
+
+ assertPropertyKeys(node).contains("get", "query");
+ }
+
+ @Test
+ public void can_parse_pathitems_component() {
+ JsonNode node = parseResource(OpenApi32Grammar.PATH_ITEMS_COMPONENT, "/models/v32/pathitems-component.yaml");
+
+ assertPropertyKeys(node).containsOnly("PetItem");
+ }
+
+ @Test
+ public void can_parse_mediatypes_component() {
+ JsonNode node = parseResource(OpenApi32Grammar.MEDIA_TYPES_COMPONENT, "/models/v32/mediatypes-component.yaml");
+
+ assertPropertyKeys(node).containsOnly("JsonPet");
+ }
+
+ @Test
+ public void server_supports_name_field() {
+ JsonNode node = parseResource(OpenApi32Grammar.SERVER, "/models/v32/server-with-name.yaml");
+
+ assertEquals("production", node, "/name");
+ assertEquals("https://api.example.com/v1", node, "/url");
+ }
+}
diff --git a/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/api/v32/WebhooksOnlyTest.java b/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/api/v32/WebhooksOnlyTest.java
new file mode 100644
index 0000000..3f0fb5d
--- /dev/null
+++ b/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/api/v32/WebhooksOnlyTest.java
@@ -0,0 +1,41 @@
+/*
+ * doSonarAPI: SonarQube OpenAPI Plugin
+ * Copyright (C) 2021-2022 Apiaddicts
+ * contacta AT apiaddicts DOT org
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.apiaddicts.apitools.dosonarapi.api.v32;
+
+import org.apiaddicts.apitools.dosonarapi.openapi.BaseNodeTest;
+import org.junit.Test;
+import org.apiaddicts.apitools.dosonarapi.sslr.yaml.grammar.JsonNode;
+
+public class WebhooksOnlyTest extends BaseNodeTest {
+
+ @Test
+ public void can_parse_root_without_paths() {
+ JsonNode node = parseResource(OpenApi32Grammar.ROOT, "/models/v32/webhooks-only.yaml");
+
+ assertPropertyKeys(node).contains("openapi", "info", "webhooks", "jsonSchemaDialect");
+ }
+
+ @Test
+ public void webhook_has_expected_name() {
+ JsonNode node = parseResource(OpenApi32Grammar.WEBHOOKS, "/models/v32/webhooks.yaml");
+
+ assertPropertyKeys(node).containsOnly("newBooking");
+ }
+}
diff --git a/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/openapi/OpenApiConfigurationTest.java b/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/openapi/OpenApiConfigurationTest.java
new file mode 100644
index 0000000..591d5ac
--- /dev/null
+++ b/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/openapi/OpenApiConfigurationTest.java
@@ -0,0 +1,42 @@
+/*
+ * doSonarAPI: SonarQube OpenAPI Plugin
+ * Copyright (C) 2021-2022 Apiaddicts
+ * contacta AT apiaddicts DOT org
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.apiaddicts.apitools.dosonarapi.openapi;
+
+import java.nio.charset.StandardCharsets;
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class OpenApiConfigurationTest {
+
+ @Test
+ public void stores_charset_and_strict_flag() {
+ OpenApiConfiguration config = new OpenApiConfiguration(StandardCharsets.UTF_8, true);
+ assertThat(config.getCharset()).isEqualTo(StandardCharsets.UTF_8);
+ assertThat(config.isStrict()).isTrue();
+ }
+
+ @Test
+ public void strict_false() {
+ OpenApiConfiguration config = new OpenApiConfiguration(StandardCharsets.ISO_8859_1, false);
+ assertThat(config.getCharset()).isEqualTo(StandardCharsets.ISO_8859_1);
+ assertThat(config.isStrict()).isFalse();
+ }
+}
diff --git a/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/openapi/metrics/FileMetricsTest.java b/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/openapi/metrics/FileMetricsTest.java
index 463dc37..36025af 100644
--- a/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/openapi/metrics/FileMetricsTest.java
+++ b/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/openapi/metrics/FileMetricsTest.java
@@ -19,10 +19,12 @@
*/
package org.apiaddicts.apitools.dosonarapi.openapi.metrics;
+import com.sonar.sslr.api.RecognitionException;
import java.io.File;
-import org.apiaddicts.apitools.dosonarapi.openapi.metrics.FileMetrics;
+import org.apiaddicts.apitools.dosonarapi.api.OpenApiFile;
import org.junit.Test;
+import org.apiaddicts.apitools.dosonarapi.api.OpenApiVisitorContext;
import org.apiaddicts.apitools.dosonarapi.api.TestOpenApiVisitorRunner;
import static org.assertj.core.api.Assertions.assertThat;
@@ -49,6 +51,21 @@ public void complexity() {
assertThat(metrics("complexity.yaml").complexity()).isEqualTo(7);
}
+ @Test
+ public void null_root_tree_yields_zero_counts() {
+ OpenApiFile file = new OpenApiFile() {
+ @Override public String content() { return ""; }
+ @Override public String fileName() { return "dummy.yaml"; }
+ };
+ OpenApiVisitorContext context = new OpenApiVisitorContext(file, new RecognitionException(0, "parse error"));
+ FileMetrics fileMetrics = new FileMetrics(context);
+
+ assertThat(fileMetrics.numberOfOperations()).isZero();
+ assertThat(fileMetrics.numberOfPaths()).isZero();
+ assertThat(fileMetrics.numberOfSchemas()).isZero();
+ assertThat(fileMetrics.complexity()).isZero();
+ }
+
private FileMetrics metrics(String fileName) {
File baseDir = new File("src/test/resources/metrics/");
File file = new File(baseDir, fileName);
diff --git a/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/openapi/metrics/OpenApiMetricsTest.java b/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/openapi/metrics/OpenApiMetricsTest.java
new file mode 100644
index 0000000..dab744e
--- /dev/null
+++ b/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/openapi/metrics/OpenApiMetricsTest.java
@@ -0,0 +1,54 @@
+/*
+ * doSonarAPI: SonarQube OpenAPI Plugin
+ * Copyright (C) 2021-2022 Apiaddicts
+ * contacta AT apiaddicts DOT org
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.apiaddicts.apitools.dosonarapi.openapi.metrics;
+
+import java.util.List;
+import org.junit.Test;
+import org.sonar.api.measures.Metric;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class OpenApiMetricsTest {
+
+ @Test
+ public void provides_three_metrics() {
+ OpenApiMetrics openApiMetrics = new OpenApiMetrics();
+ List metrics = openApiMetrics.getMetrics();
+ assertThat(metrics).hasSize(3).contains(OpenApiMetrics.OPERATIONS_COUNT, OpenApiMetrics.PATHS_COUNT, OpenApiMetrics.SCHEMAS_COUNT);
+ }
+
+ @Test
+ public void operations_count_metric_has_correct_key() {
+ assertThat(OpenApiMetrics.OPERATIONS_COUNT.getKey()).isEqualTo("operations_count");
+ assertThat(OpenApiMetrics.OPERATIONS_COUNT.getName()).isEqualTo("Operations Count");
+ }
+
+ @Test
+ public void paths_count_metric_has_correct_key() {
+ assertThat(OpenApiMetrics.PATHS_COUNT.getKey()).isEqualTo("paths_count");
+ assertThat(OpenApiMetrics.PATHS_COUNT.getName()).isEqualTo("Paths Count");
+ }
+
+ @Test
+ public void schemas_count_metric_has_correct_key() {
+ assertThat(OpenApiMetrics.SCHEMAS_COUNT.getKey()).isEqualTo("schemas_count");
+ assertThat(OpenApiMetrics.SCHEMAS_COUNT.getName()).isEqualTo("Schemas Count");
+ }
+}
diff --git a/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/openapi/parser/OpenApiParserTest.java b/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/openapi/parser/OpenApiParserTest.java
new file mode 100644
index 0000000..643f3b3
--- /dev/null
+++ b/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/openapi/parser/OpenApiParserTest.java
@@ -0,0 +1,96 @@
+/*
+ * doSonarAPI: SonarQube OpenAPI Plugin
+ * Copyright (C) 2021-2022 Apiaddicts
+ * contacta AT apiaddicts DOT org
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.apiaddicts.apitools.dosonarapi.openapi.parser;
+
+import java.nio.charset.StandardCharsets;
+import org.junit.Test;
+import org.apiaddicts.apitools.dosonarapi.openapi.OpenApiConfiguration;
+import org.apiaddicts.apitools.dosonarapi.sslr.yaml.grammar.JsonNode;
+import org.apiaddicts.apitools.dosonarapi.sslr.yaml.grammar.YamlParser;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class OpenApiParserTest {
+
+ private static final OpenApiConfiguration CONFIG = new OpenApiConfiguration(StandardCharsets.UTF_8, false);
+
+ private static final String MINIMAL_V2 =
+ "swagger: \"2.0\"\n" +
+ "info:\n" +
+ " title: Test\n" +
+ " version: 1.0.0\n" +
+ "paths: {}";
+
+ private static final String MINIMAL_V3 =
+ "openapi: \"3.0.0\"\n" +
+ "info:\n" +
+ " title: Test\n" +
+ " version: 1.0.0\n" +
+ "paths: {}";
+
+ @Test
+ public void create_v2_parser_parses_swagger_doc() {
+ YamlParser parser = OpenApiParser.createV2(CONFIG);
+ assertThat(parser).isNotNull();
+ JsonNode root = parser.parse(MINIMAL_V2);
+ assertThat(root).isNotNull();
+ assertThat(root.at("/swagger").getTokenValue()).isEqualTo("2.0");
+ }
+
+ @Test
+ public void create_v3_parser_parses_openapi_doc() {
+ YamlParser parser = OpenApiParser.createV3(CONFIG);
+ assertThat(parser).isNotNull();
+ JsonNode root = parser.parse(MINIMAL_V3);
+ assertThat(root).isNotNull();
+ assertThat(root.at("/openapi").getTokenValue()).isEqualTo("3.0.0");
+ }
+
+ @Test
+ public void create_v31_parser_returns_non_null() {
+ YamlParser parser = OpenApiParser.createV31(CONFIG);
+ assertThat(parser).isNotNull();
+ JsonNode root = parser.parse(MINIMAL_V3);
+ assertThat(root).isNotNull();
+ }
+
+ @Test
+ public void create_v32_parser_returns_non_null() {
+ YamlParser parser = OpenApiParser.createV32(CONFIG);
+ assertThat(parser).isNotNull();
+ JsonNode root = parser.parse(MINIMAL_V3);
+ assertThat(root).isNotNull();
+ }
+
+ @Test
+ public void create_generic_parser_returns_non_null() {
+ YamlParser parser = OpenApiParser.createGeneric(CONFIG);
+ assertThat(parser).isNotNull();
+ JsonNode root = parser.parse("key: value");
+ assertThat(root).isNotNull();
+ }
+
+ @Test
+ public void create_generic_with_non_strict_config() {
+ OpenApiConfiguration nonStrict = new OpenApiConfiguration(StandardCharsets.UTF_8, false);
+ YamlParser parser = OpenApiParser.createGeneric(nonStrict);
+ assertThat(parser).isNotNull();
+ }
+}
diff --git a/openapi-front-end/src/test/resources/models/v31/info_with_summary.yaml b/openapi-front-end/src/test/resources/models/v31/info_with_summary.yaml
new file mode 100644
index 0000000..194cf4d
--- /dev/null
+++ b/openapi-front-end/src/test/resources/models/v31/info_with_summary.yaml
@@ -0,0 +1,3 @@
+title: My API
+summary: A short summary of the API
+version: 1.0.0
diff --git a/openapi-front-end/src/test/resources/models/v32/jsonschema.yaml b/openapi-front-end/src/test/resources/models/v32/jsonschema.yaml
new file mode 100644
index 0000000..cf2b130
--- /dev/null
+++ b/openapi-front-end/src/test/resources/models/v32/jsonschema.yaml
@@ -0,0 +1,38 @@
+type: object
+if:
+ properties:
+ type:
+ const: cat
+then:
+ properties:
+ indoor:
+ type: boolean
+else:
+ properties:
+ outdoor:
+ type: boolean
+prefixItems:
+ - type: integer
+ - type: string
+contains:
+ type: string
+minContains: 1
+maxContains: 5
+propertyNames:
+ pattern: "^[A-Za-z_][A-Za-z0-9_]*$"
+dependentRequired:
+ creditCard:
+ - billingAddress
+$defs:
+ address:
+ type: object
+ properties:
+ street:
+ type: string
+$anchor: "mySchema"
+$comment: "An example JSON Schema 2020-12 schema"
+unevaluatedItems: false
+unevaluatedProperties: false
+patternProperties:
+ "^S_":
+ type: string
diff --git a/openapi-front-end/src/test/resources/models/v32/mediatypes-component.yaml b/openapi-front-end/src/test/resources/models/v32/mediatypes-component.yaml
new file mode 100644
index 0000000..36f8b53
--- /dev/null
+++ b/openapi-front-end/src/test/resources/models/v32/mediatypes-component.yaml
@@ -0,0 +1,8 @@
+JsonPet:
+ schema:
+ type: object
+ properties:
+ id:
+ type: integer
+ name:
+ type: string
diff --git a/openapi-front-end/src/test/resources/models/v32/path-item.yaml b/openapi-front-end/src/test/resources/models/v32/path-item.yaml
new file mode 100644
index 0000000..179953b
--- /dev/null
+++ b/openapi-front-end/src/test/resources/models/v32/path-item.yaml
@@ -0,0 +1,11 @@
+get:
+ summary: List all pets
+ operationId: listPets
+ responses:
+ '200':
+ description: A list of pets
+query:
+ summary: Search pets via QUERY method
+ responses:
+ '200':
+ description: Matching pets
diff --git a/openapi-front-end/src/test/resources/models/v32/pathitems-component.yaml b/openapi-front-end/src/test/resources/models/v32/pathitems-component.yaml
new file mode 100644
index 0000000..cb13ce6
--- /dev/null
+++ b/openapi-front-end/src/test/resources/models/v32/pathitems-component.yaml
@@ -0,0 +1,6 @@
+PetItem:
+ get:
+ summary: Get a pet
+ responses:
+ '200':
+ description: A pet
diff --git a/openapi-front-end/src/test/resources/models/v32/paths.yaml b/openapi-front-end/src/test/resources/models/v32/paths.yaml
new file mode 100644
index 0000000..a1d9e0e
--- /dev/null
+++ b/openapi-front-end/src/test/resources/models/v32/paths.yaml
@@ -0,0 +1,13 @@
+/pets:
+ get:
+ summary: List all pets
+ operationId: listPets
+ responses:
+ '200':
+ description: A list of pets
+ query:
+ summary: Search pets via QUERY method
+ operationId: searchPets
+ responses:
+ '200':
+ description: Matching pets
diff --git a/openapi-front-end/src/test/resources/models/v32/server-with-name.yaml b/openapi-front-end/src/test/resources/models/v32/server-with-name.yaml
new file mode 100644
index 0000000..b5f8e76
--- /dev/null
+++ b/openapi-front-end/src/test/resources/models/v32/server-with-name.yaml
@@ -0,0 +1,10 @@
+url: "https://api.example.com/v1"
+name: production
+description: Production server
+variables:
+ port:
+ default: "443"
+ enum:
+ - "443"
+ - "8443"
+ description: Server port
diff --git a/openapi-front-end/src/test/resources/models/v32/simple.yaml b/openapi-front-end/src/test/resources/models/v32/simple.yaml
new file mode 100644
index 0000000..1dd9fbd
--- /dev/null
+++ b/openapi-front-end/src/test/resources/models/v32/simple.yaml
@@ -0,0 +1,29 @@
+openapi: "3.2.0"
+info:
+ title: Simple API
+ version: "1.0.0"
+paths:
+ /pets:
+ get:
+ summary: List all pets
+ operationId: listPets
+ responses:
+ '200':
+ description: A list of pets
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ type: object
+ properties:
+ id:
+ type: integer
+ name:
+ type: string
+ query:
+ summary: Search pets via QUERY method
+ operationId: searchPets
+ responses:
+ '200':
+ description: Matching pets
diff --git a/openapi-front-end/src/test/resources/models/v32/webhooks-only.yaml b/openapi-front-end/src/test/resources/models/v32/webhooks-only.yaml
new file mode 100644
index 0000000..f232fba
--- /dev/null
+++ b/openapi-front-end/src/test/resources/models/v32/webhooks-only.yaml
@@ -0,0 +1,20 @@
+openapi: "3.2.0"
+info:
+ title: Webhooks-only API
+ summary: An API that only exposes webhooks
+ version: "1.0.0"
+jsonSchemaDialect: "https://json-schema.org/draft/2020-12/schema"
+webhooks:
+ newBooking:
+ post:
+ requestBody:
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ id:
+ type: string
+ responses:
+ '200':
+ description: Webhook received
diff --git a/openapi-front-end/src/test/resources/models/v32/webhooks.yaml b/openapi-front-end/src/test/resources/models/v32/webhooks.yaml
new file mode 100644
index 0000000..43d558a
--- /dev/null
+++ b/openapi-front-end/src/test/resources/models/v32/webhooks.yaml
@@ -0,0 +1,14 @@
+newBooking:
+ post:
+ summary: New Booking webhook
+ requestBody:
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ id:
+ type: string
+ responses:
+ '200':
+ description: Webhook received successfully
diff --git a/openapi-front-end/src/test/resources/petstore-v31.yaml b/openapi-front-end/src/test/resources/petstore-v31.yaml
new file mode 100644
index 0000000..d7ee194
--- /dev/null
+++ b/openapi-front-end/src/test/resources/petstore-v31.yaml
@@ -0,0 +1,10 @@
+openapi: "3.1.0"
+info:
+ version: 1.0.0
+ title: Petstore V31
+paths:
+ /pets:
+ get:
+ responses:
+ '200':
+ description: ok
diff --git a/openapi-front-end/src/test/resources/petstore-v32.yaml b/openapi-front-end/src/test/resources/petstore-v32.yaml
new file mode 100644
index 0000000..dc04299
--- /dev/null
+++ b/openapi-front-end/src/test/resources/petstore-v32.yaml
@@ -0,0 +1,10 @@
+openapi: "3.2.0"
+info:
+ version: 1.0.0
+ title: Petstore V32
+paths:
+ /pets:
+ get:
+ responses:
+ '200':
+ description: ok
diff --git a/openapi-test-tools/pom.xml b/openapi-test-tools/pom.xml
index 0774d45..cf684f6 100644
--- a/openapi-test-tools/pom.xml
+++ b/openapi-test-tools/pom.xml
@@ -5,7 +5,7 @@
org.apiaddicts.apitools.dosonarapi
dosonarapi
- 1.1.2
+ 1.2.1
../pom.xml
@@ -19,6 +19,11 @@
openapi-front-end
${project.version}
+
+ org.sonarsource.sonarqube
+ sonar-plugin-api
+ test
+
junit
junit
diff --git a/openapi-test-tools/src/test/java/org/apiaddicts/apitools/dosonarapi/OpenApiCheckVerifierTest.java b/openapi-test-tools/src/test/java/org/apiaddicts/apitools/dosonarapi/OpenApiCheckVerifierTest.java
index 6ff6377..0ce113f 100644
--- a/openapi-test-tools/src/test/java/org/apiaddicts/apitools/dosonarapi/OpenApiCheckVerifierTest.java
+++ b/openapi-test-tools/src/test/java/org/apiaddicts/apitools/dosonarapi/OpenApiCheckVerifierTest.java
@@ -28,6 +28,7 @@
import static com.sonar.sslr.api.GenericTokenType.COMMENT;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
public class OpenApiCheckVerifierTest {
@@ -76,6 +77,122 @@ public void adjusts_issue_column_based_on_issue_next_line() {
assertThat(issue.endColumn()).isEqualTo(6);
}
+ @Test
+ public void noncompliant_without_line_offset_uses_comment_line() {
+ Trivia trivia = makeComment("Noncompliant {{message}}", COMMENT_LINE, COMMENT_COLUMN);
+ OpenApiCheckVerifier verifier = new OpenApiCheckVerifier();
+ verifier.collectExpectedIssue(trivia);
+
+ assertThat(verifier.getCollectedIssues()).hasSize(1);
+ assertThat(verifier.getCollectedIssues().get(0).line()).isEqualTo(COMMENT_LINE);
+ assertThat(verifier.getCollectedIssues().get(0).message()).isEqualTo("message");
+ }
+
+ @Test
+ public void noncompliant_with_absolute_line_number() {
+ Trivia trivia = makeComment("Noncompliant @10", COMMENT_LINE, COMMENT_COLUMN);
+ OpenApiCheckVerifier verifier = new OpenApiCheckVerifier();
+ verifier.collectExpectedIssue(trivia);
+
+ assertThat(verifier.getCollectedIssues()).hasSize(1);
+ assertThat(verifier.getCollectedIssues().get(0).line()).isEqualTo(10);
+ }
+
+ @Test
+ public void noncompliant_with_negative_line_offset() {
+ Trivia trivia = makeComment("Noncompliant @-1", COMMENT_LINE, COMMENT_COLUMN);
+ OpenApiCheckVerifier verifier = new OpenApiCheckVerifier();
+ verifier.collectExpectedIssue(trivia);
+
+ assertThat(verifier.getCollectedIssues()).hasSize(1);
+ assertThat(verifier.getCollectedIssues().get(0).line()).isEqualTo(COMMENT_LINE - 1);
+ }
+
+ @Test
+ public void empty_secondary_lines_list() {
+ Trivia trivia = makeComment("Noncompliant [[secondary=]]", COMMENT_LINE, COMMENT_COLUMN);
+ OpenApiCheckVerifier verifier = new OpenApiCheckVerifier();
+ verifier.collectExpectedIssue(trivia);
+
+ assertThat(verifier.getCollectedIssues()).hasSize(1);
+ assertThat(verifier.getCollectedIssues().get(0).secondaryLines()).isEmpty();
+ }
+
+ @Test
+ public void throws_on_invalid_param_without_equals() {
+ OpenApiCheckVerifier verifier = new OpenApiCheckVerifier();
+ Trivia invalidParam = makeComment("Noncompliant [[invalidparam]]", COMMENT_LINE, COMMENT_COLUMN);
+ assertThatThrownBy(() -> verifier.collectExpectedIssue(invalidParam))
+ .isInstanceOf(IllegalStateException.class)
+ .hasMessageContaining("Invalid param");
+ }
+
+ @Test
+ public void throws_on_unknown_param_name() {
+ OpenApiCheckVerifier verifier = new OpenApiCheckVerifier();
+ Trivia unknownParam = makeComment("Noncompliant [[unknownParam=5]]", COMMENT_LINE, COMMENT_COLUMN);
+ assertThatThrownBy(() -> verifier.collectExpectedIssue(unknownParam))
+ .isInstanceOf(IllegalStateException.class)
+ .hasMessageContaining("Invalid param");
+ }
+
+ @Test
+ public void precise_location_throws_when_no_preceding_issue() {
+ OpenApiCheckVerifier verifier = new OpenApiCheckVerifier();
+ Trivia preciseLocation = makeComment("^^^", COMMENT_LINE, 0);
+ assertThatThrownBy(() -> verifier.collectExpectedIssue(preciseLocation))
+ .isInstanceOf(IllegalStateException.class)
+ .hasMessageContaining("Invalid test file");
+ }
+
+ @Test
+ public void precise_location_throws_when_not_on_next_line() {
+ OpenApiCheckVerifier verifier = new OpenApiCheckVerifier();
+ verifier.collectExpectedIssue(makeComment("Noncompliant", COMMENT_LINE, COMMENT_COLUMN));
+ Trivia notNextLine = makeComment("^^^", COMMENT_LINE + 3, 0);
+ assertThatThrownBy(() -> verifier.collectExpectedIssue(notNextLine))
+ .isInstanceOf(IllegalStateException.class)
+ .hasMessageContaining("Invalid test file");
+ }
+
+ @Test
+ public void precise_location_throws_when_column_not_zero() {
+ OpenApiCheckVerifier verifier = new OpenApiCheckVerifier();
+ verifier.collectExpectedIssue(makeComment("Noncompliant", COMMENT_LINE, COMMENT_COLUMN));
+ Trivia wrongColumn = makeComment("^^^", COMMENT_LINE + 1, 2);
+ assertThatThrownBy(() -> verifier.collectExpectedIssue(wrongColumn))
+ .isInstanceOf(IllegalStateException.class)
+ .hasMessageContaining("column 1");
+ }
+
+ @Test
+ public void verify_passes_when_no_issues_expected_and_none_reported() {
+ OpenApiCheckVerifier.verify("src/test/resources/test-verify.yaml", new TestNoOpCheck(), true, false, false);
+ }
+
+ @Test
+ public void verify_passes_when_issue_matches_annotation() {
+ OpenApiCheckVerifier.verify("src/test/resources/test-verify-noncompliant.yaml", new TestPathsReportCheck(), true, false, false);
+ }
+
+ @Test
+ public void verify_fails_when_expected_issue_not_reported() {
+ TestNoOpCheck noOpCheck = new TestNoOpCheck();
+ assertThatThrownBy(() ->
+ OpenApiCheckVerifier.verify("src/test/resources/test-verify-noncompliant.yaml", noOpCheck, true, false, false))
+ .isInstanceOf(AssertionError.class)
+ .hasMessageContaining("Missing issue");
+ }
+
+ @Test
+ public void verify_fails_when_unexpected_issue_reported() {
+ TestPathsReportCheck pathsCheck = new TestPathsReportCheck();
+ assertThatThrownBy(() ->
+ OpenApiCheckVerifier.verify("src/test/resources/test-verify.yaml", pathsCheck, true, false, false))
+ .isInstanceOf(AssertionError.class)
+ .hasMessageContaining("Unexpected issue");
+ }
+
private Trivia makeComment(String comment, int line, int column) {
try {
return Trivia.createComment(
diff --git a/openapi-test-tools/src/test/java/org/apiaddicts/apitools/dosonarapi/TestNoOpCheck.java b/openapi-test-tools/src/test/java/org/apiaddicts/apitools/dosonarapi/TestNoOpCheck.java
new file mode 100644
index 0000000..a1074b2
--- /dev/null
+++ b/openapi-test-tools/src/test/java/org/apiaddicts/apitools/dosonarapi/TestNoOpCheck.java
@@ -0,0 +1,32 @@
+/*
+ * doSonarAPI: SonarQube OpenAPI Plugin
+ * Copyright (C) 2021-2022 Apiaddicts
+ * contacta AT apiaddicts DOT org
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.apiaddicts.apitools.dosonarapi;
+
+import com.sonar.sslr.api.AstNodeType;
+import java.util.Collections;
+import java.util.Set;
+import org.apiaddicts.apitools.dosonarapi.api.OpenApiCheck;
+
+public class TestNoOpCheck extends OpenApiCheck {
+ @Override
+ public Set subscribedKinds() {
+ return Collections.emptySet();
+ }
+}
diff --git a/openapi-test-tools/src/test/java/org/apiaddicts/apitools/dosonarapi/TestPathsReportCheck.java b/openapi-test-tools/src/test/java/org/apiaddicts/apitools/dosonarapi/TestPathsReportCheck.java
new file mode 100644
index 0000000..e2b7b2a
--- /dev/null
+++ b/openapi-test-tools/src/test/java/org/apiaddicts/apitools/dosonarapi/TestPathsReportCheck.java
@@ -0,0 +1,39 @@
+/*
+ * doSonarAPI: SonarQube OpenAPI Plugin
+ * Copyright (C) 2021-2022 Apiaddicts
+ * contacta AT apiaddicts DOT org
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.apiaddicts.apitools.dosonarapi;
+
+import com.google.common.collect.Sets;
+import com.sonar.sslr.api.AstNodeType;
+import java.util.Set;
+import org.apiaddicts.apitools.dosonarapi.api.OpenApiCheck;
+import org.apiaddicts.apitools.dosonarapi.api.v2.OpenApi2Grammar;
+import org.apiaddicts.apitools.dosonarapi.sslr.yaml.grammar.JsonNode;
+
+public class TestPathsReportCheck extends OpenApiCheck {
+ @Override
+ public Set subscribedKinds() {
+ return Sets.newHashSet(OpenApi2Grammar.PATHS);
+ }
+
+ @Override
+ protected void visitNode(JsonNode node) {
+ addIssue("paths issue", node);
+ }
+}
diff --git a/openapi-test-tools/src/test/resources/test-verify-noncompliant.yaml b/openapi-test-tools/src/test/resources/test-verify-noncompliant.yaml
new file mode 100644
index 0000000..646fd59
--- /dev/null
+++ b/openapi-test-tools/src/test/resources/test-verify-noncompliant.yaml
@@ -0,0 +1,11 @@
+swagger: "2.0"
+info:
+ version: 1.0.0
+ title: Test API
+paths:
+# Noncompliant@+1 {{paths issue}}
+ /pets:
+ get:
+ responses:
+ '200':
+ description: ok
diff --git a/openapi-test-tools/src/test/resources/test-verify.yaml b/openapi-test-tools/src/test/resources/test-verify.yaml
new file mode 100644
index 0000000..32ed33a
--- /dev/null
+++ b/openapi-test-tools/src/test/resources/test-verify.yaml
@@ -0,0 +1,11 @@
+swagger: "2.0"
+info:
+ version: 1.0.0
+ title: Test API
+paths:
+ # just a comment, not noncompliant
+ /pets:
+ get:
+ responses:
+ '200':
+ description: ok
diff --git a/pom.xml b/pom.xml
index 720a3c3..5719259 100644
--- a/pom.xml
+++ b/pom.xml
@@ -9,7 +9,7 @@
org.apiaddicts.apitools.dosonarapi
dosonarapi
- 1.1.2
+ 1.2.1
pom
SonarOpenAPI
@@ -19,6 +19,20 @@
Apiaddicts
https://apiaddicts.org
+
+
+ AP
+ Adrian Palanques
+ adrian.palanques@cloudappi.net
+ Cloudappi
+
+
+ MH
+ Melsy Huamani
+ melsy.huamani.pe@cloudappi.net
+ Cloudappi
+
+
GNU LGPL 3
@@ -81,7 +95,7 @@
1.22.0.848
3.37.0.87
1.24.0.633
- 1.0.0
+ 1.0.1
diff --git a/sonar-openapi-plugin/pom.xml b/sonar-openapi-plugin/pom.xml
index 4e5b602..957db01 100644
--- a/sonar-openapi-plugin/pom.xml
+++ b/sonar-openapi-plugin/pom.xml
@@ -5,7 +5,7 @@
org.apiaddicts.apitools.dosonarapi
dosonarapi
- 1.1.2
+ 1.2.1
../pom.xml
diff --git a/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApi.java b/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApi.java
index 723749e..92d51b8 100644
--- a/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApi.java
+++ b/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApi.java
@@ -19,40 +19,19 @@
*/
package org.apiaddicts.apitools.dosonarapi.plugin;
-import java.util.ArrayList;
-import java.util.List;
-import org.apache.commons.lang.StringUtils;
-import org.sonar.api.config.Configuration;
import org.sonar.api.resources.AbstractLanguage;
-@java.lang.SuppressWarnings("squid:S2160") // purposely not redefining equals() to ignore Configuration differences
public class OpenApi extends AbstractLanguage {
public static final String KEY = "openapi";
+ private static final String NAME = "OpenAPI";
- private static final String[] DEFAULT_FILE_SUFFIXES = {"yaml"};
-
- private Configuration settings;
-
- public OpenApi(Configuration settings) {
- super(KEY, "OpenAPI");
- this.settings = settings;
- }
-
- private static String[] filterEmptyStrings(String[] stringArray) {
- List nonEmptyStrings = new ArrayList<>();
- for (String string : stringArray) {
- if (StringUtils.isNotBlank(string.trim())) {
- nonEmptyStrings.add(string.trim());
- }
- }
- return nonEmptyStrings.toArray(new String[0]);
+ public OpenApi() {
+ super(KEY, NAME);
}
@Override
public String[] getFileSuffixes() {
- String[] suffixes = filterEmptyStrings(settings.getStringArray(OpenApiPlugin.FILE_SUFFIXES_KEY));
- return suffixes.length == 0 ? OpenApi.DEFAULT_FILE_SUFFIXES : suffixes;
+ return new String[0];
}
-
}
diff --git a/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiAnalyzer.java b/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiAnalyzer.java
index 5eb1ea4..8ed5257 100644
--- a/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiAnalyzer.java
+++ b/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiAnalyzer.java
@@ -109,6 +109,20 @@ public void scanFiles() {
}
}
+ private static boolean isV3Version(JsonNode openapiNode) {
+ if (openapiNode.isMissing()) return false;
+ String v = openapiNode.getTokenValue();
+ return "3.0.0".equals(v) || "3.0.1".equals(v) || "3.0.2".equals(v) || "3.0.3".equals(v);
+ }
+
+ private YamlParser selectParser(boolean isV2, boolean isV3, boolean isV31, boolean isV32) {
+ if (isV2) return OpenApiParser.createV2(configuration);
+ if (isV32) return OpenApiParser.createV32(configuration);
+ if (isV31) return OpenApiParser.createV31(configuration);
+ if (isV3) return OpenApiParser.createV3(configuration);
+ return null;
+ }
+
private void scanFile(InputFile inputFile) {
OpenApiFile openApiFile = SonarQubeOpenApiFile.create(inputFile);
OpenApiVisitorContext visitorContext;
@@ -118,19 +132,10 @@ private void scanFile(InputFile inputFile) {
JsonNode rootNode = OpenApiParser.createGeneric(configuration).parse(content);
boolean isV2 = !rootNode.at("/swagger").isMissing();
JsonNode openapiNode = rootNode.at("/openapi");
- boolean isV3 = !openapiNode.isMissing() && (
- openapiNode.getTokenValue().equals("3.0.0") ||
- openapiNode.getTokenValue().equals("3.0.1") ||
- openapiNode.getTokenValue().equals("3.0.2") ||
- openapiNode.getTokenValue().equals("3.0.3")
- );
-
- // Verificar si el nodo "/openapi" está presente y su valor es 3.1.0 para isV31
+ boolean isV3 = isV3Version(openapiNode);
boolean isV31 = !openapiNode.isMissing() && openapiNode.getTokenValue().equals("3.1.0");
- YamlParser targetParser = null;
- if (isV2) targetParser = OpenApiParser.createV2(configuration);
- if (isV3) targetParser = OpenApiParser.createV3(configuration);
- if (isV31) targetParser = OpenApiParser.createV31(configuration);
+ boolean isV32 = !openapiNode.isMissing() && openapiNode.getTokenValue().equals("3.2.0");
+ YamlParser targetParser = selectParser(isV2, isV3, isV31, isV32);
if (targetParser == null) return;
visitorContext = new OpenApiVisitorContext(targetParser.parse(content), targetParser.getIssues(), openApiFile);
@@ -150,6 +155,11 @@ private void scanFile(InputFile inputFile) {
visitorContext = new OpenApiVisitorContext(openApiFile, re);
LOG.error("Unable to parse file in i/o: " + inputFile.filename() + "\"\n" + ex.getMessage());
dumpException(re, inputFile);
+ } catch (RuntimeException ex) {
+ RecognitionException re = new RecognitionException(0, ex.getMessage());
+ visitorContext = new OpenApiVisitorContext(openApiFile, re);
+ LOG.error("Unexpected error parsing file: " + inputFile.filename() + "\"\n" + ex.getMessage());
+ dumpException(re, inputFile);
}
for (OpenApiCheck check : checks.all()) {
diff --git a/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiChecks.java b/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiChecks.java
index f3c1843..3bc6329 100644
--- a/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiChecks.java
+++ b/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiChecks.java
@@ -57,14 +57,61 @@ public OpenApiChecks addChecks(String repositoryKey, Iterable> checkCla
public OpenApiChecks addCustomChecks(@Nullable OpenApiCustomRuleRepository[] customRuleRepositories) {
if (customRuleRepositories != null) {
+ for (OpenApiCustomRuleRepository ruleRepository : customRuleRepositories) {
+ String key = ruleRepository.repositoryKey();
+ if (!key.equals(CheckList.YAML_REPOSITORY_KEY) &&
+ !key.equals(CheckList.JSON_REPOSITORY_KEY) &&
+ !key.equals(CheckList.OPENAPI_REPOSITORY_KEY)) {
+ addChecks(key, new ArrayList<>(ruleRepository.checkClasses()));
+ }
+ }
+ }
+ return this;
+ }
+
+ public OpenApiChecks addCustomYamlChecks(@Nullable OpenApiCustomRuleRepository[] customRuleRepositories) {
+ if (customRuleRepositories != null) {
+ for (OpenApiCustomRuleRepository ruleRepository : customRuleRepositories) {
+ String key = ruleRepository.repositoryKey();
+ if (!key.equals(CheckList.YAML_REPOSITORY_KEY) &&
+ !key.equals(CheckList.JSON_REPOSITORY_KEY) &&
+ !key.equals(CheckList.OPENAPI_REPOSITORY_KEY) &&
+ key.endsWith("-yaml")) {
+ addChecks(key, new ArrayList<>(ruleRepository.checkClasses()));
+ }
+ }
+ }
+ return this;
+ }
+ public OpenApiChecks addCustomJsonChecks(@Nullable OpenApiCustomRuleRepository[] customRuleRepositories) {
+ if (customRuleRepositories != null) {
for (OpenApiCustomRuleRepository ruleRepository : customRuleRepositories) {
- if (!ruleRepository.repositoryKey().equals(CheckList.REPOSITORY_KEY)) {
- addChecks(ruleRepository.repositoryKey(), new ArrayList<>(ruleRepository.checkClasses()));
+ String key = ruleRepository.repositoryKey();
+ if (!key.equals(CheckList.YAML_REPOSITORY_KEY) &&
+ !key.equals(CheckList.JSON_REPOSITORY_KEY) &&
+ !key.equals(CheckList.OPENAPI_REPOSITORY_KEY) &&
+ key.endsWith("-json")) {
+ addChecks(key, new ArrayList<>(ruleRepository.checkClasses()));
}
}
}
+ return this;
+ }
+ public OpenApiChecks addCustomOpenApiChecks(@Nullable OpenApiCustomRuleRepository[] customRuleRepositories) {
+ if (customRuleRepositories != null) {
+ for (OpenApiCustomRuleRepository ruleRepository : customRuleRepositories) {
+ String key = ruleRepository.repositoryKey();
+ if (!key.equals(CheckList.YAML_REPOSITORY_KEY) &&
+ !key.equals(CheckList.JSON_REPOSITORY_KEY) &&
+ !key.equals(CheckList.OPENAPI_REPOSITORY_KEY) &&
+ !key.endsWith("-yaml") &&
+ !key.endsWith("-json")) {
+ addChecks(key, new ArrayList<>(ruleRepository.checkClasses()));
+ }
+ }
+ }
return this;
}
diff --git a/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiPlugin.java b/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiPlugin.java
index ca5ec80..0940201 100644
--- a/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiPlugin.java
+++ b/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiPlugin.java
@@ -20,51 +20,13 @@
package org.apiaddicts.apitools.dosonarapi.plugin;
import org.sonar.api.Plugin;
-import org.sonar.api.config.PropertyDefinition;
-import org.sonar.api.resources.Qualifiers;
import org.apiaddicts.apitools.dosonarapi.openapi.metrics.OpenApiMetrics;
public class OpenApiPlugin implements Plugin {
- public static final String FILE_SUFFIXES_KEY = "sonar.openapi.file.suffixes";
- public static final String OPENAPI_CATEGORY = "OpenApi";
- // Subcategories
- private static final String GENERAL = "General";
-
@Override
public void define(Context context) {
-
context.addExtensions(
- PropertyDefinition.builder(FILE_SUFFIXES_KEY)
- .index(10)
- .name("File Suffixes")
- .description("A list of suffixes of OpenAPI files to analyze.")
- .category(OPENAPI_CATEGORY)
- .subCategory(GENERAL)
- .onQualifiers(Qualifiers.PROJECT)
- .multiValues(true)
- .defaultValue("yaml,json")
- .build(),
-/* PropertyDefinition.builder(OpenApiProperties.V2_PATH_KEY)
- .index(11)
- .name("Paths to OpenAPI v2 contract(s)")
- .description("Path to OpenAPI v2 contracts. Ant patterns are accepted for relative path. The contracts can be in JSON or in YAML.")
- .category(OPENAPI_CATEGORY)
- .subCategory(GENERAL)
- .onQualifiers(Qualifiers.PROJECT)
- .multiValues(true)
- .defaultValue(OpenApiProperties.DEFAULT_V2_PATH)
- .build(),
- PropertyDefinition.builder(OpenApiProperties.V3_PATH_KEY)
- .index(11)
- .name("Paths to OpenAPI v3 contract(s)")
- .description("Path to OpenAPI v3 contracts. Ant patterns are accepted for relative path. The contracts can be in JSON or in YAML.")
- .category(OPENAPI_CATEGORY)
- .subCategory(GENERAL)
- .onQualifiers(Qualifiers.PROJECT)
- .multiValues(true)
- .defaultValue(OpenApiProperties.DEFAULT_V3_PATH)
- .build(),*/
OpenApi.class,
OpenApiProfileDefinition.class,
OpenApiScannerSensor.class,
diff --git a/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiProfileDefinition.java b/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiProfileDefinition.java
index 9ea793b..d272652 100644
--- a/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiProfileDefinition.java
+++ b/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiProfileDefinition.java
@@ -25,15 +25,15 @@
import org.apiaddicts.apitools.dosonarapi.checks.CheckList;
public class OpenApiProfileDefinition implements BuiltInQualityProfilesDefinition {
+
public static final String SONAR_WAY_PROFILE = "Sonar way";
@Override
- public void define(BuiltInQualityProfilesDefinition.Context context) {
- NewBuiltInQualityProfile profile = context.createBuiltInQualityProfile(SONAR_WAY_PROFILE, OpenApi.KEY);
- profile.setDefault(true);
+ public void define(Context context) {
+ NewBuiltInQualityProfile profile = context.createBuiltInQualityProfile(SONAR_WAY_PROFILE, CheckList.OPENAPI_LANGUAGE);
for (Class> check : CheckList.getChecks()) {
Rule annotation = AnnotationUtils.getAnnotation(check, Rule.class);
- profile.activateRule(CheckList.REPOSITORY_KEY, annotation.key());
+ profile.activateRule(CheckList.OPENAPI_REPOSITORY_KEY, annotation.key());
}
profile.done();
}
diff --git a/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiRulesDefinition.java b/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiRulesDefinition.java
index a296e34..e21f1e2 100644
--- a/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiRulesDefinition.java
+++ b/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiRulesDefinition.java
@@ -39,8 +39,14 @@ private static RuleMetadataLoader getRuleMetadataLoader() {
@Override
public void define(Context context) {
+ createRepository(context, repositoryKey(), CheckList.YAML_LANGUAGE);
+ createRepository(context, CheckList.JSON_REPOSITORY_KEY, CheckList.JSON_LANGUAGE);
+ createRepository(context, CheckList.OPENAPI_REPOSITORY_KEY, CheckList.OPENAPI_LANGUAGE);
+ }
+
+ private void createRepository(Context context, String key, String language) {
NewRepository repository = context
- .createRepository(repositoryKey(), OpenApi.KEY)
+ .createRepository(key, language)
.setName(REPOSITORY_NAME);
getRuleMetadataLoader().addRulesByAnnotatedClass(repository, checkClasses());
@@ -54,7 +60,7 @@ public void define(Context context) {
@Override
public String repositoryKey() {
- return CheckList.REPOSITORY_KEY;
+ return CheckList.YAML_REPOSITORY_KEY;
}
@Override
diff --git a/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiScannerSensor.java b/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiScannerSensor.java
index a6b29f3..8c6f469 100644
--- a/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiScannerSensor.java
+++ b/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiScannerSensor.java
@@ -38,7 +38,9 @@
public class OpenApiScannerSensor implements Sensor {
private static final Logger LOGGER = Loggers.get(OpenApiScannerSensor.class);
- private final OpenApiChecks checks;
+ private final OpenApiChecks yamlChecks;
+ private final OpenApiChecks jsonChecks;
+ private final OpenApiChecks openapiChecks;
private FileLinesContextFactory fileLinesContextFactory;
private final NoSonarFilter noSonarFilter;
@@ -47,9 +49,15 @@ public OpenApiScannerSensor(CheckFactory checkFactory, FileLinesContextFactory f
}
public OpenApiScannerSensor(CheckFactory checkFactory, FileLinesContextFactory fileLinesContextFactory, NoSonarFilter noSonarFilter, @Nullable OpenApiCustomRuleRepository[] customRuleRepositories) {
- this.checks = OpenApiChecks.createOpenApiCheck(checkFactory)
- .addChecks(CheckList.REPOSITORY_KEY, CheckList.getChecks())
- .addCustomChecks(customRuleRepositories);
+ this.yamlChecks = OpenApiChecks.createOpenApiCheck(checkFactory)
+ .addChecks(CheckList.YAML_REPOSITORY_KEY, CheckList.getChecks())
+ .addCustomYamlChecks(customRuleRepositories);
+ this.jsonChecks = OpenApiChecks.createOpenApiCheck(checkFactory)
+ .addChecks(CheckList.JSON_REPOSITORY_KEY, CheckList.getChecks())
+ .addCustomJsonChecks(customRuleRepositories);
+ this.openapiChecks = OpenApiChecks.createOpenApiCheck(checkFactory)
+ .addChecks(CheckList.OPENAPI_REPOSITORY_KEY, CheckList.getChecks())
+ .addCustomOpenApiChecks(customRuleRepositories);
this.fileLinesContextFactory = fileLinesContextFactory;
this.noSonarFilter = noSonarFilter;
}
@@ -58,7 +66,7 @@ public OpenApiScannerSensor(CheckFactory checkFactory, FileLinesContextFactory f
public void describe(SensorDescriptor descriptor) {
descriptor.name("OpenAPI Scanner Sensor")
.onlyOnFileType(InputFile.Type.MAIN)
- .onlyOnLanguage(OpenApi.KEY);
+ .onlyOnLanguages(CheckList.YAML_LANGUAGE, CheckList.JSON_LANGUAGE, CheckList.OPENAPI_LANGUAGE);
}
@Override
@@ -68,18 +76,32 @@ public void execute(SensorContext context) {
}
public void scanFiles(SensorContext context, FilePredicates p) {
- Iterable it = context.fileSystem().inputFiles(
- p.and(p.hasType(InputFile.Type.MAIN),
- p.hasLanguage(OpenApi.KEY)
- ));
- List list = new ArrayList<>();
- it.forEach(list::add);
- List inputFiles = Collections.unmodifiableList(list);
+ List yamlFiles = new ArrayList<>();
+ context.fileSystem().inputFiles(
+ p.and(p.hasType(InputFile.Type.MAIN), p.hasLanguage(CheckList.YAML_LANGUAGE))
+ ).forEach(yamlFiles::add);
- if (!inputFiles.isEmpty()) {
- OpenApiAnalyzer scanner = new OpenApiAnalyzer(context, checks, fileLinesContextFactory, noSonarFilter, inputFiles/*, isV2*/);
- LOGGER.info("OpenAPI Scanner called for the following files: {}.", inputFiles);
- scanner.scanFiles();
+ List jsonFiles = new ArrayList<>();
+ context.fileSystem().inputFiles(
+ p.and(p.hasType(InputFile.Type.MAIN), p.hasLanguage(CheckList.JSON_LANGUAGE))
+ ).forEach(jsonFiles::add);
+
+ List openapiFiles = new ArrayList<>();
+ context.fileSystem().inputFiles(
+ p.and(p.hasType(InputFile.Type.MAIN), p.hasLanguage(CheckList.OPENAPI_LANGUAGE))
+ ).forEach(openapiFiles::add);
+
+ if (!yamlFiles.isEmpty()) {
+ LOGGER.info("OpenAPI Scanner called for yaml files: {}.", yamlFiles);
+ new OpenApiAnalyzer(context, yamlChecks, fileLinesContextFactory, noSonarFilter, Collections.unmodifiableList(yamlFiles)).scanFiles();
+ }
+ if (!jsonFiles.isEmpty()) {
+ LOGGER.info("OpenAPI Scanner called for json files: {}.", jsonFiles);
+ new OpenApiAnalyzer(context, jsonChecks, fileLinesContextFactory, noSonarFilter, Collections.unmodifiableList(jsonFiles)).scanFiles();
+ }
+ if (!openapiFiles.isEmpty()) {
+ LOGGER.info("OpenAPI Scanner called for openapi files: {}.", openapiFiles);
+ new OpenApiAnalyzer(context, openapiChecks, fileLinesContextFactory, noSonarFilter, Collections.unmodifiableList(openapiFiles)).scanFiles();
}
}
}
diff --git a/sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/MissingPropertyExceptionTest.java b/sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/MissingPropertyExceptionTest.java
new file mode 100644
index 0000000..1d339a3
--- /dev/null
+++ b/sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/MissingPropertyExceptionTest.java
@@ -0,0 +1,40 @@
+/*
+ * doSonarAPI: SonarQube OpenAPI Plugin
+ * Copyright (C) 2021-2022 Apiaddicts
+ * contacta AT apiaddicts DOT org
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.apiaddicts.apitools.dosonarapi.plugin;
+
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class MissingPropertyExceptionTest {
+
+ @Test
+ public void stores_property_name_and_message() {
+ MissingPropertyException ex = new MissingPropertyException("my.property");
+ assertThat(ex.getPropertyName()).isEqualTo("my.property");
+ assertThat(ex.getMessage()).isEqualTo("Property my.property is not defined!");
+ }
+
+ @Test
+ public void is_runtime_exception() {
+ MissingPropertyException ex = new MissingPropertyException("foo");
+ assertThat(ex).isInstanceOf(RuntimeException.class);
+ }
+}
diff --git a/sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiChecksTest.java b/sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiChecksTest.java
new file mode 100644
index 0000000..802e32d
--- /dev/null
+++ b/sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiChecksTest.java
@@ -0,0 +1,196 @@
+/*
+ * doSonarAPI: SonarQube OpenAPI Plugin
+ * Copyright (C) 2021-2022 Apiaddicts
+ * contacta AT apiaddicts DOT org
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.apiaddicts.apitools.dosonarapi.plugin;
+
+import java.util.List;
+import org.junit.Test;
+import org.sonar.api.batch.rule.ActiveRules;
+import org.sonar.api.batch.rule.CheckFactory;
+import org.sonar.api.batch.rule.internal.ActiveRulesBuilder;
+import org.sonar.api.rule.RuleKey;
+import org.apiaddicts.apitools.dosonarapi.api.OpenApiCustomRuleRepository;
+import org.apiaddicts.apitools.dosonarapi.checks.CheckList;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class OpenApiChecksTest {
+
+ private OpenApiCustomRuleRepository repoWithKey(String key) {
+ return new OpenApiCustomRuleRepository() {
+ @Override public String repositoryKey() { return key; }
+ @Override public List> checkClasses() { return CheckList.getChecks(); }
+ };
+ }
+
+ private CheckFactory factoryWithRule(String repoKey, String ruleKey) {
+ ActiveRules rules = new ActiveRulesBuilder()
+ .create(RuleKey.of(repoKey, ruleKey)).activate()
+ .build();
+ return new CheckFactory(rules);
+ }
+
+ @Test
+ public void addCustomYamlChecks_adds_non_json_repo() {
+ CheckFactory factory = factoryWithRule("my-custom-yaml", "PathMaskerading");
+ OpenApiChecks checks = OpenApiChecks.createOpenApiCheck(factory)
+ .addCustomYamlChecks(new OpenApiCustomRuleRepository[]{repoWithKey("my-custom-yaml")});
+ assertThat(checks.all()).isNotEmpty();
+ }
+
+ @Test
+ public void addCustomYamlChecks_skips_json_repo() {
+ CheckFactory factory = factoryWithRule("my-custom-json", "PathMaskerading");
+ OpenApiChecks checks = OpenApiChecks.createOpenApiCheck(factory)
+ .addCustomYamlChecks(new OpenApiCustomRuleRepository[]{repoWithKey("my-custom-json")});
+ assertThat(checks.all()).isEmpty();
+ }
+
+ @Test
+ public void addCustomYamlChecks_skips_base_yaml_repo() {
+ CheckFactory factory = factoryWithRule(CheckList.YAML_REPOSITORY_KEY, "PathMaskerading");
+ OpenApiChecks checks = OpenApiChecks.createOpenApiCheck(factory)
+ .addCustomYamlChecks(new OpenApiCustomRuleRepository[]{repoWithKey(CheckList.YAML_REPOSITORY_KEY)});
+ assertThat(checks.all()).isEmpty();
+ }
+
+ @Test
+ public void addCustomJsonChecks_adds_json_repo() {
+ CheckFactory factory = factoryWithRule("my-custom-json", "PathMaskerading");
+ OpenApiChecks checks = OpenApiChecks.createOpenApiCheck(factory)
+ .addCustomJsonChecks(new OpenApiCustomRuleRepository[]{repoWithKey("my-custom-json")});
+ assertThat(checks.all()).isNotEmpty();
+ }
+
+ @Test
+ public void addCustomJsonChecks_skips_non_json_repo() {
+ CheckFactory factory = factoryWithRule("my-custom-yaml", "PathMaskerading");
+ OpenApiChecks checks = OpenApiChecks.createOpenApiCheck(factory)
+ .addCustomJsonChecks(new OpenApiCustomRuleRepository[]{repoWithKey("my-custom-yaml")});
+ assertThat(checks.all()).isEmpty();
+ }
+
+ @Test
+ public void addCustomJsonChecks_skips_base_json_repo() {
+ CheckFactory factory = factoryWithRule(CheckList.JSON_REPOSITORY_KEY, "PathMaskerading");
+ OpenApiChecks checks = OpenApiChecks.createOpenApiCheck(factory)
+ .addCustomJsonChecks(new OpenApiCustomRuleRepository[]{repoWithKey(CheckList.JSON_REPOSITORY_KEY)});
+ assertThat(checks.all()).isEmpty();
+ }
+
+ @Test
+ public void addCustomChecks_handles_null() {
+ ActiveRules rules = new ActiveRulesBuilder().build();
+ CheckFactory factory = new CheckFactory(rules);
+ OpenApiChecks checks = OpenApiChecks.createOpenApiCheck(factory)
+ .addCustomYamlChecks(null)
+ .addCustomJsonChecks(null);
+ assertThat(checks.all()).isEmpty();
+ }
+
+ @Test
+ public void addCustomChecks_adds_non_built_in_repo() {
+ CheckFactory factory = factoryWithRule("my-custom-repo", "PathMaskerading");
+ OpenApiChecks checks = OpenApiChecks.createOpenApiCheck(factory)
+ .addCustomChecks(new OpenApiCustomRuleRepository[]{repoWithKey("my-custom-repo")});
+ assertThat(checks.all()).isNotEmpty();
+ }
+
+ @Test
+ public void addCustomChecks_skips_yaml_built_in_repo() {
+ CheckFactory factory = factoryWithRule(CheckList.YAML_REPOSITORY_KEY, "PathMaskerading");
+ OpenApiChecks checks = OpenApiChecks.createOpenApiCheck(factory)
+ .addCustomChecks(new OpenApiCustomRuleRepository[]{repoWithKey(CheckList.YAML_REPOSITORY_KEY)});
+ assertThat(checks.all()).isEmpty();
+ }
+
+ @Test
+ public void addCustomChecks_skips_json_built_in_repo() {
+ CheckFactory factory = factoryWithRule(CheckList.JSON_REPOSITORY_KEY, "PathMaskerading");
+ OpenApiChecks checks = OpenApiChecks.createOpenApiCheck(factory)
+ .addCustomChecks(new OpenApiCustomRuleRepository[]{repoWithKey(CheckList.JSON_REPOSITORY_KEY)});
+ assertThat(checks.all()).isEmpty();
+ }
+
+ @Test
+ public void addCustomChecks_handles_null_input() {
+ ActiveRules rules = new ActiveRulesBuilder().build();
+ CheckFactory factory = new CheckFactory(rules);
+ OpenApiChecks checks = OpenApiChecks.createOpenApiCheck(factory)
+ .addCustomChecks(null);
+ assertThat(checks.all()).isEmpty();
+ }
+
+ @Test
+ public void ruleKeyFor_returns_null_when_not_found() {
+ ActiveRules rules = new ActiveRulesBuilder().build();
+ CheckFactory factory = new CheckFactory(rules);
+ OpenApiChecks checks = OpenApiChecks.createOpenApiCheck(factory);
+ assertThat(checks.ruleKeyFor(new org.apiaddicts.apitools.dosonarapi.checks.PathMaskeradingCheck())).isNull();
+ }
+
+ @Test
+ public void addCustomOpenApiChecks_adds_repo_without_suffix() {
+ CheckFactory factory = factoryWithRule("my-custom", "PathMaskerading");
+ OpenApiChecks checks = OpenApiChecks.createOpenApiCheck(factory)
+ .addCustomOpenApiChecks(new OpenApiCustomRuleRepository[]{repoWithKey("my-custom")});
+ assertThat(checks.all()).isNotEmpty();
+ }
+
+ @Test
+ public void addCustomOpenApiChecks_skips_yaml_repo() {
+ CheckFactory factory = factoryWithRule("my-custom-yaml", "PathMaskerading");
+ OpenApiChecks checks = OpenApiChecks.createOpenApiCheck(factory)
+ .addCustomOpenApiChecks(new OpenApiCustomRuleRepository[]{repoWithKey("my-custom-yaml")});
+ assertThat(checks.all()).isEmpty();
+ }
+
+ @Test
+ public void addCustomOpenApiChecks_skips_json_repo() {
+ CheckFactory factory = factoryWithRule("my-custom-json", "PathMaskerading");
+ OpenApiChecks checks = OpenApiChecks.createOpenApiCheck(factory)
+ .addCustomOpenApiChecks(new OpenApiCustomRuleRepository[]{repoWithKey("my-custom-json")});
+ assertThat(checks.all()).isEmpty();
+ }
+
+ @Test
+ public void addCustomOpenApiChecks_skips_base_openapi_repo() {
+ CheckFactory factory = factoryWithRule(CheckList.OPENAPI_REPOSITORY_KEY, "PathMaskerading");
+ OpenApiChecks checks = OpenApiChecks.createOpenApiCheck(factory)
+ .addCustomOpenApiChecks(new OpenApiCustomRuleRepository[]{repoWithKey(CheckList.OPENAPI_REPOSITORY_KEY)});
+ assertThat(checks.all()).isEmpty();
+ }
+
+ @Test
+ public void addCustomOpenApiChecks_handles_null() {
+ ActiveRules rules = new ActiveRulesBuilder().build();
+ CheckFactory factory = new CheckFactory(rules);
+ OpenApiChecks checks = OpenApiChecks.createOpenApiCheck(factory)
+ .addCustomOpenApiChecks(null);
+ assertThat(checks.all()).isEmpty();
+ }
+
+ @Test
+ public void addCustomYamlChecks_requires_yaml_suffix() {
+ CheckFactory factory = factoryWithRule("my-custom", "PathMaskerading");
+ OpenApiChecks checks = OpenApiChecks.createOpenApiCheck(factory)
+ .addCustomYamlChecks(new OpenApiCustomRuleRepository[]{repoWithKey("my-custom")});
+ assertThat(checks.all()).isEmpty();
+ }
+}
diff --git a/sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiPluginTest.java b/sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiPluginTest.java
new file mode 100644
index 0000000..5b2b1b7
--- /dev/null
+++ b/sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiPluginTest.java
@@ -0,0 +1,46 @@
+/*
+ * doSonarAPI: SonarQube OpenAPI Plugin
+ * Copyright (C) 2021-2022 Apiaddicts
+ * contacta AT apiaddicts DOT org
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.apiaddicts.apitools.dosonarapi.plugin;
+
+import org.junit.Test;
+import org.sonar.api.Plugin;
+import org.sonar.api.SonarQubeSide;
+import org.sonar.api.internal.SonarRuntimeImpl;
+import org.sonar.api.utils.Version;
+import org.apiaddicts.apitools.dosonarapi.openapi.metrics.OpenApiMetrics;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class OpenApiPluginTest {
+
+ @Test
+ public void defines_expected_extensions() {
+ Plugin.Context context = new Plugin.Context(SonarRuntimeImpl.forSonarQube(Version.create(6, 7), SonarQubeSide.SERVER));
+ new OpenApiPlugin().define(context);
+
+ assertThat(context.getExtensions())
+ .contains(
+ OpenApi.class,
+ OpenApiProfileDefinition.class,
+ OpenApiScannerSensor.class,
+ OpenApiRulesDefinition.class,
+ OpenApiMetrics.class);
+ }
+}
diff --git a/sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiProfileDefinitionTest.java b/sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiProfileDefinitionTest.java
index b1f29f5..72acf11 100644
--- a/sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiProfileDefinitionTest.java
+++ b/sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiProfileDefinitionTest.java
@@ -20,36 +20,50 @@
package org.apiaddicts.apitools.dosonarapi.plugin;
import org.junit.Test;
-import org.mockito.Mockito;
-import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition.Context;
-import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition.NewBuiltInQualityProfile;
+import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition;
+import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition.BuiltInQualityProfile;
import org.apiaddicts.apitools.dosonarapi.checks.CheckList;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-import static org.apiaddicts.apitools.dosonarapi.plugin.OpenApiProfileDefinition.SONAR_WAY_PROFILE;
+import static org.assertj.core.api.Assertions.assertThat;
public class OpenApiProfileDefinitionTest {
- private static Context context(NewBuiltInQualityProfile profile) {
- Context context = mock(Context.class);
- when(context.createBuiltInQualityProfile(anyString(), anyString())).thenReturn(profile);
- return context;
+
+ @Test
+ public void defines_sonar_way_profile_for_openapi_language() {
+ OpenApiProfileDefinition definition = new OpenApiProfileDefinition();
+ BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context();
+
+ definition.define(context);
+
+ BuiltInQualityProfile profile = context.profile(CheckList.OPENAPI_LANGUAGE, OpenApiProfileDefinition.SONAR_WAY_PROFILE);
+ assertThat(profile).isNotNull();
+ assertThat(profile.language()).isEqualTo(CheckList.OPENAPI_LANGUAGE);
+ assertThat(profile.name()).isEqualTo(OpenApiProfileDefinition.SONAR_WAY_PROFILE);
+ assertThat(profile.rules()).hasSize(CheckList.getChecks().size());
+ }
+
+ @Test
+ public void profile_rules_belong_to_openapi_repository() {
+ OpenApiProfileDefinition definition = new OpenApiProfileDefinition();
+ BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context();
+
+ definition.define(context);
+
+ BuiltInQualityProfile profile = context.profile(CheckList.OPENAPI_LANGUAGE, OpenApiProfileDefinition.SONAR_WAY_PROFILE);
+ assertThat(profile.rules()).isNotEmpty();
+ assertThat(profile.rules())
+ .allMatch(r -> r.repoKey().equals(CheckList.OPENAPI_REPOSITORY_KEY));
}
@Test
- public void should_create_sonar_way_profile() {
+ public void only_openapi_language_profile_is_created() {
OpenApiProfileDefinition definition = new OpenApiProfileDefinition();
- NewBuiltInQualityProfile profile = mock(NewBuiltInQualityProfile.class);
- Context context = context(profile);
+ BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context();
definition.define(context);
- ;
- verify(context).createBuiltInQualityProfile(SONAR_WAY_PROFILE, OpenApi.KEY);
- verify(profile).setDefault(true);
- verify(profile, Mockito.atLeast(2)).activateRule(eq(CheckList.REPOSITORY_KEY), anyString());
+ assertThat(context.profile(CheckList.YAML_LANGUAGE, OpenApiProfileDefinition.SONAR_WAY_PROFILE)).isNull();
+ assertThat(context.profile(CheckList.JSON_LANGUAGE, OpenApiProfileDefinition.SONAR_WAY_PROFILE)).isNull();
+ assertThat(context.profile(CheckList.OPENAPI_LANGUAGE, OpenApiProfileDefinition.SONAR_WAY_PROFILE)).isNotNull();
}
}
diff --git a/sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiRulesDefinitionTest.java b/sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiRulesDefinitionTest.java
new file mode 100644
index 0000000..3d10f76
--- /dev/null
+++ b/sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiRulesDefinitionTest.java
@@ -0,0 +1,77 @@
+/*
+ * doSonarAPI: SonarQube OpenAPI Plugin
+ * Copyright (C) 2021-2022 Apiaddicts
+ * contacta AT apiaddicts DOT org
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.apiaddicts.apitools.dosonarapi.plugin;
+
+import org.junit.Test;
+import org.sonar.api.server.rule.RulesDefinition;
+import org.apiaddicts.apitools.dosonarapi.checks.CheckList;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class OpenApiRulesDefinitionTest {
+
+ @Test
+ public void defines_yaml_json_and_openapi_repositories() {
+ OpenApiRulesDefinition definition = new OpenApiRulesDefinition();
+ RulesDefinition.Context context = new RulesDefinition.Context();
+
+ definition.define(context);
+
+ RulesDefinition.Repository yamlRepo = context.repository(CheckList.YAML_REPOSITORY_KEY);
+ assertThat(yamlRepo).isNotNull();
+ assertThat(yamlRepo.language()).isEqualTo(CheckList.YAML_LANGUAGE);
+ assertThat(yamlRepo.rules()).hasSizeGreaterThanOrEqualTo(1);
+
+ RulesDefinition.Repository jsonRepo = context.repository(CheckList.JSON_REPOSITORY_KEY);
+ assertThat(jsonRepo).isNotNull();
+ assertThat(jsonRepo.language()).isEqualTo(CheckList.JSON_LANGUAGE);
+ assertThat(jsonRepo.rules()).hasSizeGreaterThanOrEqualTo(1);
+
+ RulesDefinition.Repository openapiRepo = context.repository(CheckList.OPENAPI_REPOSITORY_KEY);
+ assertThat(openapiRepo).isNotNull();
+ assertThat(openapiRepo.language()).isEqualTo(CheckList.OPENAPI_LANGUAGE);
+ assertThat(openapiRepo.rules()).hasSizeGreaterThanOrEqualTo(1);
+ }
+
+ @Test
+ public void repository_key_returns_yaml_key() {
+ OpenApiRulesDefinition definition = new OpenApiRulesDefinition();
+ assertThat(definition.repositoryKey()).isEqualTo(CheckList.YAML_REPOSITORY_KEY);
+ }
+
+ @Test
+ public void check_classes_returns_all_checks() {
+ OpenApiRulesDefinition definition = new OpenApiRulesDefinition();
+ assertThat(definition.checkClasses()).isEqualTo(CheckList.getChecks());
+ }
+
+ @Test
+ public void all_repos_have_same_rules() {
+ OpenApiRulesDefinition definition = new OpenApiRulesDefinition();
+ RulesDefinition.Context context = new RulesDefinition.Context();
+ definition.define(context);
+
+ RulesDefinition.Repository yamlRepo = context.repository(CheckList.YAML_REPOSITORY_KEY);
+ RulesDefinition.Repository jsonRepo = context.repository(CheckList.JSON_REPOSITORY_KEY);
+ RulesDefinition.Repository openapiRepo = context.repository(CheckList.OPENAPI_REPOSITORY_KEY);
+ assertThat(yamlRepo.rules()).hasSameSizeAs(jsonRepo.rules());
+ assertThat(yamlRepo.rules()).hasSameSizeAs(openapiRepo.rules());
+ }
+}
diff --git a/sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiScannerSensorTest.java b/sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiScannerSensorTest.java
index ac817e4..8e87685 100644
--- a/sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiScannerSensorTest.java
+++ b/sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiScannerSensorTest.java
@@ -77,14 +77,14 @@ public void sensor_descriptor() {
sensor().describe(descriptor);
assertThat(descriptor.name()).isEqualTo("OpenAPI Scanner Sensor");
- assertThat(descriptor.languages()).containsOnly("openapi");
+ assertThat(descriptor.languages()).containsOnly("yaml", "json", "openapi");
assertThat(descriptor.type()).isEqualTo(InputFile.Type.MAIN);
}
@Test
public void test_issues() {
activeRules = (new ActiveRulesBuilder())
- .create(RuleKey.of(CheckList.REPOSITORY_KEY, "PathMaskerading"))
+ .create(RuleKey.of(CheckList.YAML_REPOSITORY_KEY, "PathMaskerading"))
.activate()
.build();
@@ -115,11 +115,56 @@ public void test_issues() {
assertThat(context.allAnalysisErrors()).isEmpty();
}
+ @Test
+ public void test_json_issues_single_per_violation() {
+ activeRules = (new ActiveRulesBuilder())
+ .create(RuleKey.of(CheckList.JSON_REPOSITORY_KEY, "PathMaskerading"))
+ .activate()
+ .build();
+
+ inputFile("file1.json");
+ sensor().execute(context);
+
+ assertThat(context.allIssues()).hasSize(1);
+ Issue issue = Iterables.get(context.allIssues(), 0);
+ assertThat(issue.ruleKey().repository()).isEqualTo(CheckList.JSON_REPOSITORY_KEY);
+ assertThat(context.allAnalysisErrors()).isEmpty();
+ }
+
+ @Test
+ public void test_yaml_rules_do_not_fire_on_json_files() {
+ activeRules = (new ActiveRulesBuilder())
+ .create(RuleKey.of(CheckList.YAML_REPOSITORY_KEY, "PathMaskerading"))
+ .activate()
+ .build();
+
+ inputFile("file1.json");
+ sensor().execute(context);
+
+ assertThat(context.allIssues()).isEmpty();
+ }
+
+ @Test
+ public void test_openapi_language_scan() {
+ activeRules = (new ActiveRulesBuilder())
+ .create(RuleKey.of(CheckList.OPENAPI_REPOSITORY_KEY, "PathMaskerading"))
+ .activate()
+ .build();
+
+ inputFile("file1.yaml", CheckList.OPENAPI_LANGUAGE);
+ sensor().execute(context);
+
+ assertThat(context.allIssues()).hasSize(1);
+ Issue issue = Iterables.get(context.allIssues(), 0);
+ assertThat(issue.ruleKey().repository()).isEqualTo(CheckList.OPENAPI_REPOSITORY_KEY);
+ assertThat(context.allAnalysisErrors()).isEmpty();
+ }
+
@Test
public void parse_error() {
inputFile("parse-error.yaml");
activeRules = (new ActiveRulesBuilder())
- .create(RuleKey.of(CheckList.REPOSITORY_KEY, ParsingErrorCheck.CHECK_KEY))
+ .create(RuleKey.of(CheckList.YAML_REPOSITORY_KEY, ParsingErrorCheck.CHECK_KEY))
.activate()
.build();
sensor().execute(context);
@@ -136,7 +181,7 @@ public void parse_error() {
public void parse_openapi3_headers_ref() {
inputFile("headers_ref.yaml");
activeRules = (new ActiveRulesBuilder())
- .create(RuleKey.of(CheckList.REPOSITORY_KEY, ParsingErrorCheck.CHECK_KEY))
+ .create(RuleKey.of(CheckList.YAML_REPOSITORY_KEY, ParsingErrorCheck.CHECK_KEY))
.activate()
.build();
sensor().execute(context);
@@ -146,7 +191,7 @@ public void parse_openapi3_headers_ref() {
public void parse_yaml_break_comment_ok() {
inputFile("parse-yaml.yaml");
activeRules = (new ActiveRulesBuilder())
- .create(RuleKey.of(CheckList.REPOSITORY_KEY, ParsingErrorCheck.CHECK_KEY))
+ .create(RuleKey.of(CheckList.YAML_REPOSITORY_KEY, ParsingErrorCheck.CHECK_KEY))
.activate()
.build();
sensor().execute(context);
@@ -158,7 +203,7 @@ public void parse_yaml_break_comment_ok() {
public void parse_yaml_slash_ok() {
inputFile("parse-error-slash.json");
activeRules = (new ActiveRulesBuilder())
- .create(RuleKey.of(CheckList.REPOSITORY_KEY, ParsingErrorCheck.CHECK_KEY))
+ .create(RuleKey.of(CheckList.YAML_REPOSITORY_KEY, ParsingErrorCheck.CHECK_KEY))
.activate()
.build();
sensor().execute(context);
@@ -170,7 +215,7 @@ public void parse_yaml_slash_ok() {
public void parse_yaml_tabs_ok() {
inputFile("parse-error-tabs.json");
activeRules = (new ActiveRulesBuilder())
- .create(RuleKey.of(CheckList.REPOSITORY_KEY, ParsingErrorCheck.CHECK_KEY))
+ .create(RuleKey.of(CheckList.YAML_REPOSITORY_KEY, ParsingErrorCheck.CHECK_KEY))
.activate()
.build();
sensor().execute(context);
@@ -182,7 +227,7 @@ public void parse_yaml_tabs_ok() {
public void parse_yaml_tabs_ok_31() {
inputFile("file2.yaml");
activeRules = (new ActiveRulesBuilder())
- .create(RuleKey.of(CheckList.REPOSITORY_KEY, ParsingErrorCheck.CHECK_KEY))
+ .create(RuleKey.of(CheckList.YAML_REPOSITORY_KEY, ParsingErrorCheck.CHECK_KEY))
.activate()
.build();
sensor().execute(context);
@@ -197,7 +242,7 @@ public void test_folder() {
for (String file: files) {
context = SensorContextTester.create(baseDir);
inputFile(file);
- activeRules = (new ActiveRulesBuilder()).create(RuleKey.of(CheckList.REPOSITORY_KEY, ParsingErrorCheck.CHECK_KEY))
+ activeRules = (new ActiveRulesBuilder()).create(RuleKey.of(CheckList.YAML_REPOSITORY_KEY, ParsingErrorCheck.CHECK_KEY))
.activate().build();
sensor().execute(context);
if (!context.allIssues().isEmpty() || !context.allAnalysisErrors().isEmpty()) errorFiles.add(file);
@@ -223,11 +268,16 @@ private OpenApiScannerSensor sensor() {
}
private InputFile inputFile(String name) {
+ String language = name.endsWith(".json") ? CheckList.JSON_LANGUAGE : CheckList.YAML_LANGUAGE;
+ return inputFile(name, language);
+ }
+
+ private InputFile inputFile(String name, String language) {
DefaultInputFile inputFile = TestInputFileBuilder.create("moduleKey", name)
.setModuleBaseDir(baseDir)
.setCharset(StandardCharsets.UTF_8)
.setType(InputFile.Type.MAIN)
- .setLanguage(OpenApi.KEY)
+ .setLanguage(language)
.initMetadata(TestUtils.fileContent(new File(baseDir.toFile(), name), StandardCharsets.UTF_8))
.build();
context.fileSystem().add(inputFile);
diff --git a/sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/SonarQubeOpenApiFileTest.java b/sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/SonarQubeOpenApiFileTest.java
new file mode 100644
index 0000000..3f409a0
--- /dev/null
+++ b/sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/SonarQubeOpenApiFileTest.java
@@ -0,0 +1,80 @@
+/*
+ * doSonarAPI: SonarQube OpenAPI Plugin
+ * Copyright (C) 2021-2022 Apiaddicts
+ * contacta AT apiaddicts DOT org
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.apiaddicts.apitools.dosonarapi.plugin;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Paths;
+import org.junit.Test;
+import org.sonar.api.batch.fs.InputFile;
+import org.sonar.api.batch.fs.internal.TestInputFileBuilder;
+import org.apiaddicts.apitools.dosonarapi.api.OpenApiFile;
+import org.apiaddicts.apitools.dosonarapi.checks.CheckList;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class SonarQubeOpenApiFileTest {
+
+ private static final File BASE_DIR = Paths.get("src/test/resources/sensor").toAbsolutePath().toFile();
+
+ @Test
+ public void returns_filename() {
+ InputFile inputFile = TestInputFileBuilder.create("moduleKey", "file1.yaml")
+ .setModuleBaseDir(BASE_DIR.toPath())
+ .setCharset(StandardCharsets.UTF_8)
+ .setType(InputFile.Type.MAIN)
+ .setLanguage(CheckList.YAML_LANGUAGE)
+ .initMetadata(TestUtils.fileContent(new File(BASE_DIR, "file1.yaml"), StandardCharsets.UTF_8))
+ .build();
+
+ OpenApiFile openApiFile = SonarQubeOpenApiFile.create(inputFile);
+ assertThat(openApiFile.fileName()).isEqualTo("file1.yaml");
+ }
+
+ @Test
+ public void returns_content() {
+ InputFile inputFile = TestInputFileBuilder.create("moduleKey", "file1.yaml")
+ .setModuleBaseDir(BASE_DIR.toPath())
+ .setCharset(StandardCharsets.UTF_8)
+ .setType(InputFile.Type.MAIN)
+ .setLanguage(CheckList.YAML_LANGUAGE)
+ .initMetadata(TestUtils.fileContent(new File(BASE_DIR, "file1.yaml"), StandardCharsets.UTF_8))
+ .build();
+
+ OpenApiFile openApiFile = SonarQubeOpenApiFile.create(inputFile);
+ assertThat(openApiFile.content()).isNotEmpty();
+ }
+
+ @Test
+ public void throws_illegal_state_on_io_exception() throws IOException {
+ InputFile inputFile = mock(InputFile.class);
+ when(inputFile.filename()).thenReturn("test.yaml");
+ when(inputFile.contents()).thenThrow(new IOException("read error"));
+
+ OpenApiFile openApiFile = SonarQubeOpenApiFile.create(inputFile);
+ assertThatThrownBy(openApiFile::content)
+ .isInstanceOf(IllegalStateException.class)
+ .hasMessageContaining("Could not read content");
+ }
+}
diff --git a/sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/cpd/OpenApiCpdAnalyzerTest.java b/sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/cpd/OpenApiCpdAnalyzerTest.java
index 1f4aa36..049e8bc 100644
--- a/sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/cpd/OpenApiCpdAnalyzerTest.java
+++ b/sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/cpd/OpenApiCpdAnalyzerTest.java
@@ -25,7 +25,7 @@
import java.util.List;
import java.util.stream.Collectors;
-import org.apiaddicts.apitools.dosonarapi.plugin.OpenApi;
+import org.apiaddicts.apitools.dosonarapi.checks.CheckList;
import org.apiaddicts.apitools.dosonarapi.plugin.TestUtils;
import org.junit.Test;
import org.sonar.api.batch.fs.InputFile;
@@ -75,7 +75,7 @@ private DefaultInputFile inputFile(String fileName) {
.setModuleBaseDir(Paths.get(BASE_DIR))
.setCharset(UTF_8)
.setType(InputFile.Type.MAIN)
- .setLanguage(OpenApi.KEY)
+ .setLanguage(CheckList.YAML_LANGUAGE)
.initMetadata(TestUtils.fileContent(file, Charsets.UTF_8))
.build();
diff --git a/sonar-openapi-plugin/src/test/resources/sensor/file1.json b/sonar-openapi-plugin/src/test/resources/sensor/file1.json
new file mode 100644
index 0000000..5178058
--- /dev/null
+++ b/sonar-openapi-plugin/src/test/resources/sensor/file1.json
@@ -0,0 +1,51 @@
+{
+ "openapi": "3.0.1",
+ "info": {
+ "version": "1.0.0",
+ "title": "Swagger Petstore"
+ },
+ "paths": {
+ "/pets/{petId}": {
+ "get": {
+ "responses": {
+ "200": {
+ "description": "success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Pet"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/pets/1234": {
+ "get": {
+ "responses": {
+ "200": {
+ "description": "success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Pet"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "Pet": {
+ "type": "object"
+ }
+ }
+ }
+}