diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f50109..4ade06e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,18 @@ 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.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..b951732 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -# 🛠️ Sonar OpenApi (plugin) ![Release](https://img.shields.io/badge/release-1.1.2-purple) ![Swagger](https://img.shields.io/badge/-openapi-%23Clojure?style=flat&logo=swagger&logoColor=white) ![Java](https://img.shields.io/badge/java-%23ED8B00.svg?style=flat&logo=openjdk&logoColor=white) [![License: LGPL v3](https://img.shields.io/badge/license-LGPL_v3-blue.svg)](https://www.gnu.org/licenses/lgpl-3.0) +# 🛠️ Sonar OpenApi (plugin) ![Release](https://img.shields.io/badge/release-1.2.0-purple) ![Swagger](https://img.shields.io/badge/-openapi-%23Clojure?style=flat&logo=swagger&logoColor=white) ![Java](https://img.shields.io/badge/java-%23ED8B00.svg?style=flat&logo=openjdk&logoColor=white) [![License: LGPL v3](https://img.shields.io/badge/license-LGPL_v3-blue.svg)](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 ![SonarOpenApi in action](sonarqube.jpg) diff --git a/its/pom.xml b/its/pom.xml index 12c0dd4..c07aa94 100644 --- a/its/pom.xml +++ b/its/pom.xml @@ -5,7 +5,7 @@ org.apiaddicts.apitools.dosonarapi dosonarapi - 1.1.2 + 1.2.0 ../pom.xml 4.0.0 diff --git a/openapi-checks/pom.xml b/openapi-checks/pom.xml index 19de1cc..e13b2c4 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.0 ../pom.xml 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-front-end/pom.xml b/openapi-front-end/pom.xml index a9e84d9..73b4941 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.0 ../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..a5ea9f2 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,52 +251,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/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/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/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-test-tools/pom.xml b/openapi-test-tools/pom.xml index 0774d45..2524ac1 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.0 ../pom.xml diff --git a/pom.xml b/pom.xml index 720a3c3..baf72e1 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ org.apiaddicts.apitools.dosonarapi dosonarapi - 1.1.2 + 1.2.0 pom SonarOpenAPI diff --git a/sonar-openapi-plugin/pom.xml b/sonar-openapi-plugin/pom.xml index 4e5b602..323e377 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.0 ../pom.xml 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..46bbd4d 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);