From 551b75d4a84433fb585036ca4f22ed3124cfaedf Mon Sep 17 00:00:00 2001 From: Melsy Huamani Date: Tue, 5 May 2026 14:24:40 -0500 Subject: [PATCH 01/15] feat: add support openapi3.2 --- .../dosonarapi/api/v32/OpenApi32Grammar.java | 517 ++++++++++++++++++ .../openapi/parser/OpenApiParser.java | 5 + .../dosonarapi/api/v32/JsonSchemaTest.java | 69 +++ .../dosonarapi/api/v32/SimpleTest.java | 63 +++ .../dosonarapi/api/v32/WebhooksOnlyTest.java | 41 ++ .../test/resources/models/v32/jsonschema.yaml | 38 ++ .../models/v32/mediatypes-component.yaml | 8 + .../test/resources/models/v32/path-item.yaml | 11 + .../models/v32/pathitems-component.yaml | 6 + .../src/test/resources/models/v32/paths.yaml | 13 + .../models/v32/server-with-name.yaml | 10 + .../src/test/resources/models/v32/simple.yaml | 29 + .../resources/models/v32/webhooks-only.yaml | 20 + .../test/resources/models/v32/webhooks.yaml | 14 + .../dosonarapi/plugin/OpenApiAnalyzer.java | 3 +- 15 files changed, 846 insertions(+), 1 deletion(-) create mode 100644 openapi-front-end/src/main/java/org/apiaddicts/apitools/dosonarapi/api/v32/OpenApi32Grammar.java create mode 100644 openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/api/v32/JsonSchemaTest.java create mode 100644 openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/api/v32/SimpleTest.java create mode 100644 openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/api/v32/WebhooksOnlyTest.java create mode 100644 openapi-front-end/src/test/resources/models/v32/jsonschema.yaml create mode 100644 openapi-front-end/src/test/resources/models/v32/mediatypes-component.yaml create mode 100644 openapi-front-end/src/test/resources/models/v32/path-item.yaml create mode 100644 openapi-front-end/src/test/resources/models/v32/pathitems-component.yaml create mode 100644 openapi-front-end/src/test/resources/models/v32/paths.yaml create mode 100644 openapi-front-end/src/test/resources/models/v32/server-with-name.yaml create mode 100644 openapi-front-end/src/test/resources/models/v32/simple.yaml create mode 100644 openapi-front-end/src/test/resources/models/v32/webhooks-only.yaml create mode 100644 openapi-front-end/src/test/resources/models/v32/webhooks.yaml 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..6f4d778 --- /dev/null +++ b/openapi-front-end/src/main/java/org/apiaddicts/apitools/dosonarapi/api/v32/OpenApi32Grammar.java @@ -0,0 +1,517 @@ +/* + * 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.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); + buildTags(b); + + 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(); + b.rule(MUTUALTLS_SECURITY_SCHEME).is(b.object( + b.discriminant("type", "mutualTLS"), + b.property("description", DESCRIPTION), + 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()))); + } + + 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.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()))); + 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.property("headers", b.object( + b.patternProperty(".*", b.firstOf(REF, HEADER)))), + 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()))); + } + + 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()))); + b.rule(SCHEMAS_COMPONENT).is(b.object(b.patternProperty(".*", b.firstOf(REF, SCHEMA)))); + 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)))); + 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("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()))); + 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()))); + } + + 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()))); + 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()))); + } + + 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/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..6cb65e1 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 @@ -125,12 +125,13 @@ private void scanFile(InputFile inputFile) { openapiNode.getTokenValue().equals("3.0.3") ); - // Verificar si el nodo "/openapi" está presente y su valor es 3.1.0 para isV31 boolean isV31 = !openapiNode.isMissing() && openapiNode.getTokenValue().equals("3.1.0"); + boolean isV32 = !openapiNode.isMissing() && openapiNode.getTokenValue().equals("3.2.0"); YamlParser targetParser = null; if (isV2) targetParser = OpenApiParser.createV2(configuration); if (isV3) targetParser = OpenApiParser.createV3(configuration); if (isV31) targetParser = OpenApiParser.createV31(configuration); + if (isV32) targetParser = OpenApiParser.createV32(configuration); if (targetParser == null) return; visitorContext = new OpenApiVisitorContext(targetParser.parse(content), targetParser.getIssues(), openApiFile); From eeee25746f44a3be5e68c1b0a818003042a0204f Mon Sep 17 00:00:00 2001 From: Melsy Huamani Date: Tue, 5 May 2026 20:36:46 -0500 Subject: [PATCH 02/15] feat: complete missing validation checks for openapi 3.2 and solve for 3.1 --- README.md | 2 +- its/pom.xml | 2 +- openapi-checks/pom.xml | 2 +- .../checks/ContactValidEmailCheck.java | 4 +++- .../dosonarapi/checks/DeclaredTagCheck.java | 4 +++- .../dosonarapi/checks/DefaultResponseCheck.java | 4 +++- .../dosonarapi/checks/DefinedResponseCheck.java | 4 +++- .../checks/DescriptionDiffersSummaryCheck.java | 4 +++- .../dosonarapi/checks/DocumentedTagCheck.java | 6 ++++-- .../dosonarapi/checks/MediaTypeCheck.java | 7 +++++-- .../dosonarapi/checks/NoContentIn204Check.java | 9 ++++++--- .../checks/NoUnusedDefinitionCheck.java | 4 +++- .../dosonarapi/checks/PathMaskeradingCheck.java | 4 +++- .../checks/ProvideOpSummaryCheck.java | 4 +++- openapi-front-end/pom.xml | 2 +- .../api/TestOpenApiVisitorRunner.java | 17 +++++++++++++---- openapi-test-tools/pom.xml | 2 +- pom.xml | 2 +- sonar-openapi-plugin/pom.xml | 2 +- 19 files changed, 59 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index fcfc996..ad7617b 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. 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..b0094e7 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 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/TestOpenApiVisitorRunner.java b/openapi-front-end/src/main/java/org/apiaddicts/apitools/dosonarapi/api/TestOpenApiVisitorRunner.java index f03d31f..bc14192 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,35 @@ 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); + } + + 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 +96,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-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 From 8e03ceada1e00f906afb1ecbb9446e5531cb5682 Mon Sep 17 00:00:00 2001 From: Melsy Huamani Date: Wed, 6 May 2026 15:48:33 -0500 Subject: [PATCH 03/15] feat: update readme and changelog --- CHANGELOG.md | 12 ++++++++++++ README.md | 2 +- .../dosonarapi/api/v31/OpenApi31Grammar.java | 2 ++ .../dosonarapi/api/v32/OpenApi32Grammar.java | 2 ++ 4 files changed, 17 insertions(+), 1 deletion(-) 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 ad7617b..b951732 100644 --- a/README.md +++ b/README.md @@ -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/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..eb50ad7 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 @@ -320,6 +320,8 @@ 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()), 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 index 6f4d778..86916af 100644 --- 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 @@ -332,6 +332,8 @@ 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()), From 1207930d6ad3578e5d0bfe18acca53ee3260e2b7 Mon Sep 17 00:00:00 2001 From: Melsy Huamani Date: Wed, 6 May 2026 19:39:32 -0500 Subject: [PATCH 04/15] fix: duplicated code and ref --- .../checks/NoUnusedDefinitionCheck.java | 6 +- .../dosonarapi/api/OpenApiGrammar.java | 312 ++++++++++++++++++ .../dosonarapi/api/v3/OpenApi3Grammar.java | 227 ++----------- .../dosonarapi/api/v31/OpenApi31Grammar.java | 252 ++------------ .../dosonarapi/api/v32/OpenApi32Grammar.java | 179 ++-------- 5 files changed, 382 insertions(+), 594 deletions(-) create mode 100644 openapi-front-end/src/main/java/org/apiaddicts/apitools/dosonarapi/api/OpenApiGrammar.java 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 b0094e7..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 @@ -167,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-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..29ed307 --- /dev/null +++ b/openapi-front-end/src/main/java/org/apiaddicts/apitools/dosonarapi/api/OpenApiGrammar.java @@ -0,0 +1,312 @@ +/* + * 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 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("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("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("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("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("refreshUrl", b.string()), + b.property("scopes", b.object(b.patternProperty(".*", b.string()))), + b.patternProperty(EXTENSION_PATTERN, b.anything()))); + b.rule(passwordFlow).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(credentialsFlow).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(authFlow).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(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("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()))); + } + + 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("description", description), + b.property("headers", b.object(b.patternProperty(".*", b.firstOf(ref, header)))), + b.property("content", b.object(b.patternProperty(".*", mediaType))), + 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(".*", mediaType))), + b.patternProperty(EXTENSION_PATTERN, b.anything()))); + } + + 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)))); + } + + 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("summary", b.string()), + b.property("description", description), + b.property("externalDocs", externalDoc), + b.property("operationId", b.string()), + b.property("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("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("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(".*", 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("description", description), + b.property("required", b.bool()), + b.property("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("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()))); + } + + 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("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()))); + } + + 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("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()))); + } + + 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("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("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("description", description), + b.property("externalDocs", externalDoc), + b.patternProperty(EXTENSION_PATTERN, b.anything()))); + } + + 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("summary", b.string()), + b.property("description", description), + b.property("value", b.anything()), + b.property("externalValue", b.string()), + b.patternProperty(EXTENSION_PATTERN, b.anything()))); + } + + 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/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 eb50ad7..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,21 +152,16 @@ 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) { @@ -393,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); + } @@ -416,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 index 86916af..7a6827c 100644 --- 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 @@ -20,6 +20,7 @@ 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") @@ -119,127 +120,30 @@ 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.property("description", DESCRIPTION), 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())))); + OpenApiGrammar.buildSecurityFlows(b, + FLOWS, IMPLICIT_FLOW, PASSWORD_FLOW, CREDENTIALS_FLOW, AUTH_FLOW, SECURITY_REQUIREMENT); } 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.buildCallbacks(b, CALLBACK, LINK, PATH, SERVER, DESCRIPTION); } 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()))); + 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), @@ -268,12 +172,7 @@ private static void buildParameters(YamlGrammarBuilder b) { 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()))); + 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()), @@ -284,14 +183,7 @@ private static void buildParameters(YamlGrammarBuilder b) { b.property("headers", b.object( b.patternProperty(".*", b.firstOf(REF, HEADER)))), 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.buildEncoding(b, ENCODING, REF, HEADER); } private static void buildComponents(YamlGrammarBuilder b) { @@ -309,16 +201,11 @@ private static void buildComponents(YamlGrammarBuilder b) { b.property("pathItems", PATH_ITEMS_COMPONENT), b.property("mediaTypes", MEDIA_TYPES_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)))); 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)))); @@ -425,21 +312,8 @@ 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); } private static void buildPaths(YamlGrammarBuilder b) { @@ -463,21 +337,8 @@ private static void buildPaths(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).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, REF, PARAMETER, REQUEST_BODY, RESPONSES, + CALLBACK, EXTERNAL_DOC, SECURITY_REQUIREMENT, SERVER, DESCRIPTION); } private static void buildServer(YamlGrammarBuilder b) { From 62a90c652fafcdc64b23d217055d70caffebb564 Mon Sep 17 00:00:00 2001 From: Melsy Huamani Date: Wed, 6 May 2026 20:33:28 -0500 Subject: [PATCH 05/15] fix: sonar issues --- .../dosonarapi/api/OpenApiGrammar.java | 148 +++++++++++------- .../api/TestOpenApiVisitorRunner.java | 1 + .../dosonarapi/plugin/OpenApiAnalyzer.java | 28 ++-- 3 files changed, 105 insertions(+), 72 deletions(-) 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 index 29ed307..fd50b07 100644 --- 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 @@ -26,6 +26,23 @@ 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, @@ -34,24 +51,24 @@ public static void buildCommonSecuritySchemes(YamlGrammarBuilder b, GrammarRuleKey flows, GrammarRuleKey description) { b.rule(httpScheme).is(b.object( b.discriminant("type", "http"), - b.property("description", description), + 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("description", description), + 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("description", description), + 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("description", description), + b.property(PROP_DESCRIPTION, description), b.mandatoryProperty("openIdConnectUrl", b.string()), b.patternProperty(EXTENSION_PATTERN, b.anything()))).skip(); } @@ -67,24 +84,24 @@ public static void buildSecurityFlows(YamlGrammarBuilder b, b.patternProperty(EXTENSION_PATTERN, b.anything()))); b.rule(implicitFlow).is(b.object( b.mandatoryProperty("authorizationUrl", b.string()), - b.property("refreshUrl", b.string()), - b.property("scopes", b.object(b.patternProperty(".*", 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("tokenUrl", b.string()), - b.property("refreshUrl", b.string()), - b.property("scopes", b.object(b.patternProperty(".*", 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(credentialsFlow).is(b.object( - b.mandatoryProperty("tokenUrl", b.string()), - b.property("refreshUrl", b.string()), - b.property("scopes", b.object(b.patternProperty(".*", 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(authFlow).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.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())))); @@ -99,13 +116,14 @@ public static void buildCallbacks(YamlGrammarBuilder b, 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(PROP_PARAMETERS, b.object(b.patternProperty(".*", b.anything()))), b.property("requestBody", b.anything()), - b.property("description", description), + 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, @@ -115,26 +133,33 @@ public static void buildResponsesAndHeader(YamlGrammarBuilder b, 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.mandatoryProperty(PROP_DESCRIPTION, description), b.property("headers", b.object(b.patternProperty(".*", b.firstOf(ref, header)))), - b.property("content", b.object(b.patternProperty(".*", mediaType))), + 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("description", description), - b.property("required", b.bool()), - b.property("deprecated", b.bool()), + b.property(PROP_DESCRIPTION, description), + b.property(PROP_REQUIRED, b.bool()), + b.property(PROP_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(".*", mediaType))), + 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, @@ -156,6 +181,7 @@ public static void buildBaseComponentRules(YamlGrammarBuilder b, 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, @@ -164,16 +190,16 @@ public static void buildOperation(YamlGrammarBuilder b, GrammarRuleKey description) { b.rule(operation).is(b.object( b.property("tags", b.array(b.string())), - b.property("summary", b.string()), - b.property("description", description), + b.property(PROP_SUMMARY, b.string()), + b.property(PROP_DESCRIPTION, description), b.property("externalDocs", externalDoc), b.property("operationId", b.string()), - b.property("parameters", b.array(b.firstOf(ref, parameter))), + 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("deprecated", b.bool()), + b.property(PROP_DEPRECATED, b.bool()), b.property("security", b.array(securityRequirement)), b.property("servers", b.array(server)), b.patternProperty(EXTENSION_PATTERN, b.anything()))); @@ -185,18 +211,18 @@ public static void buildParameter(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(PROP_DESCRIPTION, description), + b.property(PROP_REQUIRED, b.bool()), + b.property(PROP_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.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("content", b.object( + b.property(PROP_CONTENT, b.object( b.patternProperty(".*", mediaType))), b.patternProperty(EXTENSION_PATTERN, b.anything()))); } @@ -205,9 +231,9 @@ public static void buildRequestBody(YamlGrammarBuilder b, GrammarRuleKey requestBody, GrammarRuleKey ref, GrammarRuleKey mediaType, GrammarRuleKey description) { b.rule(requestBody).is(b.object( - b.property("description", description), - b.property("required", b.bool()), - b.property("content", 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()))); } @@ -216,9 +242,9 @@ public static void buildMediaType(YamlGrammarBuilder b, GrammarRuleKey mediaType, GrammarRuleKey ref, GrammarRuleKey schema, GrammarRuleKey example, GrammarRuleKey encoding) { b.rule(mediaType).is(b.object( - b.property("schema", b.firstOf(ref, schema)), - b.property("example", b.anything()), - b.property("examples", 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))), @@ -231,9 +257,9 @@ public static void buildEncoding(YamlGrammarBuilder b, 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.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()))); } @@ -242,8 +268,8 @@ public static void buildPathRule(YamlGrammarBuilder b, GrammarRuleKey parameter, GrammarRuleKey server, GrammarRuleKey description) { b.rule(path).is(b.object( b.property("$ref", b.string()), - b.property("summary", b.string()), - b.property("description", description), + b.property(PROP_SUMMARY, b.string()), + b.property(PROP_DESCRIPTION, description), b.property("get", operation), b.property("put", operation), b.property("post", operation), @@ -253,7 +279,7 @@ public static void buildPathRule(YamlGrammarBuilder b, b.property("patch", operation), b.property("trace", operation), b.property("servers", b.array(server)), - b.property("parameters", b.array(b.firstOf(ref, parameter))), + b.property(PROP_PARAMETERS, b.array(b.firstOf(ref, parameter))), b.patternProperty(EXTENSION_PATTERN, b.anything()))); } @@ -261,14 +287,14 @@ 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("description", description), + 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("description", description), + b.property(PROP_DESCRIPTION, description), b.patternProperty(EXTENSION_PATTERN, b.anything()))); } @@ -276,11 +302,12 @@ 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("description", description), + 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, @@ -293,13 +320,14 @@ public static void buildMutualTlsSecuritySetup(YamlGrammarBuilder b, public static void buildStandardExample(YamlGrammarBuilder b, GrammarRuleKey example, GrammarRuleKey description) { b.rule(example).is(b.object( - b.property("summary", b.string()), - b.property("description", description), + 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, 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 bc14192..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 @@ -66,6 +66,7 @@ public static OpenApiVisitorContext createContext(File file, boolean isV2, boole 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; 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 6cb65e1..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,20 +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") - ); - + boolean isV3 = isV3Version(openapiNode); boolean isV31 = !openapiNode.isMissing() && openapiNode.getTokenValue().equals("3.1.0"); boolean isV32 = !openapiNode.isMissing() && openapiNode.getTokenValue().equals("3.2.0"); - YamlParser targetParser = null; - if (isV2) targetParser = OpenApiParser.createV2(configuration); - if (isV3) targetParser = OpenApiParser.createV3(configuration); - if (isV31) targetParser = OpenApiParser.createV31(configuration); - if (isV32) targetParser = OpenApiParser.createV32(configuration); + YamlParser targetParser = selectParser(isV2, isV3, isV31, isV32); if (targetParser == null) return; visitorContext = new OpenApiVisitorContext(targetParser.parse(content), targetParser.getIssues(), openApiFile); From 245692a51266240be37706cff644b0876b7bdb6f Mon Sep 17 00:00:00 2001 From: Melsy Huamani Date: Wed, 13 May 2026 20:28:22 -0500 Subject: [PATCH 06/15] fix: resolve language suffix conflict with built-in yaml and json support --- .../apitools/dosonarapi/checks/CheckList.java | 3 ++ .../apitools/dosonarapi/plugin/OpenApi.java | 34 +--------------- .../dosonarapi/plugin/OpenApiAnalyzer.java | 5 +++ .../dosonarapi/plugin/OpenApiChecks.java | 3 +- .../dosonarapi/plugin/OpenApiPlugin.java | 40 ------------------- .../plugin/OpenApiProfileDefinition.java | 12 ++++-- .../plugin/OpenApiRulesDefinition.java | 7 +++- .../plugin/OpenApiScannerSensor.java | 5 ++- .../plugin/OpenApiProfileDefinitionTest.java | 35 ++++++++-------- .../plugin/OpenApiScannerSensorTest.java | 5 ++- .../plugin/cpd/OpenApiCpdAnalyzerTest.java | 4 +- 11 files changed, 50 insertions(+), 103 deletions(-) diff --git a/openapi-checks/src/main/java/org/apiaddicts/apitools/dosonarapi/checks/CheckList.java b/openapi-checks/src/main/java/org/apiaddicts/apitools/dosonarapi/checks/CheckList.java index 83e30d6..19411a8 100644 --- a/openapi-checks/src/main/java/org/apiaddicts/apitools/dosonarapi/checks/CheckList.java +++ b/openapi-checks/src/main/java/org/apiaddicts/apitools/dosonarapi/checks/CheckList.java @@ -24,6 +24,9 @@ public final class CheckList { public static final String REPOSITORY_KEY = "openapi"; + public static final String JSON_REPOSITORY_KEY = "openapi-json"; + public static final String YAML_LANGUAGE = "yaml"; + public static final String JSON_LANGUAGE = "json"; private CheckList() { } diff --git a/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApi.java b/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApi.java index 723749e..83dca61 100644 --- a/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApi.java +++ b/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApi.java @@ -19,40 +19,10 @@ */ package org.apiaddicts.apitools.dosonarapi.plugin; -import java.util.ArrayList; -import java.util.List; -import org.apache.commons.lang.StringUtils; -import org.sonar.api.config.Configuration; -import org.sonar.api.resources.AbstractLanguage; - -@java.lang.SuppressWarnings("squid:S2160") // purposely not redefining equals() to ignore Configuration differences -public class OpenApi extends AbstractLanguage { +public final class OpenApi { public static final String KEY = "openapi"; - private static final String[] DEFAULT_FILE_SUFFIXES = {"yaml"}; - - private Configuration settings; - - public OpenApi(Configuration settings) { - super(KEY, "OpenAPI"); - this.settings = settings; - } - - private static String[] filterEmptyStrings(String[] stringArray) { - List nonEmptyStrings = new ArrayList<>(); - for (String string : stringArray) { - if (StringUtils.isNotBlank(string.trim())) { - nonEmptyStrings.add(string.trim()); - } - } - return nonEmptyStrings.toArray(new String[0]); - } - - @Override - public String[] getFileSuffixes() { - String[] suffixes = filterEmptyStrings(settings.getStringArray(OpenApiPlugin.FILE_SUFFIXES_KEY)); - return suffixes.length == 0 ? OpenApi.DEFAULT_FILE_SUFFIXES : suffixes; - } + private OpenApi() {} } 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 46bbd4d..8ed5257 100644 --- a/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiAnalyzer.java +++ b/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiAnalyzer.java @@ -155,6 +155,11 @@ private void scanFile(InputFile inputFile) { visitorContext = new OpenApiVisitorContext(openApiFile, re); LOG.error("Unable to parse file in i/o: " + inputFile.filename() + "\"\n" + ex.getMessage()); dumpException(re, inputFile); + } catch (RuntimeException ex) { + RecognitionException re = new RecognitionException(0, ex.getMessage()); + visitorContext = new OpenApiVisitorContext(openApiFile, re); + LOG.error("Unexpected error parsing file: " + inputFile.filename() + "\"\n" + ex.getMessage()); + dumpException(re, inputFile); } for (OpenApiCheck check : checks.all()) { diff --git a/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiChecks.java b/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiChecks.java index f3c1843..d00f01e 100644 --- a/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiChecks.java +++ b/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiChecks.java @@ -59,7 +59,8 @@ public OpenApiChecks addCustomChecks(@Nullable OpenApiCustomRuleRepository[] cus if (customRuleRepositories != null) { for (OpenApiCustomRuleRepository ruleRepository : customRuleRepositories) { - if (!ruleRepository.repositoryKey().equals(CheckList.REPOSITORY_KEY)) { + if (!ruleRepository.repositoryKey().equals(CheckList.REPOSITORY_KEY) && + !ruleRepository.repositoryKey().equals(CheckList.JSON_REPOSITORY_KEY)) { addChecks(ruleRepository.repositoryKey(), new ArrayList<>(ruleRepository.checkClasses())); } } diff --git a/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiPlugin.java b/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiPlugin.java index ca5ec80..07f3b38 100644 --- a/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiPlugin.java +++ b/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiPlugin.java @@ -20,53 +20,13 @@ package org.apiaddicts.apitools.dosonarapi.plugin; import org.sonar.api.Plugin; -import org.sonar.api.config.PropertyDefinition; -import org.sonar.api.resources.Qualifiers; import org.apiaddicts.apitools.dosonarapi.openapi.metrics.OpenApiMetrics; public class OpenApiPlugin implements Plugin { - public static final String FILE_SUFFIXES_KEY = "sonar.openapi.file.suffixes"; - public static final String OPENAPI_CATEGORY = "OpenApi"; - // Subcategories - private static final String GENERAL = "General"; - @Override public void define(Context context) { - context.addExtensions( - PropertyDefinition.builder(FILE_SUFFIXES_KEY) - .index(10) - .name("File Suffixes") - .description("A list of suffixes of OpenAPI files to analyze.") - .category(OPENAPI_CATEGORY) - .subCategory(GENERAL) - .onQualifiers(Qualifiers.PROJECT) - .multiValues(true) - .defaultValue("yaml,json") - .build(), -/* PropertyDefinition.builder(OpenApiProperties.V2_PATH_KEY) - .index(11) - .name("Paths to OpenAPI v2 contract(s)") - .description("Path to OpenAPI v2 contracts. Ant patterns are accepted for relative path. The contracts can be in JSON or in YAML.") - .category(OPENAPI_CATEGORY) - .subCategory(GENERAL) - .onQualifiers(Qualifiers.PROJECT) - .multiValues(true) - .defaultValue(OpenApiProperties.DEFAULT_V2_PATH) - .build(), - PropertyDefinition.builder(OpenApiProperties.V3_PATH_KEY) - .index(11) - .name("Paths to OpenAPI v3 contract(s)") - .description("Path to OpenAPI v3 contracts. Ant patterns are accepted for relative path. The contracts can be in JSON or in YAML.") - .category(OPENAPI_CATEGORY) - .subCategory(GENERAL) - .onQualifiers(Qualifiers.PROJECT) - .multiValues(true) - .defaultValue(OpenApiProperties.DEFAULT_V3_PATH) - .build(),*/ - OpenApi.class, - OpenApiProfileDefinition.class, OpenApiScannerSensor.class, OpenApiRulesDefinition.class, OpenApiMetrics.class); diff --git a/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiProfileDefinition.java b/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiProfileDefinition.java index 9ea793b..1e6775f 100644 --- a/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiProfileDefinition.java +++ b/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiProfileDefinition.java @@ -25,15 +25,19 @@ import org.apiaddicts.apitools.dosonarapi.checks.CheckList; public class OpenApiProfileDefinition implements BuiltInQualityProfilesDefinition { - public static final String SONAR_WAY_PROFILE = "Sonar way"; + public static final String SONAR_WAY_PROFILE = "Sonar way OpenAPI"; @Override public void define(BuiltInQualityProfilesDefinition.Context context) { - NewBuiltInQualityProfile profile = context.createBuiltInQualityProfile(SONAR_WAY_PROFILE, OpenApi.KEY); - profile.setDefault(true); + createProfile(context, CheckList.YAML_LANGUAGE, CheckList.REPOSITORY_KEY); + createProfile(context, CheckList.JSON_LANGUAGE, CheckList.JSON_REPOSITORY_KEY); + } + + private void createProfile(BuiltInQualityProfilesDefinition.Context context, String language, String repositoryKey) { + NewBuiltInQualityProfile profile = context.createBuiltInQualityProfile(SONAR_WAY_PROFILE, language); for (Class check : CheckList.getChecks()) { Rule annotation = AnnotationUtils.getAnnotation(check, Rule.class); - profile.activateRule(CheckList.REPOSITORY_KEY, annotation.key()); + profile.activateRule(repositoryKey, annotation.key()); } profile.done(); } diff --git a/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiRulesDefinition.java b/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiRulesDefinition.java index a296e34..61b6a6d 100644 --- a/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiRulesDefinition.java +++ b/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiRulesDefinition.java @@ -39,8 +39,13 @@ private static RuleMetadataLoader getRuleMetadataLoader() { @Override public void define(Context context) { + createRepository(context, repositoryKey(), CheckList.YAML_LANGUAGE); + createRepository(context, CheckList.JSON_REPOSITORY_KEY, CheckList.JSON_LANGUAGE); + } + + private void createRepository(Context context, String key, String language) { NewRepository repository = context - .createRepository(repositoryKey(), OpenApi.KEY) + .createRepository(key, language) .setName(REPOSITORY_NAME); getRuleMetadataLoader().addRulesByAnnotatedClass(repository, checkClasses()); diff --git a/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiScannerSensor.java b/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiScannerSensor.java index a6b29f3..82b1809 100644 --- a/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiScannerSensor.java +++ b/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiScannerSensor.java @@ -49,6 +49,7 @@ public OpenApiScannerSensor(CheckFactory checkFactory, FileLinesContextFactory f public OpenApiScannerSensor(CheckFactory checkFactory, FileLinesContextFactory fileLinesContextFactory, NoSonarFilter noSonarFilter, @Nullable OpenApiCustomRuleRepository[] customRuleRepositories) { this.checks = OpenApiChecks.createOpenApiCheck(checkFactory) .addChecks(CheckList.REPOSITORY_KEY, CheckList.getChecks()) + .addChecks(CheckList.JSON_REPOSITORY_KEY, CheckList.getChecks()) .addCustomChecks(customRuleRepositories); this.fileLinesContextFactory = fileLinesContextFactory; this.noSonarFilter = noSonarFilter; @@ -58,7 +59,7 @@ public OpenApiScannerSensor(CheckFactory checkFactory, FileLinesContextFactory f public void describe(SensorDescriptor descriptor) { descriptor.name("OpenAPI Scanner Sensor") .onlyOnFileType(InputFile.Type.MAIN) - .onlyOnLanguage(OpenApi.KEY); + .onlyOnLanguages(CheckList.YAML_LANGUAGE, CheckList.JSON_LANGUAGE); } @Override @@ -70,7 +71,7 @@ public void execute(SensorContext context) { public void scanFiles(SensorContext context, FilePredicates p) { Iterable it = context.fileSystem().inputFiles( p.and(p.hasType(InputFile.Type.MAIN), - p.hasLanguage(OpenApi.KEY) + p.or(p.hasLanguage(CheckList.YAML_LANGUAGE), p.hasLanguage(CheckList.JSON_LANGUAGE)) )); List list = new ArrayList<>(); it.forEach(list::add); diff --git a/sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiProfileDefinitionTest.java b/sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiProfileDefinitionTest.java index b1f29f5..9c70c58 100644 --- a/sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiProfileDefinitionTest.java +++ b/sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiProfileDefinitionTest.java @@ -20,36 +20,33 @@ package org.apiaddicts.apitools.dosonarapi.plugin; import org.junit.Test; -import org.mockito.Mockito; +import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition; import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition.Context; -import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition.NewBuiltInQualityProfile; +import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition.BuiltInQualityProfile; import org.apiaddicts.apitools.dosonarapi.checks.CheckList; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import static org.assertj.core.api.Assertions.assertThat; import static org.apiaddicts.apitools.dosonarapi.plugin.OpenApiProfileDefinition.SONAR_WAY_PROFILE; public class OpenApiProfileDefinitionTest { - private static Context context(NewBuiltInQualityProfile profile) { - Context context = mock(Context.class); - when(context.createBuiltInQualityProfile(anyString(), anyString())).thenReturn(profile); - return context; - } @Test - public void should_create_sonar_way_profile() { + public void should_create_yaml_and_json_profiles() { OpenApiProfileDefinition definition = new OpenApiProfileDefinition(); - NewBuiltInQualityProfile profile = mock(NewBuiltInQualityProfile.class); - Context context = context(profile); + Context context = new BuiltInQualityProfilesDefinition.Context(); definition.define(context); - ; - verify(context).createBuiltInQualityProfile(SONAR_WAY_PROFILE, OpenApi.KEY); - verify(profile).setDefault(true); - verify(profile, Mockito.atLeast(2)).activateRule(eq(CheckList.REPOSITORY_KEY), anyString()); + BuiltInQualityProfile yamlProfile = context.profile(CheckList.YAML_LANGUAGE, SONAR_WAY_PROFILE); + assertThat(yamlProfile).isNotNull(); + assertThat(yamlProfile.language()).isEqualTo(CheckList.YAML_LANGUAGE); + assertThat(yamlProfile.rules()).hasSizeGreaterThanOrEqualTo(2); + assertThat(yamlProfile.rules()).allMatch(r -> r.repoKey().equals(CheckList.REPOSITORY_KEY)); + + BuiltInQualityProfile jsonProfile = context.profile(CheckList.JSON_LANGUAGE, SONAR_WAY_PROFILE); + assertThat(jsonProfile).isNotNull(); + assertThat(jsonProfile.language()).isEqualTo(CheckList.JSON_LANGUAGE); + assertThat(jsonProfile.rules()).hasSizeGreaterThanOrEqualTo(2); + assertThat(jsonProfile.rules()).allMatch(r -> r.repoKey().equals(CheckList.JSON_REPOSITORY_KEY)); } } diff --git a/sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiScannerSensorTest.java b/sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiScannerSensorTest.java index ac817e4..d93fa28 100644 --- a/sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiScannerSensorTest.java +++ b/sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiScannerSensorTest.java @@ -77,7 +77,7 @@ public void sensor_descriptor() { sensor().describe(descriptor); assertThat(descriptor.name()).isEqualTo("OpenAPI Scanner Sensor"); - assertThat(descriptor.languages()).containsOnly("openapi"); + assertThat(descriptor.languages()).containsOnly("yaml", "json"); assertThat(descriptor.type()).isEqualTo(InputFile.Type.MAIN); } @@ -223,11 +223,12 @@ private OpenApiScannerSensor sensor() { } private InputFile inputFile(String name) { + String language = name.endsWith(".json") ? CheckList.JSON_LANGUAGE : CheckList.YAML_LANGUAGE; DefaultInputFile inputFile = TestInputFileBuilder.create("moduleKey", name) .setModuleBaseDir(baseDir) .setCharset(StandardCharsets.UTF_8) .setType(InputFile.Type.MAIN) - .setLanguage(OpenApi.KEY) + .setLanguage(language) .initMetadata(TestUtils.fileContent(new File(baseDir.toFile(), name), StandardCharsets.UTF_8)) .build(); context.fileSystem().add(inputFile); diff --git a/sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/cpd/OpenApiCpdAnalyzerTest.java b/sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/cpd/OpenApiCpdAnalyzerTest.java index 1f4aa36..049e8bc 100644 --- a/sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/cpd/OpenApiCpdAnalyzerTest.java +++ b/sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/cpd/OpenApiCpdAnalyzerTest.java @@ -25,7 +25,7 @@ import java.util.List; import java.util.stream.Collectors; -import org.apiaddicts.apitools.dosonarapi.plugin.OpenApi; +import org.apiaddicts.apitools.dosonarapi.checks.CheckList; import org.apiaddicts.apitools.dosonarapi.plugin.TestUtils; import org.junit.Test; import org.sonar.api.batch.fs.InputFile; @@ -75,7 +75,7 @@ private DefaultInputFile inputFile(String fileName) { .setModuleBaseDir(Paths.get(BASE_DIR)) .setCharset(UTF_8) .setType(InputFile.Type.MAIN) - .setLanguage(OpenApi.KEY) + .setLanguage(CheckList.YAML_LANGUAGE) .initMetadata(TestUtils.fileContent(file, Charsets.UTF_8)) .build(); From 333cc426ef3ad9da0796e0d2e2918d8fb8493e57 Mon Sep 17 00:00:00 2001 From: Melsy Huamani Date: Thu, 14 May 2026 10:35:54 -0500 Subject: [PATCH 07/15] fix: duplicated sonar issues for yaml and json --- .../apitools/dosonarapi/checks/CheckList.java | 2 +- .../dosonarapi/api/v31/OpenApi31Grammar.java | 1 + .../apitools/dosonarapi/api/v31/InfoTest.java | 47 +++++++++++++++++ .../models/v31/info_with_summary.yaml | 3 ++ .../dosonarapi/plugin/OpenApiChecks.java | 30 ++++++++++- .../plugin/OpenApiProfileDefinition.java | 2 +- .../plugin/OpenApiRulesDefinition.java | 2 +- .../plugin/OpenApiScannerSensor.java | 38 ++++++++------ .../plugin/OpenApiProfileDefinitionTest.java | 2 +- .../plugin/OpenApiScannerSensorTest.java | 45 +++++++++++++--- .../src/test/resources/sensor/file1.json | 51 +++++++++++++++++++ 11 files changed, 195 insertions(+), 28 deletions(-) create mode 100644 openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/api/v31/InfoTest.java create mode 100644 openapi-front-end/src/test/resources/models/v31/info_with_summary.yaml create mode 100644 sonar-openapi-plugin/src/test/resources/sensor/file1.json diff --git a/openapi-checks/src/main/java/org/apiaddicts/apitools/dosonarapi/checks/CheckList.java b/openapi-checks/src/main/java/org/apiaddicts/apitools/dosonarapi/checks/CheckList.java index 19411a8..2933c5f 100644 --- a/openapi-checks/src/main/java/org/apiaddicts/apitools/dosonarapi/checks/CheckList.java +++ b/openapi-checks/src/main/java/org/apiaddicts/apitools/dosonarapi/checks/CheckList.java @@ -23,7 +23,7 @@ import java.util.List; public final class CheckList { - public static final String REPOSITORY_KEY = "openapi"; + public static final String YAML_REPOSITORY_KEY = "openapi-yaml"; public static final String JSON_REPOSITORY_KEY = "openapi-json"; public static final String YAML_LANGUAGE = "yaml"; public static final String JSON_LANGUAGE = "json"; 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 a5ea9f2..b359e92 100644 --- a/openapi-front-end/src/main/java/org/apiaddicts/apitools/dosonarapi/api/v31/OpenApi31Grammar.java +++ b/openapi-front-end/src/main/java/org/apiaddicts/apitools/dosonarapi/api/v31/OpenApi31Grammar.java @@ -263,6 +263,7 @@ private static void buildServer(YamlGrammarBuilder b) { private static void buildInfo(YamlGrammarBuilder b) { b.rule(INFO).is(b.object( b.mandatoryProperty("title", b.string()), + b.property("summary", b.string()), b.property("description", DESCRIPTION), b.property("termsOfService", b.string()), b.property("contact", CONTACT), diff --git a/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/api/v31/InfoTest.java b/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/api/v31/InfoTest.java new file mode 100644 index 0000000..356dfec --- /dev/null +++ b/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/api/v31/InfoTest.java @@ -0,0 +1,47 @@ +/* + * doSonarAPI: SonarQube OpenAPI Plugin + * Copyright (C) 2021-2022 Apiaddicts + * contacta AT apiaddicts DOT org + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.apiaddicts.apitools.dosonarapi.api.v31; + +import org.apiaddicts.apitools.dosonarapi.openapi.BaseNodeTest; +import org.junit.Test; +import org.apiaddicts.apitools.dosonarapi.sslr.yaml.grammar.JsonNode; + + +public class InfoTest extends BaseNodeTest { + + @Test + public void can_parse_info_with_summary() { + JsonNode node = parseResource(OpenApi31Grammar.INFO, "/models/v31/info_with_summary.yaml"); + + assertEquals("My API", node, "/title"); + assertEquals("A short summary of the API", node, "/summary"); + assertEquals("1.0.0", node, "/version"); + assertMissing(node.at("/description")); + } + + @Test + public void can_parse_info_without_summary() { + JsonNode node = parseResource(OpenApi31Grammar.INFO, "/models/shared/info/minimal.yaml"); + + assertMissing(node.at("/summary")); + assertEquals("simple model", node, "/title"); + assertEquals("1.0.0", node, "/version"); + } +} diff --git a/openapi-front-end/src/test/resources/models/v31/info_with_summary.yaml b/openapi-front-end/src/test/resources/models/v31/info_with_summary.yaml new file mode 100644 index 0000000..194cf4d --- /dev/null +++ b/openapi-front-end/src/test/resources/models/v31/info_with_summary.yaml @@ -0,0 +1,3 @@ +title: My API +summary: A short summary of the API +version: 1.0.0 diff --git a/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiChecks.java b/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiChecks.java index d00f01e..ae2c35e 100644 --- a/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiChecks.java +++ b/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiChecks.java @@ -59,7 +59,7 @@ public OpenApiChecks addCustomChecks(@Nullable OpenApiCustomRuleRepository[] cus if (customRuleRepositories != null) { for (OpenApiCustomRuleRepository ruleRepository : customRuleRepositories) { - if (!ruleRepository.repositoryKey().equals(CheckList.REPOSITORY_KEY) && + if (!ruleRepository.repositoryKey().equals(CheckList.YAML_REPOSITORY_KEY) && !ruleRepository.repositoryKey().equals(CheckList.JSON_REPOSITORY_KEY)) { addChecks(ruleRepository.repositoryKey(), new ArrayList<>(ruleRepository.checkClasses())); } @@ -69,6 +69,34 @@ public OpenApiChecks addCustomChecks(@Nullable OpenApiCustomRuleRepository[] cus return this; } + public OpenApiChecks addCustomYamlChecks(@Nullable OpenApiCustomRuleRepository[] customRuleRepositories) { + if (customRuleRepositories != null) { + for (OpenApiCustomRuleRepository ruleRepository : customRuleRepositories) { + String key = ruleRepository.repositoryKey(); + if (!key.equals(CheckList.YAML_REPOSITORY_KEY) && + !key.equals(CheckList.JSON_REPOSITORY_KEY) && + !key.endsWith("-json")) { + addChecks(key, new ArrayList<>(ruleRepository.checkClasses())); + } + } + } + return this; + } + + public OpenApiChecks addCustomJsonChecks(@Nullable OpenApiCustomRuleRepository[] customRuleRepositories) { + if (customRuleRepositories != null) { + for (OpenApiCustomRuleRepository ruleRepository : customRuleRepositories) { + String key = ruleRepository.repositoryKey(); + if (!key.equals(CheckList.YAML_REPOSITORY_KEY) && + !key.equals(CheckList.JSON_REPOSITORY_KEY) && + key.endsWith("-json")) { + addChecks(key, new ArrayList<>(ruleRepository.checkClasses())); + } + } + } + return this; + } + public List all() { List allVisitors = new ArrayList<>(); diff --git a/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiProfileDefinition.java b/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiProfileDefinition.java index 1e6775f..320d007 100644 --- a/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiProfileDefinition.java +++ b/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiProfileDefinition.java @@ -29,7 +29,7 @@ public class OpenApiProfileDefinition implements BuiltInQualityProfilesDefinitio @Override public void define(BuiltInQualityProfilesDefinition.Context context) { - createProfile(context, CheckList.YAML_LANGUAGE, CheckList.REPOSITORY_KEY); + createProfile(context, CheckList.YAML_LANGUAGE, CheckList.YAML_REPOSITORY_KEY); createProfile(context, CheckList.JSON_LANGUAGE, CheckList.JSON_REPOSITORY_KEY); } diff --git a/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiRulesDefinition.java b/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiRulesDefinition.java index 61b6a6d..4794f9a 100644 --- a/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiRulesDefinition.java +++ b/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiRulesDefinition.java @@ -59,7 +59,7 @@ private void createRepository(Context context, String key, String language) { @Override public String repositoryKey() { - return CheckList.REPOSITORY_KEY; + return CheckList.YAML_REPOSITORY_KEY; } @Override diff --git a/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiScannerSensor.java b/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiScannerSensor.java index 82b1809..fd47dbe 100644 --- a/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiScannerSensor.java +++ b/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiScannerSensor.java @@ -38,7 +38,8 @@ public class OpenApiScannerSensor implements Sensor { private static final Logger LOGGER = Loggers.get(OpenApiScannerSensor.class); - private final OpenApiChecks checks; + private final OpenApiChecks yamlChecks; + private final OpenApiChecks jsonChecks; private FileLinesContextFactory fileLinesContextFactory; private final NoSonarFilter noSonarFilter; @@ -47,10 +48,12 @@ public OpenApiScannerSensor(CheckFactory checkFactory, FileLinesContextFactory f } public OpenApiScannerSensor(CheckFactory checkFactory, FileLinesContextFactory fileLinesContextFactory, NoSonarFilter noSonarFilter, @Nullable OpenApiCustomRuleRepository[] customRuleRepositories) { - this.checks = OpenApiChecks.createOpenApiCheck(checkFactory) - .addChecks(CheckList.REPOSITORY_KEY, CheckList.getChecks()) + this.yamlChecks = OpenApiChecks.createOpenApiCheck(checkFactory) + .addChecks(CheckList.YAML_REPOSITORY_KEY, CheckList.getChecks()) + .addCustomYamlChecks(customRuleRepositories); + this.jsonChecks = OpenApiChecks.createOpenApiCheck(checkFactory) .addChecks(CheckList.JSON_REPOSITORY_KEY, CheckList.getChecks()) - .addCustomChecks(customRuleRepositories); + .addCustomJsonChecks(customRuleRepositories); this.fileLinesContextFactory = fileLinesContextFactory; this.noSonarFilter = noSonarFilter; } @@ -69,18 +72,23 @@ public void execute(SensorContext context) { } public void scanFiles(SensorContext context, FilePredicates p) { - Iterable it = context.fileSystem().inputFiles( - p.and(p.hasType(InputFile.Type.MAIN), - p.or(p.hasLanguage(CheckList.YAML_LANGUAGE), p.hasLanguage(CheckList.JSON_LANGUAGE)) - )); - List list = new ArrayList<>(); - it.forEach(list::add); - List inputFiles = Collections.unmodifiableList(list); + List yamlFiles = new ArrayList<>(); + context.fileSystem().inputFiles( + p.and(p.hasType(InputFile.Type.MAIN), p.hasLanguage(CheckList.YAML_LANGUAGE)) + ).forEach(yamlFiles::add); - if (!inputFiles.isEmpty()) { - OpenApiAnalyzer scanner = new OpenApiAnalyzer(context, checks, fileLinesContextFactory, noSonarFilter, inputFiles/*, isV2*/); - LOGGER.info("OpenAPI Scanner called for the following files: {}.", inputFiles); - scanner.scanFiles(); + List jsonFiles = new ArrayList<>(); + context.fileSystem().inputFiles( + p.and(p.hasType(InputFile.Type.MAIN), p.hasLanguage(CheckList.JSON_LANGUAGE)) + ).forEach(jsonFiles::add); + + if (!yamlFiles.isEmpty()) { + LOGGER.info("OpenAPI Scanner called for yaml files: {}.", yamlFiles); + new OpenApiAnalyzer(context, yamlChecks, fileLinesContextFactory, noSonarFilter, Collections.unmodifiableList(yamlFiles)).scanFiles(); + } + if (!jsonFiles.isEmpty()) { + LOGGER.info("OpenAPI Scanner called for json files: {}.", jsonFiles); + new OpenApiAnalyzer(context, jsonChecks, fileLinesContextFactory, noSonarFilter, Collections.unmodifiableList(jsonFiles)).scanFiles(); } } } diff --git a/sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiProfileDefinitionTest.java b/sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiProfileDefinitionTest.java index 9c70c58..80d9143 100644 --- a/sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiProfileDefinitionTest.java +++ b/sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiProfileDefinitionTest.java @@ -41,7 +41,7 @@ public void should_create_yaml_and_json_profiles() { assertThat(yamlProfile).isNotNull(); assertThat(yamlProfile.language()).isEqualTo(CheckList.YAML_LANGUAGE); assertThat(yamlProfile.rules()).hasSizeGreaterThanOrEqualTo(2); - assertThat(yamlProfile.rules()).allMatch(r -> r.repoKey().equals(CheckList.REPOSITORY_KEY)); + assertThat(yamlProfile.rules()).allMatch(r -> r.repoKey().equals(CheckList.YAML_REPOSITORY_KEY)); BuiltInQualityProfile jsonProfile = context.profile(CheckList.JSON_LANGUAGE, SONAR_WAY_PROFILE); assertThat(jsonProfile).isNotNull(); diff --git a/sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiScannerSensorTest.java b/sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiScannerSensorTest.java index d93fa28..d9dc59d 100644 --- a/sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiScannerSensorTest.java +++ b/sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiScannerSensorTest.java @@ -84,7 +84,7 @@ public void sensor_descriptor() { @Test public void test_issues() { activeRules = (new ActiveRulesBuilder()) - .create(RuleKey.of(CheckList.REPOSITORY_KEY, "PathMaskerading")) + .create(RuleKey.of(CheckList.YAML_REPOSITORY_KEY, "PathMaskerading")) .activate() .build(); @@ -115,11 +115,40 @@ public void test_issues() { assertThat(context.allAnalysisErrors()).isEmpty(); } + @Test + public void test_json_issues_single_per_violation() { + activeRules = (new ActiveRulesBuilder()) + .create(RuleKey.of(CheckList.JSON_REPOSITORY_KEY, "PathMaskerading")) + .activate() + .build(); + + InputFile inputFile = inputFile("file1.json"); + sensor().execute(context); + + assertThat(context.allIssues()).hasSize(1); + Issue issue = Iterables.get(context.allIssues(), 0); + assertThat(issue.ruleKey().repository()).isEqualTo(CheckList.JSON_REPOSITORY_KEY); + assertThat(context.allAnalysisErrors()).isEmpty(); + } + + @Test + public void test_yaml_rules_do_not_fire_on_json_files() { + activeRules = (new ActiveRulesBuilder()) + .create(RuleKey.of(CheckList.YAML_REPOSITORY_KEY, "PathMaskerading")) + .activate() + .build(); + + inputFile("file1.json"); + sensor().execute(context); + + assertThat(context.allIssues()).isEmpty(); + } + @Test public void parse_error() { inputFile("parse-error.yaml"); activeRules = (new ActiveRulesBuilder()) - .create(RuleKey.of(CheckList.REPOSITORY_KEY, ParsingErrorCheck.CHECK_KEY)) + .create(RuleKey.of(CheckList.YAML_REPOSITORY_KEY, ParsingErrorCheck.CHECK_KEY)) .activate() .build(); sensor().execute(context); @@ -136,7 +165,7 @@ public void parse_error() { public void parse_openapi3_headers_ref() { inputFile("headers_ref.yaml"); activeRules = (new ActiveRulesBuilder()) - .create(RuleKey.of(CheckList.REPOSITORY_KEY, ParsingErrorCheck.CHECK_KEY)) + .create(RuleKey.of(CheckList.YAML_REPOSITORY_KEY, ParsingErrorCheck.CHECK_KEY)) .activate() .build(); sensor().execute(context); @@ -146,7 +175,7 @@ public void parse_openapi3_headers_ref() { public void parse_yaml_break_comment_ok() { inputFile("parse-yaml.yaml"); activeRules = (new ActiveRulesBuilder()) - .create(RuleKey.of(CheckList.REPOSITORY_KEY, ParsingErrorCheck.CHECK_KEY)) + .create(RuleKey.of(CheckList.YAML_REPOSITORY_KEY, ParsingErrorCheck.CHECK_KEY)) .activate() .build(); sensor().execute(context); @@ -158,7 +187,7 @@ public void parse_yaml_break_comment_ok() { public void parse_yaml_slash_ok() { inputFile("parse-error-slash.json"); activeRules = (new ActiveRulesBuilder()) - .create(RuleKey.of(CheckList.REPOSITORY_KEY, ParsingErrorCheck.CHECK_KEY)) + .create(RuleKey.of(CheckList.YAML_REPOSITORY_KEY, ParsingErrorCheck.CHECK_KEY)) .activate() .build(); sensor().execute(context); @@ -170,7 +199,7 @@ public void parse_yaml_slash_ok() { public void parse_yaml_tabs_ok() { inputFile("parse-error-tabs.json"); activeRules = (new ActiveRulesBuilder()) - .create(RuleKey.of(CheckList.REPOSITORY_KEY, ParsingErrorCheck.CHECK_KEY)) + .create(RuleKey.of(CheckList.YAML_REPOSITORY_KEY, ParsingErrorCheck.CHECK_KEY)) .activate() .build(); sensor().execute(context); @@ -182,7 +211,7 @@ public void parse_yaml_tabs_ok() { public void parse_yaml_tabs_ok_31() { inputFile("file2.yaml"); activeRules = (new ActiveRulesBuilder()) - .create(RuleKey.of(CheckList.REPOSITORY_KEY, ParsingErrorCheck.CHECK_KEY)) + .create(RuleKey.of(CheckList.YAML_REPOSITORY_KEY, ParsingErrorCheck.CHECK_KEY)) .activate() .build(); sensor().execute(context); @@ -197,7 +226,7 @@ public void test_folder() { for (String file: files) { context = SensorContextTester.create(baseDir); inputFile(file); - activeRules = (new ActiveRulesBuilder()).create(RuleKey.of(CheckList.REPOSITORY_KEY, ParsingErrorCheck.CHECK_KEY)) + activeRules = (new ActiveRulesBuilder()).create(RuleKey.of(CheckList.YAML_REPOSITORY_KEY, ParsingErrorCheck.CHECK_KEY)) .activate().build(); sensor().execute(context); if (!context.allIssues().isEmpty() || !context.allAnalysisErrors().isEmpty()) errorFiles.add(file); diff --git a/sonar-openapi-plugin/src/test/resources/sensor/file1.json b/sonar-openapi-plugin/src/test/resources/sensor/file1.json new file mode 100644 index 0000000..5178058 --- /dev/null +++ b/sonar-openapi-plugin/src/test/resources/sensor/file1.json @@ -0,0 +1,51 @@ +{ + "openapi": "3.0.1", + "info": { + "version": "1.0.0", + "title": "Swagger Petstore" + }, + "paths": { + "/pets/{petId}": { + "get": { + "responses": { + "200": { + "description": "success", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Pet" + } + } + } + } + } + } + } + }, + "/pets/1234": { + "get": { + "responses": { + "200": { + "description": "success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "Pet": { + "type": "object" + } + } + } +} From 7fa47411c10793be44d25625b1edb464ca50e2d5 Mon Sep 17 00:00:00 2001 From: Melsy Huamani Date: Thu, 14 May 2026 14:56:46 -0500 Subject: [PATCH 08/15] add tests and update changelog and readme --- .vscode/settings.json | 6 + CHANGELOG.md | 6 + README.md | 2 +- its/pom.xml | 2 +- openapi-checks/pom.xml | 2 +- .../dosonarapi/checks/CheckListTest.java | 57 ++++++ .../checks/DocumentedTagCheckTest.java | 8 +- .../checks/ParsingErrorCheckTest.java | 32 +++ openapi-front-end/pom.xml | 2 +- .../dosonarapi/api/IssueLocationTest.java | 39 ++++ .../dosonarapi/api/OpenApiCheckTest.java | 42 ++++ .../dosonarapi/api/PreciseIssueTest.java | 80 +++++++- .../dosonarapi/api/ResourceCheckTest.java | 191 ++++++++++++++++++ .../api/TestOpenApiVisitorRunnerTest.java | 82 ++++++++ .../openapi/OpenApiConfigurationTest.java | 42 ++++ .../openapi/metrics/FileMetricsTest.java | 19 +- .../openapi/metrics/OpenApiMetricsTest.java | 55 +++++ .../openapi/parser/OpenApiParserTest.java | 96 +++++++++ .../src/test/resources/petstore-v31.yaml | 10 + .../src/test/resources/petstore-v32.yaml | 10 + openapi-test-tools/pom.xml | 7 +- .../dosonarapi/OpenApiCheckVerifierTest.java | 112 ++++++++++ .../apitools/dosonarapi/TestNoOpCheck.java | 32 +++ .../dosonarapi/TestPathsReportCheck.java | 39 ++++ .../resources/test-verify-noncompliant.yaml | 11 + .../src/test/resources/test-verify.yaml | 11 + pom.xml | 2 +- sonar-openapi-plugin/pom.xml | 2 +- .../plugin/MissingPropertyExceptionTest.java | 40 ++++ .../dosonarapi/plugin/OpenApiChecksTest.java | 147 ++++++++++++++ .../dosonarapi/plugin/OpenApiPluginTest.java | 41 ++++ .../plugin/OpenApiRulesDefinitionTest.java | 70 +++++++ .../plugin/SonarQubeOpenApiFileTest.java | 80 ++++++++ 33 files changed, 1360 insertions(+), 17 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 openapi-checks/src/test/java/org/apiaddicts/apitools/dosonarapi/checks/CheckListTest.java create mode 100644 openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/api/ResourceCheckTest.java create mode 100644 openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/api/TestOpenApiVisitorRunnerTest.java create mode 100644 openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/openapi/OpenApiConfigurationTest.java create mode 100644 openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/openapi/metrics/OpenApiMetricsTest.java create mode 100644 openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/openapi/parser/OpenApiParserTest.java create mode 100644 openapi-front-end/src/test/resources/petstore-v31.yaml create mode 100644 openapi-front-end/src/test/resources/petstore-v32.yaml create mode 100644 openapi-test-tools/src/test/java/org/apiaddicts/apitools/dosonarapi/TestNoOpCheck.java create mode 100644 openapi-test-tools/src/test/java/org/apiaddicts/apitools/dosonarapi/TestPathsReportCheck.java create mode 100644 openapi-test-tools/src/test/resources/test-verify-noncompliant.yaml create mode 100644 openapi-test-tools/src/test/resources/test-verify.yaml create mode 100644 sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/MissingPropertyExceptionTest.java create mode 100644 sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiChecksTest.java create mode 100644 sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiPluginTest.java create mode 100644 sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiRulesDefinitionTest.java create mode 100644 sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/SonarQubeOpenApiFileTest.java diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..d9d00aa --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "java.configuration.updateBuildConfiguration": "automatic", + "java.test.config": { + "workingDirectory": "${workspaceFolder}" + } +} diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ade06e..4e703d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.2.1] - 2024-05-14 + +## Fixed + +- Resolve language suffix conflict between the plugin's custom YAML/JSON support and SonarQube's built-in language detection. + ## [1.2.0] - 2024-05-05 ## Added diff --git a/README.md b/README.md index b951732..852982a 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -# 🛠️ 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) ![Release](https://img.shields.io/badge/release-1.2.1-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. diff --git a/its/pom.xml b/its/pom.xml index c07aa94..49f6740 100644 --- a/its/pom.xml +++ b/its/pom.xml @@ -5,7 +5,7 @@ org.apiaddicts.apitools.dosonarapi dosonarapi - 1.2.0 + 1.2.1 ../pom.xml 4.0.0 diff --git a/openapi-checks/pom.xml b/openapi-checks/pom.xml index e13b2c4..4ae3c61 100644 --- a/openapi-checks/pom.xml +++ b/openapi-checks/pom.xml @@ -5,7 +5,7 @@ org.apiaddicts.apitools.dosonarapi dosonarapi - 1.2.0 + 1.2.1 ../pom.xml diff --git a/openapi-checks/src/test/java/org/apiaddicts/apitools/dosonarapi/checks/CheckListTest.java b/openapi-checks/src/test/java/org/apiaddicts/apitools/dosonarapi/checks/CheckListTest.java new file mode 100644 index 0000000..8b6b6da --- /dev/null +++ b/openapi-checks/src/test/java/org/apiaddicts/apitools/dosonarapi/checks/CheckListTest.java @@ -0,0 +1,57 @@ +/* + * doSonarAPI: SonarQube OpenAPI Plugin + * Copyright (C) 2021-2022 Apiaddicts + * contacta AT apiaddicts DOT org + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.apiaddicts.apitools.dosonarapi.checks; + +import java.util.List; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class CheckListTest { + + @Test + public void returns_all_check_classes() { + List> checks = CheckList.getChecks(); + assertThat(checks).isNotEmpty(); + assertThat(checks).contains( + PathMaskeradingCheck.class, + MediaTypeCheck.class, + ParsingErrorCheck.class, + DefaultResponseCheck.class, + DefinedResponseCheck.class, + DeclaredTagCheck.class, + DocumentedTagCheck.class, + AtMostOneBodyParameterCheck.class, + NoUnusedDefinitionCheck.class, + NoContentIn204Check.class, + ProvideOpSummaryCheck.class, + ContactValidEmailCheck.class, + DescriptionDiffersSummaryCheck.class + ); + } + + @Test + public void constants_have_expected_values() { + assertThat(CheckList.YAML_REPOSITORY_KEY).isEqualTo("openapi-yaml"); + assertThat(CheckList.JSON_REPOSITORY_KEY).isEqualTo("openapi-json"); + assertThat(CheckList.YAML_LANGUAGE).isEqualTo("yaml"); + assertThat(CheckList.JSON_LANGUAGE).isEqualTo("json"); + } +} diff --git a/openapi-checks/src/test/java/org/apiaddicts/apitools/dosonarapi/checks/DocumentedTagCheckTest.java b/openapi-checks/src/test/java/org/apiaddicts/apitools/dosonarapi/checks/DocumentedTagCheckTest.java index acbb4ae..80fc0b5 100644 --- a/openapi-checks/src/test/java/org/apiaddicts/apitools/dosonarapi/checks/DocumentedTagCheckTest.java +++ b/openapi-checks/src/test/java/org/apiaddicts/apitools/dosonarapi/checks/DocumentedTagCheckTest.java @@ -24,12 +24,12 @@ public class DocumentedTagCheckTest { @Test - public void verify_media_type_in_v2() { - OpenApiCheckVerifier.verify("src/test/resources/checks/v2/declared-tag.yaml", new DeclaredTagCheck(), true, false, false); + public void verify_documented_tag_in_v2() { + OpenApiCheckVerifier.verify("src/test/resources/checks/v2/documented-tag.yaml", new DocumentedTagCheck(), true, false, false); } @Test - public void verify_media_type_in_v3() { - OpenApiCheckVerifier.verify("src/test/resources/checks/v3/declared-tag.yaml", new DeclaredTagCheck(), false, true, false); + public void verify_documented_tag_in_v3() { + OpenApiCheckVerifier.verify("src/test/resources/checks/v3/documented-tag.yaml", new DocumentedTagCheck(), false, true, false); } } diff --git a/openapi-checks/src/test/java/org/apiaddicts/apitools/dosonarapi/checks/ParsingErrorCheckTest.java b/openapi-checks/src/test/java/org/apiaddicts/apitools/dosonarapi/checks/ParsingErrorCheckTest.java index 0aa785d..7e07428 100644 --- a/openapi-checks/src/test/java/org/apiaddicts/apitools/dosonarapi/checks/ParsingErrorCheckTest.java +++ b/openapi-checks/src/test/java/org/apiaddicts/apitools/dosonarapi/checks/ParsingErrorCheckTest.java @@ -20,11 +20,16 @@ package org.apiaddicts.apitools.dosonarapi.checks; import com.sonar.sslr.api.RecognitionException; +import java.nio.charset.StandardCharsets; import java.util.List; import org.junit.Test; import org.apiaddicts.apitools.dosonarapi.api.OpenApiFile; import org.apiaddicts.apitools.dosonarapi.api.OpenApiVisitorContext; import org.apiaddicts.apitools.dosonarapi.api.PreciseIssue; +import org.apiaddicts.apitools.dosonarapi.openapi.OpenApiConfiguration; +import org.apiaddicts.apitools.dosonarapi.openapi.parser.OpenApiParser; +import org.apiaddicts.apitools.dosonarapi.sslr.yaml.grammar.ValidationException; +import org.apiaddicts.apitools.dosonarapi.sslr.yaml.grammar.YamlParser; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.tuple; @@ -43,6 +48,33 @@ public void reports_parsing_errors() { .contains(tuple(3, "Parsing exception message")); } + @Test + public void reports_validation_exception_causes_as_issues() { + String invalidYaml = + "openapi: \"3.0.0\"\n" + + "info:\n" + + " title: Test API\n" + + "paths:\n" + + " /pets:\n" + + " post:\n" + + " description: missing responses\n"; + + OpenApiConfiguration config = new OpenApiConfiguration(StandardCharsets.UTF_8, true); + YamlParser parser = OpenApiParser.createV3(config); + OpenApiVisitorContext context; + try { + parser.parse(invalidYaml); + context = new OpenApiVisitorContext(new TestFile(), (RecognitionException) null); + } catch (ValidationException e) { + context = new OpenApiVisitorContext(new TestFile(), e); + } + + ParsingErrorCheck check = new ParsingErrorCheck(); + List issues = check.scanFileForIssues(context); + + assertThat(issues).isNotEmpty(); + } + private static class TestFile implements OpenApiFile { @Override diff --git a/openapi-front-end/pom.xml b/openapi-front-end/pom.xml index 73b4941..9c28b76 100644 --- a/openapi-front-end/pom.xml +++ b/openapi-front-end/pom.xml @@ -5,7 +5,7 @@ org.apiaddicts.apitools.dosonarapi dosonarapi - 1.2.0 + 1.2.1 ../pom.xml diff --git a/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/api/IssueLocationTest.java b/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/api/IssueLocationTest.java index 6ab9038..bd41ac6 100644 --- a/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/api/IssueLocationTest.java +++ b/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/api/IssueLocationTest.java @@ -175,5 +175,44 @@ public void compare_precise_location_differents_objects(){ assertThat(issueLocation1.equals(issueLocation2)).isFalse(); } + @Test + public void both_null_messages_are_equal() { + IssueLocation loc1 = IssueLocation.atLineLevel(null, 10); + IssueLocation loc2 = IssueLocation.atLineLevel(null, 10); + assertThat(loc1.equals(loc2)).isTrue(); + assertThat(loc1.hashCode()).isEqualTo(loc2.hashCode()); + } + + @Test + public void null_message_not_equal_to_non_null_message() { + IssueLocation loc1 = IssueLocation.atLineLevel(null, 10); + IssueLocation loc2 = IssueLocation.atLineLevel(MESSAGE, 10); + assertThat(loc1.equals(loc2)).isFalse(); + assertThat(loc2.equals(loc1)).isFalse(); + } + + @Test + public void hashcode_with_null_message() { + IssueLocation loc = IssueLocation.atLineLevel(null, 5); + int hash = loc.hashCode(); + assertThat(hash).isEqualTo(IssueLocation.atLineLevel(null, 5).hashCode()); + } + + @Test + public void precise_location_with_multiline_value() { + JsonNode root = parser.parse("swagger: \"2.0\"\n" + + "info:\n" + + " version: 1.0.0\n" + + " title: T\n" + + " description: |\n" + + " line one\n" + + " line two\n" + + "paths:\n" + + " /pets: {}"); + JsonNode descNode = root.at("/info/description").value(); + IssueLocation loc = IssueLocation.preciseLocation(MESSAGE, descNode); + assertThat(loc.startLine()).isGreaterThan(0); + assertThat(loc.endLine()).isGreaterThanOrEqualTo(loc.startLine()); + } } diff --git a/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/api/OpenApiCheckTest.java b/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/api/OpenApiCheckTest.java index bd46711..6eadf0d 100644 --- a/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/api/OpenApiCheckTest.java +++ b/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/api/OpenApiCheckTest.java @@ -22,13 +22,19 @@ import com.google.common.collect.Sets; import com.sonar.sslr.api.AstNodeType; import java.io.File; +import java.nio.charset.StandardCharsets; +import java.util.Collections; import java.util.HashSet; +import java.util.List; import java.util.Set; import org.junit.Test; import org.sonar.check.Rule; import org.apiaddicts.apitools.dosonarapi.api.v3.OpenApi3Grammar; +import org.apiaddicts.apitools.dosonarapi.openapi.OpenApiConfiguration; +import org.apiaddicts.apitools.dosonarapi.openapi.parser.OpenApiParser; import org.apiaddicts.apitools.dosonarapi.sslr.yaml.grammar.JsonNode; +import org.apiaddicts.apitools.dosonarapi.sslr.yaml.grammar.YamlParser; import static org.assertj.core.api.Assertions.assertThat; @@ -76,4 +82,40 @@ public void skips_rules_on_x_nosonar() { assertThat(rule2.visitedNodes).isEmpty(); assertThat(rule3.visitedNodes).containsOnly("/paths/~1pets/get", "/paths/~1pets/get/parameters/0"); } + + private static class LineIssueCheck extends OpenApiCheck { + @Override + public Set subscribedKinds() { + return Collections.emptySet(); + } + + @Override + protected void visitFile(JsonNode root) { + addLineIssue("line problem", 3); + } + } + + private static class NoAnnotationCheck extends OpenApiCheck {} + + @Test + public void add_line_issue_creates_issue_on_given_line() { + OpenApiConfiguration config = new OpenApiConfiguration(StandardCharsets.UTF_8, false); + YamlParser parser = OpenApiParser.createV3(config); + JsonNode root = parser.parse("openapi: \"3.0.0\"\ninfo:\n title: T\n version: 1.0\npaths: {}"); + OpenApiFile file = new OpenApiFile() { + @Override public String content() { return ""; } + @Override public String fileName() { return "test.yaml"; } + }; + OpenApiVisitorContext ctx = new OpenApiVisitorContext(root, parser.getIssues(), file); + + LineIssueCheck check = new LineIssueCheck(); + List issues = check.scanFileForIssues(ctx); + assertThat(issues).hasSize(1); + assertThat(issues.get(0).primaryLocation().startLine()).isEqualTo(3); + } + + @Test + public void no_rule_annotation_returns_empty_rule_id() { + assertThat(new NoAnnotationCheck().getRuleId()).isEmpty(); + } } diff --git a/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/api/PreciseIssueTest.java b/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/api/PreciseIssueTest.java index 668c04e..fb81605 100644 --- a/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/api/PreciseIssueTest.java +++ b/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/api/PreciseIssueTest.java @@ -19,22 +19,92 @@ */ package org.apiaddicts.apitools.dosonarapi.api; -import org.apiaddicts.apitools.dosonarapi.api.IssueLocation; -import org.apiaddicts.apitools.dosonarapi.api.PreciseIssue; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatObject; public class PreciseIssueTest { - private static final String MESSAGE = "Test Message"; - @Test - public void compare_equals_objects(){ + public void compare_equals_objects() { PreciseIssue preciseIssue1 = new PreciseIssue(IssueLocation.atLineLevel(null, 42000)).withCost(5); PreciseIssue preciseIssue2 = new PreciseIssue(IssueLocation.atLineLevel(null, 42000)).withCost(5); assertThat(preciseIssue1.equals(preciseIssue2)).isTrue(); assertThat(preciseIssue1.hashCode() == preciseIssue2.hashCode()).isTrue(); } + @Test + public void not_equal_to_non_precise_issue() { + PreciseIssue issue = new PreciseIssue(IssueLocation.atLineLevel("msg", 1)); + assertThatObject(issue).isNotEqualTo("not an issue"); + assertThatObject(issue).isNotEqualTo(null); + } + + @Test + public void not_equal_when_costs_differ() { + PreciseIssue issue1 = new PreciseIssue(IssueLocation.atLineLevel("msg", 1)).withCost(1); + PreciseIssue issue2 = new PreciseIssue(IssueLocation.atLineLevel("msg", 1)).withCost(2); + assertThat(issue1.equals(issue2)).isFalse(); + } + + @Test + public void not_equal_when_primary_location_differs() { + PreciseIssue issue1 = new PreciseIssue(IssueLocation.atLineLevel("msg", 1)); + PreciseIssue issue2 = new PreciseIssue(IssueLocation.atLineLevel("msg", 2)); + assertThat(issue1.equals(issue2)).isFalse(); + } + + @Test + public void secondary_location_via_issue_location() { + IssueLocation loc = IssueLocation.atLineLevel("secondary", 5); + PreciseIssue issue = new PreciseIssue(IssueLocation.atLineLevel("primary", 1)); + issue.secondary(loc); + assertThat(issue.secondaryLocations()).hasSize(1); + assertThat(issue.secondaryLocations().get(0)).isEqualTo(loc); + } + + @Test + public void cost_is_null_by_default() { + PreciseIssue issue = new PreciseIssue(IssueLocation.atLineLevel("msg", 1)); + assertThat(issue.cost()).isNull(); + } + + @Test + public void hashcode_without_cost() { + PreciseIssue issue1 = new PreciseIssue(IssueLocation.atLineLevel("msg", 1)); + PreciseIssue issue2 = new PreciseIssue(IssueLocation.atLineLevel("msg", 1)); + assertThat(issue1.hashCode()).isEqualTo(issue2.hashCode()); + } + + @Test + public void equals_with_null_primary_location() { + PreciseIssue issue1 = new PreciseIssue(null); + PreciseIssue issue2 = new PreciseIssue(null); + assertThat(issue1.equals(issue2)).isTrue(); + assertThat(issue1.hashCode()).isEqualTo(issue2.hashCode()); + } + + @Test + public void null_primary_location_not_equal_to_non_null() { + PreciseIssue issue1 = new PreciseIssue(null); + PreciseIssue issue2 = new PreciseIssue(IssueLocation.atLineLevel("msg", 1)); + assertThat(issue1.equals(issue2)).isFalse(); + assertThat(issue2.equals(issue1)).isFalse(); + } + + @Test + public void secondary_location_via_node() { + java.nio.charset.Charset utf8 = java.nio.charset.StandardCharsets.UTF_8; + org.apiaddicts.apitools.dosonarapi.openapi.OpenApiConfiguration config = + new org.apiaddicts.apitools.dosonarapi.openapi.OpenApiConfiguration(utf8, true); + org.apiaddicts.apitools.dosonarapi.sslr.yaml.grammar.YamlParser parser = + org.apiaddicts.apitools.dosonarapi.openapi.parser.OpenApiParser.createV2(config); + org.apiaddicts.apitools.dosonarapi.sslr.yaml.grammar.JsonNode root = + parser.parse("swagger: \"2.0\"\ninfo:\n version: 1.0.0\n title: T\npaths:\n /pets: {}"); + org.apiaddicts.apitools.dosonarapi.sslr.yaml.grammar.JsonNode node = root.at("/paths/~1pets").value(); + PreciseIssue issue = new PreciseIssue(IssueLocation.atLineLevel("primary", 1)); + issue.secondary(node, "secondary message"); + assertThat(issue.secondaryLocations()).hasSize(1); + } } diff --git a/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/api/ResourceCheckTest.java b/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/api/ResourceCheckTest.java new file mode 100644 index 0000000..b34aae9 --- /dev/null +++ b/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/api/ResourceCheckTest.java @@ -0,0 +1,191 @@ +/* + * doSonarAPI: SonarQube OpenAPI Plugin + * Copyright (C) 2021-2022 Apiaddicts + * contacta AT apiaddicts DOT org + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.apiaddicts.apitools.dosonarapi.api; + +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import org.junit.Test; +import org.apiaddicts.apitools.dosonarapi.openapi.OpenApiConfiguration; +import org.apiaddicts.apitools.dosonarapi.openapi.parser.OpenApiParser; +import org.apiaddicts.apitools.dosonarapi.sslr.yaml.grammar.JsonNode; +import org.apiaddicts.apitools.dosonarapi.sslr.yaml.grammar.YamlParser; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ResourceCheckTest { + + private static class RecordingResourceCheck extends ResourceCheck { + final List visitedPaths = new ArrayList<>(); + + @Override + protected void visitResource(JsonNode node) { + visitedPaths.add(node.key().getTokenValue()); + } + } + + private OpenApiVisitorContext createV2Context(String yaml) { + OpenApiConfiguration config = new OpenApiConfiguration(StandardCharsets.UTF_8, true); + YamlParser parser = OpenApiParser.createV2(config); + JsonNode root = parser.parse(yaml); + OpenApiFile file = new OpenApiFile() { + @Override public String content() { return yaml; } + @Override public String fileName() { return "test.yaml"; } + }; + return new OpenApiVisitorContext(root, parser.getIssues(), file); + } + + @Test + public void visits_resource_paths_only() { + String yaml = + "swagger: \"2.0\"\n" + + "info:\n" + + " version: 1.0.0\n" + + " title: Test\n" + + "paths:\n" + + " /pets:\n" + + " get:\n" + + " responses:\n" + + " '200':\n" + + " description: ok\n" + + " /pets/{petId}:\n" + + " get:\n" + + " responses:\n" + + " '200':\n" + + " description: ok\n"; + + RecordingResourceCheck check = new RecordingResourceCheck(); + check.scanFileForIssues(createV2Context(yaml)); + + assertThat(check.visitedPaths).contains("/pets"); + assertThat(check.visitedPaths).doesNotContain("/pets/{petId}"); + } + + @Test + public void does_not_visit_when_no_resource_paths() { + String yaml = + "swagger: \"2.0\"\n" + + "info:\n" + + " version: 1.0.0\n" + + " title: Test\n" + + "paths:\n" + + " /pets/{petId}:\n" + + " get:\n" + + " responses:\n" + + " '200':\n" + + " description: ok\n"; + + RecordingResourceCheck check = new RecordingResourceCheck(); + check.scanFileForIssues(createV2Context(yaml)); + + assertThat(check.visitedPaths).isEmpty(); + } + + @Test + public void visits_sub_resource_paths() { + String yaml = + "swagger: \"2.0\"\n" + + "info:\n" + + " version: 1.0.0\n" + + " title: Test\n" + + "paths:\n" + + " /pets/{petId}/tags:\n" + + " get:\n" + + " responses:\n" + + " '200':\n" + + " description: ok\n" + + " /pets/{petId}/tags/{tagId}:\n" + + " get:\n" + + " responses:\n" + + " '200':\n" + + " description: ok\n"; + + RecordingResourceCheck check = new RecordingResourceCheck(); + check.scanFileForIssues(createV2Context(yaml)); + + assertThat(check.visitedPaths).contains("/pets/{petId}/tags"); + } + + @Test + public void skips_path_ending_with_trailing_slash() { + String yaml = + "swagger: \"2.0\"\n" + + "info:\n" + + " version: 1.0.0\n" + + " title: Test\n" + + "paths:\n" + + " /pets/:\n" + + " get:\n" + + " responses:\n" + + " '200':\n" + + " description: ok\n"; + + RecordingResourceCheck check = new RecordingResourceCheck(); + check.scanFileForIssues(createV2Context(yaml)); + + assertThat(check.visitedPaths).isEmpty(); + } + + @Test + public void skips_path_with_only_variable_last_segment() { + String yaml = + "swagger: \"2.0\"\n" + + "info:\n" + + " version: 1.0.0\n" + + " title: Test\n" + + "paths:\n" + + " /{entity}:\n" + + " get:\n" + + " responses:\n" + + " '200':\n" + + " description: ok\n"; + + RecordingResourceCheck check = new RecordingResourceCheck(); + check.scanFileForIssues(createV2Context(yaml)); + + assertThat(check.visitedPaths).isEmpty(); + } + + @Test + public void path_is_last_without_following_variable_child_is_not_resource() { + String yaml = + "swagger: \"2.0\"\n" + + "info:\n" + + " version: 1.0.0\n" + + " title: Test\n" + + "paths:\n" + + " /pets:\n" + + " get:\n" + + " responses:\n" + + " '200':\n" + + " description: ok\n" + + " /other:\n" + + " get:\n" + + " responses:\n" + + " '200':\n" + + " description: ok\n"; + + RecordingResourceCheck check = new RecordingResourceCheck(); + check.scanFileForIssues(createV2Context(yaml)); + + assertThat(check.visitedPaths).doesNotContain("/pets"); + assertThat(check.visitedPaths).doesNotContain("/other"); + } +} diff --git a/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/api/TestOpenApiVisitorRunnerTest.java b/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/api/TestOpenApiVisitorRunnerTest.java new file mode 100644 index 0000000..1027932 --- /dev/null +++ b/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/api/TestOpenApiVisitorRunnerTest.java @@ -0,0 +1,82 @@ +/* + * doSonarAPI: SonarQube OpenAPI Plugin + * Copyright (C) 2021-2022 Apiaddicts + * contacta AT apiaddicts DOT org + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.apiaddicts.apitools.dosonarapi.api; + +import java.io.File; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class TestOpenApiVisitorRunnerTest { + + private static final File V3_FILE = new File( + TestOpenApiVisitorRunnerTest.class.getResource("/petstore.yaml").getFile()); + private static final File V2_FILE = new File( + TestOpenApiVisitorRunnerTest.class.getResource("/models/v2/pet-store.yaml").getFile()); + private static final File V31_FILE = new File( + TestOpenApiVisitorRunnerTest.class.getResource("/petstore-v31.yaml").getFile()); + private static final File V32_FILE = new File( + TestOpenApiVisitorRunnerTest.class.getResource("/petstore-v32.yaml").getFile()); + + @Test + public void create_context_with_default_args_uses_v3_parser() { + OpenApiVisitorContext ctx = TestOpenApiVisitorRunner.createContext(V3_FILE); + assertThat(ctx.rootTree()).isNotNull(); + } + + @Test + public void create_context_with_v2_flag_uses_v2_parser() { + OpenApiVisitorContext ctx = TestOpenApiVisitorRunner.createContext(V2_FILE, true); + assertThat(ctx.rootTree()).isNotNull(); + } + + @Test + public void create_context_with_three_flags_false_uses_v3_parser() { + OpenApiVisitorContext ctx = TestOpenApiVisitorRunner.createContext(V3_FILE, false, false, false); + assertThat(ctx.rootTree()).isNotNull(); + } + + @Test + public void create_context_with_v31_flag_uses_v31_parser() { + OpenApiVisitorContext ctx = TestOpenApiVisitorRunner.createContext(V31_FILE, false, false, true); + assertThat(ctx.rootTree()).isNotNull(); + } + + @Test + public void create_context_with_v32_flag_uses_v32_parser() { + OpenApiVisitorContext ctx = TestOpenApiVisitorRunner.createContext(V32_FILE, false, false, false, true); + assertThat(ctx.rootTree()).isNotNull(); + } + + @Test + public void scan_file_for_comments_with_three_flags() { + TestOpenApiVisitorRunner.scanFileForComments(V3_FILE, false, false, false); + } + + @Test + public void scan_file_for_comments_with_v32_flag() { + TestOpenApiVisitorRunner.scanFileForComments(V32_FILE, false, false, false, true); + } + + @Test + public void scan_file_with_visitors() { + TestOpenApiVisitorRunner.scanFile(V3_FILE, new OpenApiVisitor()); + } +} diff --git a/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/openapi/OpenApiConfigurationTest.java b/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/openapi/OpenApiConfigurationTest.java new file mode 100644 index 0000000..591d5ac --- /dev/null +++ b/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/openapi/OpenApiConfigurationTest.java @@ -0,0 +1,42 @@ +/* + * doSonarAPI: SonarQube OpenAPI Plugin + * Copyright (C) 2021-2022 Apiaddicts + * contacta AT apiaddicts DOT org + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.apiaddicts.apitools.dosonarapi.openapi; + +import java.nio.charset.StandardCharsets; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class OpenApiConfigurationTest { + + @Test + public void stores_charset_and_strict_flag() { + OpenApiConfiguration config = new OpenApiConfiguration(StandardCharsets.UTF_8, true); + assertThat(config.getCharset()).isEqualTo(StandardCharsets.UTF_8); + assertThat(config.isStrict()).isTrue(); + } + + @Test + public void strict_false() { + OpenApiConfiguration config = new OpenApiConfiguration(StandardCharsets.ISO_8859_1, false); + assertThat(config.getCharset()).isEqualTo(StandardCharsets.ISO_8859_1); + assertThat(config.isStrict()).isFalse(); + } +} diff --git a/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/openapi/metrics/FileMetricsTest.java b/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/openapi/metrics/FileMetricsTest.java index 463dc37..80e2434 100644 --- a/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/openapi/metrics/FileMetricsTest.java +++ b/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/openapi/metrics/FileMetricsTest.java @@ -19,10 +19,12 @@ */ package org.apiaddicts.apitools.dosonarapi.openapi.metrics; +import com.sonar.sslr.api.RecognitionException; import java.io.File; -import org.apiaddicts.apitools.dosonarapi.openapi.metrics.FileMetrics; +import org.apiaddicts.apitools.dosonarapi.api.OpenApiFile; import org.junit.Test; +import org.apiaddicts.apitools.dosonarapi.api.OpenApiVisitorContext; import org.apiaddicts.apitools.dosonarapi.api.TestOpenApiVisitorRunner; import static org.assertj.core.api.Assertions.assertThat; @@ -49,6 +51,21 @@ public void complexity() { assertThat(metrics("complexity.yaml").complexity()).isEqualTo(7); } + @Test + public void null_root_tree_yields_zero_counts() { + OpenApiFile file = new OpenApiFile() { + @Override public String content() { return ""; } + @Override public String fileName() { return "dummy.yaml"; } + }; + OpenApiVisitorContext context = new OpenApiVisitorContext(file, new RecognitionException(0, "parse error")); + FileMetrics fileMetrics = new FileMetrics(context); + + assertThat(fileMetrics.numberOfOperations()).isEqualTo(0); + assertThat(fileMetrics.numberOfPaths()).isEqualTo(0); + assertThat(fileMetrics.numberOfSchemas()).isEqualTo(0); + assertThat(fileMetrics.complexity()).isEqualTo(0); + } + private FileMetrics metrics(String fileName) { File baseDir = new File("src/test/resources/metrics/"); File file = new File(baseDir, fileName); diff --git a/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/openapi/metrics/OpenApiMetricsTest.java b/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/openapi/metrics/OpenApiMetricsTest.java new file mode 100644 index 0000000..35cc8f8 --- /dev/null +++ b/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/openapi/metrics/OpenApiMetricsTest.java @@ -0,0 +1,55 @@ +/* + * doSonarAPI: SonarQube OpenAPI Plugin + * Copyright (C) 2021-2022 Apiaddicts + * contacta AT apiaddicts DOT org + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.apiaddicts.apitools.dosonarapi.openapi.metrics; + +import java.util.List; +import org.junit.Test; +import org.sonar.api.measures.Metric; + +import static org.assertj.core.api.Assertions.assertThat; + +public class OpenApiMetricsTest { + + @Test + public void provides_three_metrics() { + OpenApiMetrics openApiMetrics = new OpenApiMetrics(); + List metrics = openApiMetrics.getMetrics(); + assertThat(metrics).hasSize(3); + assertThat(metrics).contains(OpenApiMetrics.OPERATIONS_COUNT, OpenApiMetrics.PATHS_COUNT, OpenApiMetrics.SCHEMAS_COUNT); + } + + @Test + public void operations_count_metric_has_correct_key() { + assertThat(OpenApiMetrics.OPERATIONS_COUNT.getKey()).isEqualTo("operations_count"); + assertThat(OpenApiMetrics.OPERATIONS_COUNT.getName()).isEqualTo("Operations Count"); + } + + @Test + public void paths_count_metric_has_correct_key() { + assertThat(OpenApiMetrics.PATHS_COUNT.getKey()).isEqualTo("paths_count"); + assertThat(OpenApiMetrics.PATHS_COUNT.getName()).isEqualTo("Paths Count"); + } + + @Test + public void schemas_count_metric_has_correct_key() { + assertThat(OpenApiMetrics.SCHEMAS_COUNT.getKey()).isEqualTo("schemas_count"); + assertThat(OpenApiMetrics.SCHEMAS_COUNT.getName()).isEqualTo("Schemas Count"); + } +} diff --git a/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/openapi/parser/OpenApiParserTest.java b/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/openapi/parser/OpenApiParserTest.java new file mode 100644 index 0000000..643f3b3 --- /dev/null +++ b/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/openapi/parser/OpenApiParserTest.java @@ -0,0 +1,96 @@ +/* + * doSonarAPI: SonarQube OpenAPI Plugin + * Copyright (C) 2021-2022 Apiaddicts + * contacta AT apiaddicts DOT org + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.apiaddicts.apitools.dosonarapi.openapi.parser; + +import java.nio.charset.StandardCharsets; +import org.junit.Test; +import org.apiaddicts.apitools.dosonarapi.openapi.OpenApiConfiguration; +import org.apiaddicts.apitools.dosonarapi.sslr.yaml.grammar.JsonNode; +import org.apiaddicts.apitools.dosonarapi.sslr.yaml.grammar.YamlParser; + +import static org.assertj.core.api.Assertions.assertThat; + +public class OpenApiParserTest { + + private static final OpenApiConfiguration CONFIG = new OpenApiConfiguration(StandardCharsets.UTF_8, false); + + private static final String MINIMAL_V2 = + "swagger: \"2.0\"\n" + + "info:\n" + + " title: Test\n" + + " version: 1.0.0\n" + + "paths: {}"; + + private static final String MINIMAL_V3 = + "openapi: \"3.0.0\"\n" + + "info:\n" + + " title: Test\n" + + " version: 1.0.0\n" + + "paths: {}"; + + @Test + public void create_v2_parser_parses_swagger_doc() { + YamlParser parser = OpenApiParser.createV2(CONFIG); + assertThat(parser).isNotNull(); + JsonNode root = parser.parse(MINIMAL_V2); + assertThat(root).isNotNull(); + assertThat(root.at("/swagger").getTokenValue()).isEqualTo("2.0"); + } + + @Test + public void create_v3_parser_parses_openapi_doc() { + YamlParser parser = OpenApiParser.createV3(CONFIG); + assertThat(parser).isNotNull(); + JsonNode root = parser.parse(MINIMAL_V3); + assertThat(root).isNotNull(); + assertThat(root.at("/openapi").getTokenValue()).isEqualTo("3.0.0"); + } + + @Test + public void create_v31_parser_returns_non_null() { + YamlParser parser = OpenApiParser.createV31(CONFIG); + assertThat(parser).isNotNull(); + JsonNode root = parser.parse(MINIMAL_V3); + assertThat(root).isNotNull(); + } + + @Test + public void create_v32_parser_returns_non_null() { + YamlParser parser = OpenApiParser.createV32(CONFIG); + assertThat(parser).isNotNull(); + JsonNode root = parser.parse(MINIMAL_V3); + assertThat(root).isNotNull(); + } + + @Test + public void create_generic_parser_returns_non_null() { + YamlParser parser = OpenApiParser.createGeneric(CONFIG); + assertThat(parser).isNotNull(); + JsonNode root = parser.parse("key: value"); + assertThat(root).isNotNull(); + } + + @Test + public void create_generic_with_non_strict_config() { + OpenApiConfiguration nonStrict = new OpenApiConfiguration(StandardCharsets.UTF_8, false); + YamlParser parser = OpenApiParser.createGeneric(nonStrict); + assertThat(parser).isNotNull(); + } +} diff --git a/openapi-front-end/src/test/resources/petstore-v31.yaml b/openapi-front-end/src/test/resources/petstore-v31.yaml new file mode 100644 index 0000000..d7ee194 --- /dev/null +++ b/openapi-front-end/src/test/resources/petstore-v31.yaml @@ -0,0 +1,10 @@ +openapi: "3.1.0" +info: + version: 1.0.0 + title: Petstore V31 +paths: + /pets: + get: + responses: + '200': + description: ok diff --git a/openapi-front-end/src/test/resources/petstore-v32.yaml b/openapi-front-end/src/test/resources/petstore-v32.yaml new file mode 100644 index 0000000..dc04299 --- /dev/null +++ b/openapi-front-end/src/test/resources/petstore-v32.yaml @@ -0,0 +1,10 @@ +openapi: "3.2.0" +info: + version: 1.0.0 + title: Petstore V32 +paths: + /pets: + get: + responses: + '200': + description: ok diff --git a/openapi-test-tools/pom.xml b/openapi-test-tools/pom.xml index 2524ac1..cf684f6 100644 --- a/openapi-test-tools/pom.xml +++ b/openapi-test-tools/pom.xml @@ -5,7 +5,7 @@ org.apiaddicts.apitools.dosonarapi dosonarapi - 1.2.0 + 1.2.1 ../pom.xml @@ -19,6 +19,11 @@ openapi-front-end ${project.version} + + org.sonarsource.sonarqube + sonar-plugin-api + test + junit junit diff --git a/openapi-test-tools/src/test/java/org/apiaddicts/apitools/dosonarapi/OpenApiCheckVerifierTest.java b/openapi-test-tools/src/test/java/org/apiaddicts/apitools/dosonarapi/OpenApiCheckVerifierTest.java index 6ff6377..820f4d3 100644 --- a/openapi-test-tools/src/test/java/org/apiaddicts/apitools/dosonarapi/OpenApiCheckVerifierTest.java +++ b/openapi-test-tools/src/test/java/org/apiaddicts/apitools/dosonarapi/OpenApiCheckVerifierTest.java @@ -28,6 +28,7 @@ import static com.sonar.sslr.api.GenericTokenType.COMMENT; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; public class OpenApiCheckVerifierTest { @@ -76,6 +77,117 @@ public void adjusts_issue_column_based_on_issue_next_line() { assertThat(issue.endColumn()).isEqualTo(6); } + @Test + public void noncompliant_without_line_offset_uses_comment_line() { + Trivia trivia = makeComment("Noncompliant {{message}}", COMMENT_LINE, COMMENT_COLUMN); + OpenApiCheckVerifier verifier = new OpenApiCheckVerifier(); + verifier.collectExpectedIssue(trivia); + + assertThat(verifier.getCollectedIssues()).hasSize(1); + assertThat(verifier.getCollectedIssues().get(0).line()).isEqualTo(COMMENT_LINE); + assertThat(verifier.getCollectedIssues().get(0).message()).isEqualTo("message"); + } + + @Test + public void noncompliant_with_absolute_line_number() { + Trivia trivia = makeComment("Noncompliant @10", COMMENT_LINE, COMMENT_COLUMN); + OpenApiCheckVerifier verifier = new OpenApiCheckVerifier(); + verifier.collectExpectedIssue(trivia); + + assertThat(verifier.getCollectedIssues()).hasSize(1); + assertThat(verifier.getCollectedIssues().get(0).line()).isEqualTo(10); + } + + @Test + public void noncompliant_with_negative_line_offset() { + Trivia trivia = makeComment("Noncompliant @-1", COMMENT_LINE, COMMENT_COLUMN); + OpenApiCheckVerifier verifier = new OpenApiCheckVerifier(); + verifier.collectExpectedIssue(trivia); + + assertThat(verifier.getCollectedIssues()).hasSize(1); + assertThat(verifier.getCollectedIssues().get(0).line()).isEqualTo(COMMENT_LINE - 1); + } + + @Test + public void empty_secondary_lines_list() { + Trivia trivia = makeComment("Noncompliant [[secondary=]]", COMMENT_LINE, COMMENT_COLUMN); + OpenApiCheckVerifier verifier = new OpenApiCheckVerifier(); + verifier.collectExpectedIssue(trivia); + + assertThat(verifier.getCollectedIssues()).hasSize(1); + assertThat(verifier.getCollectedIssues().get(0).secondaryLines()).isEmpty(); + } + + @Test + public void throws_on_invalid_param_without_equals() { + OpenApiCheckVerifier verifier = new OpenApiCheckVerifier(); + assertThatThrownBy(() -> verifier.collectExpectedIssue( + makeComment("Noncompliant [[invalidparam]]", COMMENT_LINE, COMMENT_COLUMN))) + .isInstanceOf(IllegalStateException.class) + .hasMessageContaining("Invalid param"); + } + + @Test + public void throws_on_unknown_param_name() { + OpenApiCheckVerifier verifier = new OpenApiCheckVerifier(); + assertThatThrownBy(() -> verifier.collectExpectedIssue( + makeComment("Noncompliant [[unknownParam=5]]", COMMENT_LINE, COMMENT_COLUMN))) + .isInstanceOf(IllegalStateException.class) + .hasMessageContaining("Invalid param"); + } + + @Test + public void precise_location_throws_when_no_preceding_issue() { + OpenApiCheckVerifier verifier = new OpenApiCheckVerifier(); + assertThatThrownBy(() -> verifier.collectExpectedIssue(makeComment("^^^", COMMENT_LINE, 0))) + .isInstanceOf(IllegalStateException.class) + .hasMessageContaining("Invalid test file"); + } + + @Test + public void precise_location_throws_when_not_on_next_line() { + OpenApiCheckVerifier verifier = new OpenApiCheckVerifier(); + verifier.collectExpectedIssue(makeComment("Noncompliant", COMMENT_LINE, COMMENT_COLUMN)); + assertThatThrownBy(() -> verifier.collectExpectedIssue(makeComment("^^^", COMMENT_LINE + 3, 0))) + .isInstanceOf(IllegalStateException.class) + .hasMessageContaining("Invalid test file"); + } + + @Test + public void precise_location_throws_when_column_not_zero() { + OpenApiCheckVerifier verifier = new OpenApiCheckVerifier(); + verifier.collectExpectedIssue(makeComment("Noncompliant", COMMENT_LINE, COMMENT_COLUMN)); + assertThatThrownBy(() -> verifier.collectExpectedIssue(makeComment("^^^", COMMENT_LINE + 1, 2))) + .isInstanceOf(IllegalStateException.class) + .hasMessageContaining("column 1"); + } + + @Test + public void verify_passes_when_no_issues_expected_and_none_reported() { + OpenApiCheckVerifier.verify("src/test/resources/test-verify.yaml", new TestNoOpCheck(), true, false, false); + } + + @Test + public void verify_passes_when_issue_matches_annotation() { + OpenApiCheckVerifier.verify("src/test/resources/test-verify-noncompliant.yaml", new TestPathsReportCheck(), true, false, false); + } + + @Test + public void verify_fails_when_expected_issue_not_reported() { + assertThatThrownBy(() -> + OpenApiCheckVerifier.verify("src/test/resources/test-verify-noncompliant.yaml", new TestNoOpCheck(), true, false, false)) + .isInstanceOf(AssertionError.class) + .hasMessageContaining("Missing issue"); + } + + @Test + public void verify_fails_when_unexpected_issue_reported() { + assertThatThrownBy(() -> + OpenApiCheckVerifier.verify("src/test/resources/test-verify.yaml", new TestPathsReportCheck(), true, false, false)) + .isInstanceOf(AssertionError.class) + .hasMessageContaining("Unexpected issue"); + } + private Trivia makeComment(String comment, int line, int column) { try { return Trivia.createComment( diff --git a/openapi-test-tools/src/test/java/org/apiaddicts/apitools/dosonarapi/TestNoOpCheck.java b/openapi-test-tools/src/test/java/org/apiaddicts/apitools/dosonarapi/TestNoOpCheck.java new file mode 100644 index 0000000..a1074b2 --- /dev/null +++ b/openapi-test-tools/src/test/java/org/apiaddicts/apitools/dosonarapi/TestNoOpCheck.java @@ -0,0 +1,32 @@ +/* + * doSonarAPI: SonarQube OpenAPI Plugin + * Copyright (C) 2021-2022 Apiaddicts + * contacta AT apiaddicts DOT org + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.apiaddicts.apitools.dosonarapi; + +import com.sonar.sslr.api.AstNodeType; +import java.util.Collections; +import java.util.Set; +import org.apiaddicts.apitools.dosonarapi.api.OpenApiCheck; + +public class TestNoOpCheck extends OpenApiCheck { + @Override + public Set subscribedKinds() { + return Collections.emptySet(); + } +} diff --git a/openapi-test-tools/src/test/java/org/apiaddicts/apitools/dosonarapi/TestPathsReportCheck.java b/openapi-test-tools/src/test/java/org/apiaddicts/apitools/dosonarapi/TestPathsReportCheck.java new file mode 100644 index 0000000..e2b7b2a --- /dev/null +++ b/openapi-test-tools/src/test/java/org/apiaddicts/apitools/dosonarapi/TestPathsReportCheck.java @@ -0,0 +1,39 @@ +/* + * doSonarAPI: SonarQube OpenAPI Plugin + * Copyright (C) 2021-2022 Apiaddicts + * contacta AT apiaddicts DOT org + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.apiaddicts.apitools.dosonarapi; + +import com.google.common.collect.Sets; +import com.sonar.sslr.api.AstNodeType; +import java.util.Set; +import org.apiaddicts.apitools.dosonarapi.api.OpenApiCheck; +import org.apiaddicts.apitools.dosonarapi.api.v2.OpenApi2Grammar; +import org.apiaddicts.apitools.dosonarapi.sslr.yaml.grammar.JsonNode; + +public class TestPathsReportCheck extends OpenApiCheck { + @Override + public Set subscribedKinds() { + return Sets.newHashSet(OpenApi2Grammar.PATHS); + } + + @Override + protected void visitNode(JsonNode node) { + addIssue("paths issue", node); + } +} diff --git a/openapi-test-tools/src/test/resources/test-verify-noncompliant.yaml b/openapi-test-tools/src/test/resources/test-verify-noncompliant.yaml new file mode 100644 index 0000000..646fd59 --- /dev/null +++ b/openapi-test-tools/src/test/resources/test-verify-noncompliant.yaml @@ -0,0 +1,11 @@ +swagger: "2.0" +info: + version: 1.0.0 + title: Test API +paths: +# Noncompliant@+1 {{paths issue}} + /pets: + get: + responses: + '200': + description: ok diff --git a/openapi-test-tools/src/test/resources/test-verify.yaml b/openapi-test-tools/src/test/resources/test-verify.yaml new file mode 100644 index 0000000..32ed33a --- /dev/null +++ b/openapi-test-tools/src/test/resources/test-verify.yaml @@ -0,0 +1,11 @@ +swagger: "2.0" +info: + version: 1.0.0 + title: Test API +paths: + # just a comment, not noncompliant + /pets: + get: + responses: + '200': + description: ok diff --git a/pom.xml b/pom.xml index baf72e1..991d94c 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ org.apiaddicts.apitools.dosonarapi dosonarapi - 1.2.0 + 1.2.1 pom SonarOpenAPI diff --git a/sonar-openapi-plugin/pom.xml b/sonar-openapi-plugin/pom.xml index 323e377..957db01 100644 --- a/sonar-openapi-plugin/pom.xml +++ b/sonar-openapi-plugin/pom.xml @@ -5,7 +5,7 @@ org.apiaddicts.apitools.dosonarapi dosonarapi - 1.2.0 + 1.2.1 ../pom.xml diff --git a/sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/MissingPropertyExceptionTest.java b/sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/MissingPropertyExceptionTest.java new file mode 100644 index 0000000..1d339a3 --- /dev/null +++ b/sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/MissingPropertyExceptionTest.java @@ -0,0 +1,40 @@ +/* + * doSonarAPI: SonarQube OpenAPI Plugin + * Copyright (C) 2021-2022 Apiaddicts + * contacta AT apiaddicts DOT org + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.apiaddicts.apitools.dosonarapi.plugin; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class MissingPropertyExceptionTest { + + @Test + public void stores_property_name_and_message() { + MissingPropertyException ex = new MissingPropertyException("my.property"); + assertThat(ex.getPropertyName()).isEqualTo("my.property"); + assertThat(ex.getMessage()).isEqualTo("Property my.property is not defined!"); + } + + @Test + public void is_runtime_exception() { + MissingPropertyException ex = new MissingPropertyException("foo"); + assertThat(ex).isInstanceOf(RuntimeException.class); + } +} diff --git a/sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiChecksTest.java b/sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiChecksTest.java new file mode 100644 index 0000000..5b059e0 --- /dev/null +++ b/sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiChecksTest.java @@ -0,0 +1,147 @@ +/* + * doSonarAPI: SonarQube OpenAPI Plugin + * Copyright (C) 2021-2022 Apiaddicts + * contacta AT apiaddicts DOT org + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.apiaddicts.apitools.dosonarapi.plugin; + +import java.util.List; +import org.junit.Test; +import org.sonar.api.batch.rule.ActiveRules; +import org.sonar.api.batch.rule.CheckFactory; +import org.sonar.api.batch.rule.internal.ActiveRulesBuilder; +import org.sonar.api.rule.RuleKey; +import org.apiaddicts.apitools.dosonarapi.api.OpenApiCustomRuleRepository; +import org.apiaddicts.apitools.dosonarapi.checks.CheckList; + +import static org.assertj.core.api.Assertions.assertThat; + +public class OpenApiChecksTest { + + private OpenApiCustomRuleRepository repoWithKey(String key) { + return new OpenApiCustomRuleRepository() { + @Override public String repositoryKey() { return key; } + @Override public List> checkClasses() { return CheckList.getChecks(); } + }; + } + + private CheckFactory factoryWithRule(String repoKey, String ruleKey) { + ActiveRules rules = new ActiveRulesBuilder() + .create(RuleKey.of(repoKey, ruleKey)).activate() + .build(); + return new CheckFactory(rules); + } + + @Test + public void addCustomYamlChecks_adds_non_json_repo() { + CheckFactory factory = factoryWithRule("my-custom-yaml", "PathMaskerading"); + OpenApiChecks checks = OpenApiChecks.createOpenApiCheck(factory) + .addCustomYamlChecks(new OpenApiCustomRuleRepository[]{repoWithKey("my-custom-yaml")}); + assertThat(checks.all()).isNotEmpty(); + } + + @Test + public void addCustomYamlChecks_skips_json_repo() { + CheckFactory factory = factoryWithRule("my-custom-json", "PathMaskerading"); + OpenApiChecks checks = OpenApiChecks.createOpenApiCheck(factory) + .addCustomYamlChecks(new OpenApiCustomRuleRepository[]{repoWithKey("my-custom-json")}); + assertThat(checks.all()).isEmpty(); + } + + @Test + public void addCustomYamlChecks_skips_base_yaml_repo() { + CheckFactory factory = factoryWithRule(CheckList.YAML_REPOSITORY_KEY, "PathMaskerading"); + OpenApiChecks checks = OpenApiChecks.createOpenApiCheck(factory) + .addCustomYamlChecks(new OpenApiCustomRuleRepository[]{repoWithKey(CheckList.YAML_REPOSITORY_KEY)}); + assertThat(checks.all()).isEmpty(); + } + + @Test + public void addCustomJsonChecks_adds_json_repo() { + CheckFactory factory = factoryWithRule("my-custom-json", "PathMaskerading"); + OpenApiChecks checks = OpenApiChecks.createOpenApiCheck(factory) + .addCustomJsonChecks(new OpenApiCustomRuleRepository[]{repoWithKey("my-custom-json")}); + assertThat(checks.all()).isNotEmpty(); + } + + @Test + public void addCustomJsonChecks_skips_non_json_repo() { + CheckFactory factory = factoryWithRule("my-custom-yaml", "PathMaskerading"); + OpenApiChecks checks = OpenApiChecks.createOpenApiCheck(factory) + .addCustomJsonChecks(new OpenApiCustomRuleRepository[]{repoWithKey("my-custom-yaml")}); + assertThat(checks.all()).isEmpty(); + } + + @Test + public void addCustomJsonChecks_skips_base_json_repo() { + CheckFactory factory = factoryWithRule(CheckList.JSON_REPOSITORY_KEY, "PathMaskerading"); + OpenApiChecks checks = OpenApiChecks.createOpenApiCheck(factory) + .addCustomJsonChecks(new OpenApiCustomRuleRepository[]{repoWithKey(CheckList.JSON_REPOSITORY_KEY)}); + assertThat(checks.all()).isEmpty(); + } + + @Test + public void addCustomChecks_handles_null() { + ActiveRules rules = new ActiveRulesBuilder().build(); + CheckFactory factory = new CheckFactory(rules); + OpenApiChecks checks = OpenApiChecks.createOpenApiCheck(factory) + .addCustomYamlChecks(null) + .addCustomJsonChecks(null); + assertThat(checks.all()).isEmpty(); + } + + @Test + public void addCustomChecks_adds_non_built_in_repo() { + CheckFactory factory = factoryWithRule("my-custom-repo", "PathMaskerading"); + OpenApiChecks checks = OpenApiChecks.createOpenApiCheck(factory) + .addCustomChecks(new OpenApiCustomRuleRepository[]{repoWithKey("my-custom-repo")}); + assertThat(checks.all()).isNotEmpty(); + } + + @Test + public void addCustomChecks_skips_yaml_built_in_repo() { + CheckFactory factory = factoryWithRule(CheckList.YAML_REPOSITORY_KEY, "PathMaskerading"); + OpenApiChecks checks = OpenApiChecks.createOpenApiCheck(factory) + .addCustomChecks(new OpenApiCustomRuleRepository[]{repoWithKey(CheckList.YAML_REPOSITORY_KEY)}); + assertThat(checks.all()).isEmpty(); + } + + @Test + public void addCustomChecks_skips_json_built_in_repo() { + CheckFactory factory = factoryWithRule(CheckList.JSON_REPOSITORY_KEY, "PathMaskerading"); + OpenApiChecks checks = OpenApiChecks.createOpenApiCheck(factory) + .addCustomChecks(new OpenApiCustomRuleRepository[]{repoWithKey(CheckList.JSON_REPOSITORY_KEY)}); + assertThat(checks.all()).isEmpty(); + } + + @Test + public void addCustomChecks_handles_null_input() { + ActiveRules rules = new ActiveRulesBuilder().build(); + CheckFactory factory = new CheckFactory(rules); + OpenApiChecks checks = OpenApiChecks.createOpenApiCheck(factory) + .addCustomChecks(null); + assertThat(checks.all()).isEmpty(); + } + + @Test + public void ruleKeyFor_returns_null_when_not_found() { + ActiveRules rules = new ActiveRulesBuilder().build(); + CheckFactory factory = new CheckFactory(rules); + OpenApiChecks checks = OpenApiChecks.createOpenApiCheck(factory); + assertThat(checks.ruleKeyFor(new org.apiaddicts.apitools.dosonarapi.checks.PathMaskeradingCheck())).isNull(); + } +} diff --git a/sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiPluginTest.java b/sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiPluginTest.java new file mode 100644 index 0000000..bc68fe8 --- /dev/null +++ b/sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiPluginTest.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.plugin; + +import org.junit.Test; +import org.sonar.api.Plugin; +import org.sonar.api.SonarQubeSide; +import org.sonar.api.internal.SonarRuntimeImpl; +import org.sonar.api.utils.Version; +import org.apiaddicts.apitools.dosonarapi.openapi.metrics.OpenApiMetrics; + +import static org.assertj.core.api.Assertions.assertThat; + +public class OpenApiPluginTest { + + @Test + public void defines_expected_extensions() { + Plugin.Context context = new Plugin.Context(SonarRuntimeImpl.forSonarQube(Version.create(6, 7), SonarQubeSide.SERVER)); + new OpenApiPlugin().define(context); + + assertThat(context.getExtensions()) + .contains(OpenApiScannerSensor.class, OpenApiRulesDefinition.class, OpenApiMetrics.class); + } +} diff --git a/sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiRulesDefinitionTest.java b/sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiRulesDefinitionTest.java new file mode 100644 index 0000000..817814b --- /dev/null +++ b/sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiRulesDefinitionTest.java @@ -0,0 +1,70 @@ +/* + * doSonarAPI: SonarQube OpenAPI Plugin + * Copyright (C) 2021-2022 Apiaddicts + * contacta AT apiaddicts DOT org + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.apiaddicts.apitools.dosonarapi.plugin; + +import org.junit.Test; +import org.sonar.api.server.rule.RulesDefinition; +import org.apiaddicts.apitools.dosonarapi.checks.CheckList; + +import static org.assertj.core.api.Assertions.assertThat; + +public class OpenApiRulesDefinitionTest { + + @Test + public void defines_yaml_and_json_repositories() { + OpenApiRulesDefinition definition = new OpenApiRulesDefinition(); + RulesDefinition.Context context = new RulesDefinition.Context(); + + definition.define(context); + + RulesDefinition.Repository yamlRepo = context.repository(CheckList.YAML_REPOSITORY_KEY); + assertThat(yamlRepo).isNotNull(); + assertThat(yamlRepo.language()).isEqualTo(CheckList.YAML_LANGUAGE); + assertThat(yamlRepo.rules()).hasSizeGreaterThanOrEqualTo(1); + + RulesDefinition.Repository jsonRepo = context.repository(CheckList.JSON_REPOSITORY_KEY); + assertThat(jsonRepo).isNotNull(); + assertThat(jsonRepo.language()).isEqualTo(CheckList.JSON_LANGUAGE); + assertThat(jsonRepo.rules()).hasSizeGreaterThanOrEqualTo(1); + } + + @Test + public void repository_key_returns_yaml_key() { + OpenApiRulesDefinition definition = new OpenApiRulesDefinition(); + assertThat(definition.repositoryKey()).isEqualTo(CheckList.YAML_REPOSITORY_KEY); + } + + @Test + public void check_classes_returns_all_checks() { + OpenApiRulesDefinition definition = new OpenApiRulesDefinition(); + assertThat(definition.checkClasses()).isEqualTo(CheckList.getChecks()); + } + + @Test + public void yaml_and_json_repos_have_same_rules() { + OpenApiRulesDefinition definition = new OpenApiRulesDefinition(); + RulesDefinition.Context context = new RulesDefinition.Context(); + definition.define(context); + + RulesDefinition.Repository yamlRepo = context.repository(CheckList.YAML_REPOSITORY_KEY); + RulesDefinition.Repository jsonRepo = context.repository(CheckList.JSON_REPOSITORY_KEY); + assertThat(yamlRepo.rules()).hasSameSizeAs(jsonRepo.rules()); + } +} diff --git a/sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/SonarQubeOpenApiFileTest.java b/sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/SonarQubeOpenApiFileTest.java new file mode 100644 index 0000000..3f409a0 --- /dev/null +++ b/sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/SonarQubeOpenApiFileTest.java @@ -0,0 +1,80 @@ +/* + * doSonarAPI: SonarQube OpenAPI Plugin + * Copyright (C) 2021-2022 Apiaddicts + * contacta AT apiaddicts DOT org + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.apiaddicts.apitools.dosonarapi.plugin; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Paths; +import org.junit.Test; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.fs.internal.TestInputFileBuilder; +import org.apiaddicts.apitools.dosonarapi.api.OpenApiFile; +import org.apiaddicts.apitools.dosonarapi.checks.CheckList; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class SonarQubeOpenApiFileTest { + + private static final File BASE_DIR = Paths.get("src/test/resources/sensor").toAbsolutePath().toFile(); + + @Test + public void returns_filename() { + InputFile inputFile = TestInputFileBuilder.create("moduleKey", "file1.yaml") + .setModuleBaseDir(BASE_DIR.toPath()) + .setCharset(StandardCharsets.UTF_8) + .setType(InputFile.Type.MAIN) + .setLanguage(CheckList.YAML_LANGUAGE) + .initMetadata(TestUtils.fileContent(new File(BASE_DIR, "file1.yaml"), StandardCharsets.UTF_8)) + .build(); + + OpenApiFile openApiFile = SonarQubeOpenApiFile.create(inputFile); + assertThat(openApiFile.fileName()).isEqualTo("file1.yaml"); + } + + @Test + public void returns_content() { + InputFile inputFile = TestInputFileBuilder.create("moduleKey", "file1.yaml") + .setModuleBaseDir(BASE_DIR.toPath()) + .setCharset(StandardCharsets.UTF_8) + .setType(InputFile.Type.MAIN) + .setLanguage(CheckList.YAML_LANGUAGE) + .initMetadata(TestUtils.fileContent(new File(BASE_DIR, "file1.yaml"), StandardCharsets.UTF_8)) + .build(); + + OpenApiFile openApiFile = SonarQubeOpenApiFile.create(inputFile); + assertThat(openApiFile.content()).isNotEmpty(); + } + + @Test + public void throws_illegal_state_on_io_exception() throws IOException { + InputFile inputFile = mock(InputFile.class); + when(inputFile.filename()).thenReturn("test.yaml"); + when(inputFile.contents()).thenThrow(new IOException("read error")); + + OpenApiFile openApiFile = SonarQubeOpenApiFile.create(inputFile); + assertThatThrownBy(openApiFile::content) + .isInstanceOf(IllegalStateException.class) + .hasMessageContaining("Could not read content"); + } +} From b74cba013400d0df3bf8bf3d5b2a5e54a914c8db Mon Sep 17 00:00:00 2001 From: Melsy Huamani Date: Thu, 14 May 2026 15:25:38 -0500 Subject: [PATCH 09/15] fix: sonar issues --- .../dosonarapi/checks/CheckListTest.java | 3 +- .../dosonarapi/api/IssueLocationTest.java | 2 +- .../dosonarapi/api/PreciseIssueTest.java | 4 +- .../dosonarapi/api/ResourceCheckTest.java | 82 ++++++++----------- .../api/TestOpenApiVisitorRunnerTest.java | 10 ++- .../openapi/metrics/FileMetricsTest.java | 8 +- .../openapi/metrics/OpenApiMetricsTest.java | 3 +- .../dosonarapi/OpenApiCheckVerifierTest.java | 23 ++++-- .../plugin/OpenApiScannerSensorTest.java | 2 +- 9 files changed, 66 insertions(+), 71 deletions(-) diff --git a/openapi-checks/src/test/java/org/apiaddicts/apitools/dosonarapi/checks/CheckListTest.java b/openapi-checks/src/test/java/org/apiaddicts/apitools/dosonarapi/checks/CheckListTest.java index 8b6b6da..8a31509 100644 --- a/openapi-checks/src/test/java/org/apiaddicts/apitools/dosonarapi/checks/CheckListTest.java +++ b/openapi-checks/src/test/java/org/apiaddicts/apitools/dosonarapi/checks/CheckListTest.java @@ -29,8 +29,7 @@ public class CheckListTest { @Test public void returns_all_check_classes() { List> checks = CheckList.getChecks(); - assertThat(checks).isNotEmpty(); - assertThat(checks).contains( + assertThat(checks).isNotEmpty().contains( PathMaskeradingCheck.class, MediaTypeCheck.class, ParsingErrorCheck.class, diff --git a/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/api/IssueLocationTest.java b/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/api/IssueLocationTest.java index bd41ac6..a91e9d9 100644 --- a/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/api/IssueLocationTest.java +++ b/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/api/IssueLocationTest.java @@ -180,7 +180,7 @@ public void both_null_messages_are_equal() { IssueLocation loc1 = IssueLocation.atLineLevel(null, 10); IssueLocation loc2 = IssueLocation.atLineLevel(null, 10); assertThat(loc1.equals(loc2)).isTrue(); - assertThat(loc1.hashCode()).isEqualTo(loc2.hashCode()); + assertThat(loc1).hasSameHashCodeAs(loc2); } @Test diff --git a/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/api/PreciseIssueTest.java b/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/api/PreciseIssueTest.java index fb81605..720ec0a 100644 --- a/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/api/PreciseIssueTest.java +++ b/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/api/PreciseIssueTest.java @@ -74,7 +74,7 @@ public void cost_is_null_by_default() { public void hashcode_without_cost() { PreciseIssue issue1 = new PreciseIssue(IssueLocation.atLineLevel("msg", 1)); PreciseIssue issue2 = new PreciseIssue(IssueLocation.atLineLevel("msg", 1)); - assertThat(issue1.hashCode()).isEqualTo(issue2.hashCode()); + assertThat(issue1).hasSameHashCodeAs(issue2); } @Test @@ -82,7 +82,7 @@ public void equals_with_null_primary_location() { PreciseIssue issue1 = new PreciseIssue(null); PreciseIssue issue2 = new PreciseIssue(null); assertThat(issue1.equals(issue2)).isTrue(); - assertThat(issue1.hashCode()).isEqualTo(issue2.hashCode()); + assertThat(issue1).hasSameHashCodeAs(issue2); } @Test diff --git a/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/api/ResourceCheckTest.java b/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/api/ResourceCheckTest.java index b34aae9..11bf77c 100644 --- a/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/api/ResourceCheckTest.java +++ b/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/api/ResourceCheckTest.java @@ -23,6 +23,10 @@ import java.util.ArrayList; import java.util.List; import org.junit.Test; +import org.junit.experimental.theories.DataPoints; +import org.junit.experimental.theories.Theories; +import org.junit.experimental.theories.Theory; +import org.junit.runner.RunWith; import org.apiaddicts.apitools.dosonarapi.openapi.OpenApiConfiguration; import org.apiaddicts.apitools.dosonarapi.openapi.parser.OpenApiParser; import org.apiaddicts.apitools.dosonarapi.sslr.yaml.grammar.JsonNode; @@ -30,6 +34,7 @@ import static org.assertj.core.api.Assertions.assertThat; +@RunWith(Theories.class) public class ResourceCheckTest { private static class RecordingResourceCheck extends ResourceCheck { @@ -41,7 +46,7 @@ protected void visitResource(JsonNode node) { } } - private OpenApiVisitorContext createV2Context(String yaml) { + private static OpenApiVisitorContext createV2Context(String yaml) { OpenApiConfiguration config = new OpenApiConfiguration(StandardCharsets.UTF_8, true); YamlParser parser = OpenApiParser.createV2(config); JsonNode root = parser.parse(yaml); @@ -52,66 +57,65 @@ private OpenApiVisitorContext createV2Context(String yaml) { return new OpenApiVisitorContext(root, parser.getIssues(), file); } - @Test - public void visits_resource_paths_only() { - String yaml = + @DataPoints + public static String[] yamlsWithNoResourcePaths() { + return new String[] { "swagger: \"2.0\"\n" + "info:\n" + " version: 1.0.0\n" + " title: Test\n" + "paths:\n" + - " /pets:\n" + + " /pets/{petId}:\n" + " get:\n" + " responses:\n" + " '200':\n" + - " description: ok\n" + - " /pets/{petId}:\n" + + " description: ok\n", + + "swagger: \"2.0\"\n" + + "info:\n" + + " version: 1.0.0\n" + + " title: Test\n" + + "paths:\n" + + " /pets/:\n" + " get:\n" + " responses:\n" + " '200':\n" + - " description: ok\n"; + " description: ok\n", - RecordingResourceCheck check = new RecordingResourceCheck(); - check.scanFileForIssues(createV2Context(yaml)); - - assertThat(check.visitedPaths).contains("/pets"); - assertThat(check.visitedPaths).doesNotContain("/pets/{petId}"); - } - - @Test - public void does_not_visit_when_no_resource_paths() { - String yaml = "swagger: \"2.0\"\n" + "info:\n" + " version: 1.0.0\n" + " title: Test\n" + "paths:\n" + - " /pets/{petId}:\n" + + " /{entity}:\n" + " get:\n" + " responses:\n" + " '200':\n" + - " description: ok\n"; + " description: ok\n" + }; + } + @Theory + public void does_not_visit_non_resource_paths(String yaml) { RecordingResourceCheck check = new RecordingResourceCheck(); check.scanFileForIssues(createV2Context(yaml)); - assertThat(check.visitedPaths).isEmpty(); } @Test - public void visits_sub_resource_paths() { + public void visits_resource_paths_only() { String yaml = "swagger: \"2.0\"\n" + "info:\n" + " version: 1.0.0\n" + " title: Test\n" + "paths:\n" + - " /pets/{petId}/tags:\n" + + " /pets:\n" + " get:\n" + " responses:\n" + " '200':\n" + " description: ok\n" + - " /pets/{petId}/tags/{tagId}:\n" + + " /pets/{petId}:\n" + " get:\n" + " responses:\n" + " '200':\n" + @@ -120,38 +124,23 @@ public void visits_sub_resource_paths() { RecordingResourceCheck check = new RecordingResourceCheck(); check.scanFileForIssues(createV2Context(yaml)); - assertThat(check.visitedPaths).contains("/pets/{petId}/tags"); + assertThat(check.visitedPaths).contains("/pets").doesNotContain("/pets/{petId}"); } @Test - public void skips_path_ending_with_trailing_slash() { + public void visits_sub_resource_paths() { String yaml = "swagger: \"2.0\"\n" + "info:\n" + " version: 1.0.0\n" + " title: Test\n" + "paths:\n" + - " /pets/:\n" + + " /pets/{petId}/tags:\n" + " get:\n" + " responses:\n" + " '200':\n" + - " description: ok\n"; - - RecordingResourceCheck check = new RecordingResourceCheck(); - check.scanFileForIssues(createV2Context(yaml)); - - assertThat(check.visitedPaths).isEmpty(); - } - - @Test - public void skips_path_with_only_variable_last_segment() { - String yaml = - "swagger: \"2.0\"\n" + - "info:\n" + - " version: 1.0.0\n" + - " title: Test\n" + - "paths:\n" + - " /{entity}:\n" + + " description: ok\n" + + " /pets/{petId}/tags/{tagId}:\n" + " get:\n" + " responses:\n" + " '200':\n" + @@ -160,7 +149,7 @@ public void skips_path_with_only_variable_last_segment() { RecordingResourceCheck check = new RecordingResourceCheck(); check.scanFileForIssues(createV2Context(yaml)); - assertThat(check.visitedPaths).isEmpty(); + assertThat(check.visitedPaths).contains("/pets/{petId}/tags"); } @Test @@ -185,7 +174,6 @@ public void path_is_last_without_following_variable_child_is_not_resource() { RecordingResourceCheck check = new RecordingResourceCheck(); check.scanFileForIssues(createV2Context(yaml)); - assertThat(check.visitedPaths).doesNotContain("/pets"); - assertThat(check.visitedPaths).doesNotContain("/other"); + assertThat(check.visitedPaths).doesNotContain("/pets", "/other"); } } diff --git a/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/api/TestOpenApiVisitorRunnerTest.java b/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/api/TestOpenApiVisitorRunnerTest.java index 1027932..16f3a00 100644 --- a/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/api/TestOpenApiVisitorRunnerTest.java +++ b/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/api/TestOpenApiVisitorRunnerTest.java @@ -23,6 +23,7 @@ import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; public class TestOpenApiVisitorRunnerTest { @@ -67,16 +68,19 @@ public void create_context_with_v32_flag_uses_v32_parser() { @Test public void scan_file_for_comments_with_three_flags() { - TestOpenApiVisitorRunner.scanFileForComments(V3_FILE, false, false, false); + assertThatCode(() -> TestOpenApiVisitorRunner.scanFileForComments(V3_FILE, false, false, false)) + .doesNotThrowAnyException(); } @Test public void scan_file_for_comments_with_v32_flag() { - TestOpenApiVisitorRunner.scanFileForComments(V32_FILE, false, false, false, true); + assertThatCode(() -> TestOpenApiVisitorRunner.scanFileForComments(V32_FILE, false, false, false, true)) + .doesNotThrowAnyException(); } @Test public void scan_file_with_visitors() { - TestOpenApiVisitorRunner.scanFile(V3_FILE, new OpenApiVisitor()); + assertThatCode(() -> TestOpenApiVisitorRunner.scanFile(V3_FILE, new OpenApiVisitor())) + .doesNotThrowAnyException(); } } diff --git a/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/openapi/metrics/FileMetricsTest.java b/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/openapi/metrics/FileMetricsTest.java index 80e2434..36025af 100644 --- a/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/openapi/metrics/FileMetricsTest.java +++ b/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/openapi/metrics/FileMetricsTest.java @@ -60,10 +60,10 @@ public void null_root_tree_yields_zero_counts() { OpenApiVisitorContext context = new OpenApiVisitorContext(file, new RecognitionException(0, "parse error")); FileMetrics fileMetrics = new FileMetrics(context); - assertThat(fileMetrics.numberOfOperations()).isEqualTo(0); - assertThat(fileMetrics.numberOfPaths()).isEqualTo(0); - assertThat(fileMetrics.numberOfSchemas()).isEqualTo(0); - assertThat(fileMetrics.complexity()).isEqualTo(0); + assertThat(fileMetrics.numberOfOperations()).isZero(); + assertThat(fileMetrics.numberOfPaths()).isZero(); + assertThat(fileMetrics.numberOfSchemas()).isZero(); + assertThat(fileMetrics.complexity()).isZero(); } private FileMetrics metrics(String fileName) { diff --git a/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/openapi/metrics/OpenApiMetricsTest.java b/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/openapi/metrics/OpenApiMetricsTest.java index 35cc8f8..dab744e 100644 --- a/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/openapi/metrics/OpenApiMetricsTest.java +++ b/openapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/openapi/metrics/OpenApiMetricsTest.java @@ -31,8 +31,7 @@ public class OpenApiMetricsTest { public void provides_three_metrics() { OpenApiMetrics openApiMetrics = new OpenApiMetrics(); List metrics = openApiMetrics.getMetrics(); - assertThat(metrics).hasSize(3); - assertThat(metrics).contains(OpenApiMetrics.OPERATIONS_COUNT, OpenApiMetrics.PATHS_COUNT, OpenApiMetrics.SCHEMAS_COUNT); + assertThat(metrics).hasSize(3).contains(OpenApiMetrics.OPERATIONS_COUNT, OpenApiMetrics.PATHS_COUNT, OpenApiMetrics.SCHEMAS_COUNT); } @Test diff --git a/openapi-test-tools/src/test/java/org/apiaddicts/apitools/dosonarapi/OpenApiCheckVerifierTest.java b/openapi-test-tools/src/test/java/org/apiaddicts/apitools/dosonarapi/OpenApiCheckVerifierTest.java index 820f4d3..0ce113f 100644 --- a/openapi-test-tools/src/test/java/org/apiaddicts/apitools/dosonarapi/OpenApiCheckVerifierTest.java +++ b/openapi-test-tools/src/test/java/org/apiaddicts/apitools/dosonarapi/OpenApiCheckVerifierTest.java @@ -121,8 +121,8 @@ public void empty_secondary_lines_list() { @Test public void throws_on_invalid_param_without_equals() { OpenApiCheckVerifier verifier = new OpenApiCheckVerifier(); - assertThatThrownBy(() -> verifier.collectExpectedIssue( - makeComment("Noncompliant [[invalidparam]]", COMMENT_LINE, COMMENT_COLUMN))) + Trivia invalidParam = makeComment("Noncompliant [[invalidparam]]", COMMENT_LINE, COMMENT_COLUMN); + assertThatThrownBy(() -> verifier.collectExpectedIssue(invalidParam)) .isInstanceOf(IllegalStateException.class) .hasMessageContaining("Invalid param"); } @@ -130,8 +130,8 @@ public void throws_on_invalid_param_without_equals() { @Test public void throws_on_unknown_param_name() { OpenApiCheckVerifier verifier = new OpenApiCheckVerifier(); - assertThatThrownBy(() -> verifier.collectExpectedIssue( - makeComment("Noncompliant [[unknownParam=5]]", COMMENT_LINE, COMMENT_COLUMN))) + Trivia unknownParam = makeComment("Noncompliant [[unknownParam=5]]", COMMENT_LINE, COMMENT_COLUMN); + assertThatThrownBy(() -> verifier.collectExpectedIssue(unknownParam)) .isInstanceOf(IllegalStateException.class) .hasMessageContaining("Invalid param"); } @@ -139,7 +139,8 @@ public void throws_on_unknown_param_name() { @Test public void precise_location_throws_when_no_preceding_issue() { OpenApiCheckVerifier verifier = new OpenApiCheckVerifier(); - assertThatThrownBy(() -> verifier.collectExpectedIssue(makeComment("^^^", COMMENT_LINE, 0))) + Trivia preciseLocation = makeComment("^^^", COMMENT_LINE, 0); + assertThatThrownBy(() -> verifier.collectExpectedIssue(preciseLocation)) .isInstanceOf(IllegalStateException.class) .hasMessageContaining("Invalid test file"); } @@ -148,7 +149,8 @@ public void precise_location_throws_when_no_preceding_issue() { public void precise_location_throws_when_not_on_next_line() { OpenApiCheckVerifier verifier = new OpenApiCheckVerifier(); verifier.collectExpectedIssue(makeComment("Noncompliant", COMMENT_LINE, COMMENT_COLUMN)); - assertThatThrownBy(() -> verifier.collectExpectedIssue(makeComment("^^^", COMMENT_LINE + 3, 0))) + Trivia notNextLine = makeComment("^^^", COMMENT_LINE + 3, 0); + assertThatThrownBy(() -> verifier.collectExpectedIssue(notNextLine)) .isInstanceOf(IllegalStateException.class) .hasMessageContaining("Invalid test file"); } @@ -157,7 +159,8 @@ public void precise_location_throws_when_not_on_next_line() { public void precise_location_throws_when_column_not_zero() { OpenApiCheckVerifier verifier = new OpenApiCheckVerifier(); verifier.collectExpectedIssue(makeComment("Noncompliant", COMMENT_LINE, COMMENT_COLUMN)); - assertThatThrownBy(() -> verifier.collectExpectedIssue(makeComment("^^^", COMMENT_LINE + 1, 2))) + Trivia wrongColumn = makeComment("^^^", COMMENT_LINE + 1, 2); + assertThatThrownBy(() -> verifier.collectExpectedIssue(wrongColumn)) .isInstanceOf(IllegalStateException.class) .hasMessageContaining("column 1"); } @@ -174,16 +177,18 @@ public void verify_passes_when_issue_matches_annotation() { @Test public void verify_fails_when_expected_issue_not_reported() { + TestNoOpCheck noOpCheck = new TestNoOpCheck(); assertThatThrownBy(() -> - OpenApiCheckVerifier.verify("src/test/resources/test-verify-noncompliant.yaml", new TestNoOpCheck(), true, false, false)) + OpenApiCheckVerifier.verify("src/test/resources/test-verify-noncompliant.yaml", noOpCheck, true, false, false)) .isInstanceOf(AssertionError.class) .hasMessageContaining("Missing issue"); } @Test public void verify_fails_when_unexpected_issue_reported() { + TestPathsReportCheck pathsCheck = new TestPathsReportCheck(); assertThatThrownBy(() -> - OpenApiCheckVerifier.verify("src/test/resources/test-verify.yaml", new TestPathsReportCheck(), true, false, false)) + OpenApiCheckVerifier.verify("src/test/resources/test-verify.yaml", pathsCheck, true, false, false)) .isInstanceOf(AssertionError.class) .hasMessageContaining("Unexpected issue"); } diff --git a/sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiScannerSensorTest.java b/sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiScannerSensorTest.java index d9dc59d..2d6b20b 100644 --- a/sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiScannerSensorTest.java +++ b/sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiScannerSensorTest.java @@ -122,7 +122,7 @@ public void test_json_issues_single_per_violation() { .activate() .build(); - InputFile inputFile = inputFile("file1.json"); + inputFile("file1.json"); sensor().execute(context); assertThat(context.allIssues()).hasSize(1); From 2216e02b9bc360738c8cc66abae7a32bfbbba7d2 Mon Sep 17 00:00:00 2001 From: Melsy Huamani Date: Fri, 15 May 2026 13:43:06 -0500 Subject: [PATCH 10/15] delete unused files --- .vscode/settings.json | 6 --- .../plugin/OpenApiProfileDefinition.java | 44 ---------------- .../plugin/OpenApiProfileDefinitionTest.java | 52 ------------------- 3 files changed, 102 deletions(-) delete mode 100644 .vscode/settings.json delete mode 100644 sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiProfileDefinition.java delete mode 100644 sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiProfileDefinitionTest.java diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index d9d00aa..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "java.configuration.updateBuildConfiguration": "automatic", - "java.test.config": { - "workingDirectory": "${workspaceFolder}" - } -} diff --git a/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiProfileDefinition.java b/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiProfileDefinition.java deleted file mode 100644 index 320d007..0000000 --- a/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiProfileDefinition.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * doSonarAPI: SonarQube OpenAPI Plugin - * Copyright (C) 2021-2022 Apiaddicts - * contacta AT apiaddicts DOT org - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.apiaddicts.apitools.dosonarapi.plugin; - -import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition; -import org.sonar.api.utils.AnnotationUtils; -import org.sonar.check.Rule; -import org.apiaddicts.apitools.dosonarapi.checks.CheckList; - -public class OpenApiProfileDefinition implements BuiltInQualityProfilesDefinition { - public static final String SONAR_WAY_PROFILE = "Sonar way OpenAPI"; - - @Override - public void define(BuiltInQualityProfilesDefinition.Context context) { - createProfile(context, CheckList.YAML_LANGUAGE, CheckList.YAML_REPOSITORY_KEY); - createProfile(context, CheckList.JSON_LANGUAGE, CheckList.JSON_REPOSITORY_KEY); - } - - private void createProfile(BuiltInQualityProfilesDefinition.Context context, String language, String repositoryKey) { - NewBuiltInQualityProfile profile = context.createBuiltInQualityProfile(SONAR_WAY_PROFILE, language); - for (Class check : CheckList.getChecks()) { - Rule annotation = AnnotationUtils.getAnnotation(check, Rule.class); - profile.activateRule(repositoryKey, annotation.key()); - } - profile.done(); - } -} diff --git a/sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiProfileDefinitionTest.java b/sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiProfileDefinitionTest.java deleted file mode 100644 index 80d9143..0000000 --- a/sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiProfileDefinitionTest.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * doSonarAPI: SonarQube OpenAPI Plugin - * Copyright (C) 2021-2022 Apiaddicts - * contacta AT apiaddicts DOT org - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.apiaddicts.apitools.dosonarapi.plugin; - -import org.junit.Test; -import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition; -import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition.Context; -import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition.BuiltInQualityProfile; -import org.apiaddicts.apitools.dosonarapi.checks.CheckList; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.apiaddicts.apitools.dosonarapi.plugin.OpenApiProfileDefinition.SONAR_WAY_PROFILE; - -public class OpenApiProfileDefinitionTest { - - @Test - public void should_create_yaml_and_json_profiles() { - OpenApiProfileDefinition definition = new OpenApiProfileDefinition(); - Context context = new BuiltInQualityProfilesDefinition.Context(); - - definition.define(context); - - BuiltInQualityProfile yamlProfile = context.profile(CheckList.YAML_LANGUAGE, SONAR_WAY_PROFILE); - assertThat(yamlProfile).isNotNull(); - assertThat(yamlProfile.language()).isEqualTo(CheckList.YAML_LANGUAGE); - assertThat(yamlProfile.rules()).hasSizeGreaterThanOrEqualTo(2); - assertThat(yamlProfile.rules()).allMatch(r -> r.repoKey().equals(CheckList.YAML_REPOSITORY_KEY)); - - BuiltInQualityProfile jsonProfile = context.profile(CheckList.JSON_LANGUAGE, SONAR_WAY_PROFILE); - assertThat(jsonProfile).isNotNull(); - assertThat(jsonProfile.language()).isEqualTo(CheckList.JSON_LANGUAGE); - assertThat(jsonProfile.rules()).hasSizeGreaterThanOrEqualTo(2); - assertThat(jsonProfile.rules()).allMatch(r -> r.repoKey().equals(CheckList.JSON_REPOSITORY_KEY)); - } -} From 5304355e1589567dcc81960cf14a6814d1fa052e Mon Sep 17 00:00:00 2001 From: Melsy Huamani Date: Fri, 22 May 2026 08:52:52 -0500 Subject: [PATCH 11/15] fix: cve alert with guava 32 in sslr-yaml-parser --- CHANGELOG.md | 8 ++++++-- pom.xml | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e703d7..29c20c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,9 +5,13 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [1.2.1] - 2024-05-14 +## [1.2.1] - 2024-05-22 -## Fixed +### Security + +- Update `sslr-yaml-parser` to resolve transitive `guava` vulnerabilities. + +### Fixed - Resolve language suffix conflict between the plugin's custom YAML/JSON support and SonarQube's built-in language detection. diff --git a/pom.xml b/pom.xml index 991d94c..7023955 100644 --- a/pom.xml +++ b/pom.xml @@ -81,7 +81,7 @@ 1.22.0.848 3.37.0.87 1.24.0.633 - 1.0.0 + 1.0.1 From b1b7c355b09a3c29945a88bcdd07a6f0ad8cbc90 Mon Sep 17 00:00:00 2001 From: Sebastian Diaz Torres Date: Tue, 26 May 2026 09:08:44 -0500 Subject: [PATCH 12/15] feat: Update to beta --- CHANGELOG.md | 2 +- its/pom.xml | 2 +- openapi-checks/pom.xml | 2 +- openapi-front-end/pom.xml | 2 +- openapi-test-tools/pom.xml | 2 +- pom.xml | 16 +++++++++++++++- sonar-openapi-plugin/pom.xml | 2 +- 7 files changed, 21 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 29c20c4..f9b85ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [1.2.1] - 2024-05-22 +## [1.2.1-beta-1] - 2024-05-22 ### Security diff --git a/its/pom.xml b/its/pom.xml index 49f6740..cb9e214 100644 --- a/its/pom.xml +++ b/its/pom.xml @@ -5,7 +5,7 @@ org.apiaddicts.apitools.dosonarapi dosonarapi - 1.2.1 + 1.2.1-beta-1 ../pom.xml 4.0.0 diff --git a/openapi-checks/pom.xml b/openapi-checks/pom.xml index 4ae3c61..4244f0b 100644 --- a/openapi-checks/pom.xml +++ b/openapi-checks/pom.xml @@ -5,7 +5,7 @@ org.apiaddicts.apitools.dosonarapi dosonarapi - 1.2.1 + 1.2.1-beta-1 ../pom.xml diff --git a/openapi-front-end/pom.xml b/openapi-front-end/pom.xml index 9c28b76..cc4b3ba 100644 --- a/openapi-front-end/pom.xml +++ b/openapi-front-end/pom.xml @@ -5,7 +5,7 @@ org.apiaddicts.apitools.dosonarapi dosonarapi - 1.2.1 + 1.2.1-beta-1 ../pom.xml diff --git a/openapi-test-tools/pom.xml b/openapi-test-tools/pom.xml index cf684f6..02cb4d1 100644 --- a/openapi-test-tools/pom.xml +++ b/openapi-test-tools/pom.xml @@ -5,7 +5,7 @@ org.apiaddicts.apitools.dosonarapi dosonarapi - 1.2.1 + 1.2.1-beta-1 ../pom.xml diff --git a/pom.xml b/pom.xml index 7023955..61dfb4c 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ org.apiaddicts.apitools.dosonarapi dosonarapi - 1.2.1 + 1.2.1-beta-1 pom SonarOpenAPI @@ -19,6 +19,20 @@ Apiaddicts https://apiaddicts.org + + + AP + Adrian Palanques + adrian.palanques@cloudappi.net + Cloudappi + + + MH + Melsy Huamani + melsy.huamani.pe@cloudappi.net + Cloudappi + + GNU LGPL 3 diff --git a/sonar-openapi-plugin/pom.xml b/sonar-openapi-plugin/pom.xml index 957db01..b9fbc86 100644 --- a/sonar-openapi-plugin/pom.xml +++ b/sonar-openapi-plugin/pom.xml @@ -5,7 +5,7 @@ org.apiaddicts.apitools.dosonarapi dosonarapi - 1.2.1 + 1.2.1-beta-1 ../pom.xml From 585ddf9ffb02700b71985d46b28737555bc9feb6 Mon Sep 17 00:00:00 2001 From: Melsy Huamani Date: Wed, 27 May 2026 16:24:49 -0500 Subject: [PATCH 13/15] feat: add OpenAPI language support without YAML and JSON conflicts --- .../apitools/dosonarapi/checks/CheckList.java | 2 + .../apitools/dosonarapi/plugin/OpenApi.java | 13 +++- .../dosonarapi/plugin/OpenApiChecks.java | 30 ++++++-- .../dosonarapi/plugin/OpenApiPlugin.java | 2 + .../plugin/OpenApiProfileDefinition.java | 40 +++++++++++ .../plugin/OpenApiRulesDefinition.java | 1 + .../plugin/OpenApiScannerSensor.java | 15 +++- .../dosonarapi/plugin/OpenApiChecksTest.java | 49 +++++++++++++ .../dosonarapi/plugin/OpenApiPluginTest.java | 7 +- .../plugin/OpenApiProfileDefinitionTest.java | 69 +++++++++++++++++++ .../plugin/OpenApiRulesDefinitionTest.java | 11 ++- .../plugin/OpenApiScannerSensorTest.java | 22 +++++- 12 files changed, 248 insertions(+), 13 deletions(-) create mode 100644 sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiProfileDefinition.java create mode 100644 sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiProfileDefinitionTest.java diff --git a/openapi-checks/src/main/java/org/apiaddicts/apitools/dosonarapi/checks/CheckList.java b/openapi-checks/src/main/java/org/apiaddicts/apitools/dosonarapi/checks/CheckList.java index 2933c5f..f2346ac 100644 --- a/openapi-checks/src/main/java/org/apiaddicts/apitools/dosonarapi/checks/CheckList.java +++ b/openapi-checks/src/main/java/org/apiaddicts/apitools/dosonarapi/checks/CheckList.java @@ -25,8 +25,10 @@ public final class CheckList { public static final String YAML_REPOSITORY_KEY = "openapi-yaml"; public static final String JSON_REPOSITORY_KEY = "openapi-json"; + public static final String OPENAPI_REPOSITORY_KEY = "openapi"; public static final String YAML_LANGUAGE = "yaml"; public static final String JSON_LANGUAGE = "json"; + public static final String OPENAPI_LANGUAGE = "openapi"; private CheckList() { } diff --git a/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApi.java b/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApi.java index 83dca61..92d51b8 100644 --- a/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApi.java +++ b/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApi.java @@ -19,10 +19,19 @@ */ package org.apiaddicts.apitools.dosonarapi.plugin; -public final class OpenApi { +import org.sonar.api.resources.AbstractLanguage; + +public class OpenApi extends AbstractLanguage { public static final String KEY = "openapi"; + private static final String NAME = "OpenAPI"; - private OpenApi() {} + public OpenApi() { + super(KEY, NAME); + } + @Override + public String[] getFileSuffixes() { + return new String[0]; + } } diff --git a/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiChecks.java b/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiChecks.java index ae2c35e..3bc6329 100644 --- a/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiChecks.java +++ b/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiChecks.java @@ -57,15 +57,15 @@ public OpenApiChecks addChecks(String repositoryKey, Iterable> checkCla public OpenApiChecks addCustomChecks(@Nullable OpenApiCustomRuleRepository[] customRuleRepositories) { if (customRuleRepositories != null) { - for (OpenApiCustomRuleRepository ruleRepository : customRuleRepositories) { - if (!ruleRepository.repositoryKey().equals(CheckList.YAML_REPOSITORY_KEY) && - !ruleRepository.repositoryKey().equals(CheckList.JSON_REPOSITORY_KEY)) { - addChecks(ruleRepository.repositoryKey(), new ArrayList<>(ruleRepository.checkClasses())); + String key = ruleRepository.repositoryKey(); + if (!key.equals(CheckList.YAML_REPOSITORY_KEY) && + !key.equals(CheckList.JSON_REPOSITORY_KEY) && + !key.equals(CheckList.OPENAPI_REPOSITORY_KEY)) { + addChecks(key, new ArrayList<>(ruleRepository.checkClasses())); } } } - return this; } @@ -75,7 +75,8 @@ public OpenApiChecks addCustomYamlChecks(@Nullable OpenApiCustomRuleRepository[] String key = ruleRepository.repositoryKey(); if (!key.equals(CheckList.YAML_REPOSITORY_KEY) && !key.equals(CheckList.JSON_REPOSITORY_KEY) && - !key.endsWith("-json")) { + !key.equals(CheckList.OPENAPI_REPOSITORY_KEY) && + key.endsWith("-yaml")) { addChecks(key, new ArrayList<>(ruleRepository.checkClasses())); } } @@ -89,6 +90,7 @@ public OpenApiChecks addCustomJsonChecks(@Nullable OpenApiCustomRuleRepository[] String key = ruleRepository.repositoryKey(); if (!key.equals(CheckList.YAML_REPOSITORY_KEY) && !key.equals(CheckList.JSON_REPOSITORY_KEY) && + !key.equals(CheckList.OPENAPI_REPOSITORY_KEY) && key.endsWith("-json")) { addChecks(key, new ArrayList<>(ruleRepository.checkClasses())); } @@ -97,6 +99,22 @@ public OpenApiChecks addCustomJsonChecks(@Nullable OpenApiCustomRuleRepository[] return this; } + public OpenApiChecks addCustomOpenApiChecks(@Nullable OpenApiCustomRuleRepository[] customRuleRepositories) { + if (customRuleRepositories != null) { + for (OpenApiCustomRuleRepository ruleRepository : customRuleRepositories) { + String key = ruleRepository.repositoryKey(); + if (!key.equals(CheckList.YAML_REPOSITORY_KEY) && + !key.equals(CheckList.JSON_REPOSITORY_KEY) && + !key.equals(CheckList.OPENAPI_REPOSITORY_KEY) && + !key.endsWith("-yaml") && + !key.endsWith("-json")) { + addChecks(key, new ArrayList<>(ruleRepository.checkClasses())); + } + } + } + return this; + } + public List all() { List allVisitors = new ArrayList<>(); diff --git a/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiPlugin.java b/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiPlugin.java index 07f3b38..0940201 100644 --- a/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiPlugin.java +++ b/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiPlugin.java @@ -27,6 +27,8 @@ public class OpenApiPlugin implements Plugin { @Override public void define(Context context) { context.addExtensions( + OpenApi.class, + OpenApiProfileDefinition.class, OpenApiScannerSensor.class, OpenApiRulesDefinition.class, OpenApiMetrics.class); diff --git a/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiProfileDefinition.java b/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiProfileDefinition.java new file mode 100644 index 0000000..d272652 --- /dev/null +++ b/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiProfileDefinition.java @@ -0,0 +1,40 @@ +/* + * doSonarAPI: SonarQube OpenAPI Plugin + * Copyright (C) 2021-2022 Apiaddicts + * contacta AT apiaddicts DOT org + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.apiaddicts.apitools.dosonarapi.plugin; + +import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition; +import org.sonar.api.utils.AnnotationUtils; +import org.sonar.check.Rule; +import org.apiaddicts.apitools.dosonarapi.checks.CheckList; + +public class OpenApiProfileDefinition implements BuiltInQualityProfilesDefinition { + + public static final String SONAR_WAY_PROFILE = "Sonar way"; + + @Override + public void define(Context context) { + NewBuiltInQualityProfile profile = context.createBuiltInQualityProfile(SONAR_WAY_PROFILE, CheckList.OPENAPI_LANGUAGE); + for (Class check : CheckList.getChecks()) { + Rule annotation = AnnotationUtils.getAnnotation(check, Rule.class); + profile.activateRule(CheckList.OPENAPI_REPOSITORY_KEY, annotation.key()); + } + profile.done(); + } +} diff --git a/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiRulesDefinition.java b/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiRulesDefinition.java index 4794f9a..e21f1e2 100644 --- a/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiRulesDefinition.java +++ b/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiRulesDefinition.java @@ -41,6 +41,7 @@ private static RuleMetadataLoader getRuleMetadataLoader() { public void define(Context context) { createRepository(context, repositoryKey(), CheckList.YAML_LANGUAGE); createRepository(context, CheckList.JSON_REPOSITORY_KEY, CheckList.JSON_LANGUAGE); + createRepository(context, CheckList.OPENAPI_REPOSITORY_KEY, CheckList.OPENAPI_LANGUAGE); } private void createRepository(Context context, String key, String language) { diff --git a/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiScannerSensor.java b/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiScannerSensor.java index fd47dbe..8c6f469 100644 --- a/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiScannerSensor.java +++ b/sonar-openapi-plugin/src/main/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiScannerSensor.java @@ -40,6 +40,7 @@ public class OpenApiScannerSensor implements Sensor { private static final Logger LOGGER = Loggers.get(OpenApiScannerSensor.class); private final OpenApiChecks yamlChecks; private final OpenApiChecks jsonChecks; + private final OpenApiChecks openapiChecks; private FileLinesContextFactory fileLinesContextFactory; private final NoSonarFilter noSonarFilter; @@ -54,6 +55,9 @@ public OpenApiScannerSensor(CheckFactory checkFactory, FileLinesContextFactory f this.jsonChecks = OpenApiChecks.createOpenApiCheck(checkFactory) .addChecks(CheckList.JSON_REPOSITORY_KEY, CheckList.getChecks()) .addCustomJsonChecks(customRuleRepositories); + this.openapiChecks = OpenApiChecks.createOpenApiCheck(checkFactory) + .addChecks(CheckList.OPENAPI_REPOSITORY_KEY, CheckList.getChecks()) + .addCustomOpenApiChecks(customRuleRepositories); this.fileLinesContextFactory = fileLinesContextFactory; this.noSonarFilter = noSonarFilter; } @@ -62,7 +66,7 @@ public OpenApiScannerSensor(CheckFactory checkFactory, FileLinesContextFactory f public void describe(SensorDescriptor descriptor) { descriptor.name("OpenAPI Scanner Sensor") .onlyOnFileType(InputFile.Type.MAIN) - .onlyOnLanguages(CheckList.YAML_LANGUAGE, CheckList.JSON_LANGUAGE); + .onlyOnLanguages(CheckList.YAML_LANGUAGE, CheckList.JSON_LANGUAGE, CheckList.OPENAPI_LANGUAGE); } @Override @@ -82,6 +86,11 @@ public void scanFiles(SensorContext context, FilePredicates p) { p.and(p.hasType(InputFile.Type.MAIN), p.hasLanguage(CheckList.JSON_LANGUAGE)) ).forEach(jsonFiles::add); + List openapiFiles = new ArrayList<>(); + context.fileSystem().inputFiles( + p.and(p.hasType(InputFile.Type.MAIN), p.hasLanguage(CheckList.OPENAPI_LANGUAGE)) + ).forEach(openapiFiles::add); + if (!yamlFiles.isEmpty()) { LOGGER.info("OpenAPI Scanner called for yaml files: {}.", yamlFiles); new OpenApiAnalyzer(context, yamlChecks, fileLinesContextFactory, noSonarFilter, Collections.unmodifiableList(yamlFiles)).scanFiles(); @@ -90,5 +99,9 @@ public void scanFiles(SensorContext context, FilePredicates p) { LOGGER.info("OpenAPI Scanner called for json files: {}.", jsonFiles); new OpenApiAnalyzer(context, jsonChecks, fileLinesContextFactory, noSonarFilter, Collections.unmodifiableList(jsonFiles)).scanFiles(); } + if (!openapiFiles.isEmpty()) { + LOGGER.info("OpenAPI Scanner called for openapi files: {}.", openapiFiles); + new OpenApiAnalyzer(context, openapiChecks, fileLinesContextFactory, noSonarFilter, Collections.unmodifiableList(openapiFiles)).scanFiles(); + } } } diff --git a/sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiChecksTest.java b/sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiChecksTest.java index 5b059e0..802e32d 100644 --- a/sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiChecksTest.java +++ b/sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiChecksTest.java @@ -144,4 +144,53 @@ public void ruleKeyFor_returns_null_when_not_found() { OpenApiChecks checks = OpenApiChecks.createOpenApiCheck(factory); assertThat(checks.ruleKeyFor(new org.apiaddicts.apitools.dosonarapi.checks.PathMaskeradingCheck())).isNull(); } + + @Test + public void addCustomOpenApiChecks_adds_repo_without_suffix() { + CheckFactory factory = factoryWithRule("my-custom", "PathMaskerading"); + OpenApiChecks checks = OpenApiChecks.createOpenApiCheck(factory) + .addCustomOpenApiChecks(new OpenApiCustomRuleRepository[]{repoWithKey("my-custom")}); + assertThat(checks.all()).isNotEmpty(); + } + + @Test + public void addCustomOpenApiChecks_skips_yaml_repo() { + CheckFactory factory = factoryWithRule("my-custom-yaml", "PathMaskerading"); + OpenApiChecks checks = OpenApiChecks.createOpenApiCheck(factory) + .addCustomOpenApiChecks(new OpenApiCustomRuleRepository[]{repoWithKey("my-custom-yaml")}); + assertThat(checks.all()).isEmpty(); + } + + @Test + public void addCustomOpenApiChecks_skips_json_repo() { + CheckFactory factory = factoryWithRule("my-custom-json", "PathMaskerading"); + OpenApiChecks checks = OpenApiChecks.createOpenApiCheck(factory) + .addCustomOpenApiChecks(new OpenApiCustomRuleRepository[]{repoWithKey("my-custom-json")}); + assertThat(checks.all()).isEmpty(); + } + + @Test + public void addCustomOpenApiChecks_skips_base_openapi_repo() { + CheckFactory factory = factoryWithRule(CheckList.OPENAPI_REPOSITORY_KEY, "PathMaskerading"); + OpenApiChecks checks = OpenApiChecks.createOpenApiCheck(factory) + .addCustomOpenApiChecks(new OpenApiCustomRuleRepository[]{repoWithKey(CheckList.OPENAPI_REPOSITORY_KEY)}); + assertThat(checks.all()).isEmpty(); + } + + @Test + public void addCustomOpenApiChecks_handles_null() { + ActiveRules rules = new ActiveRulesBuilder().build(); + CheckFactory factory = new CheckFactory(rules); + OpenApiChecks checks = OpenApiChecks.createOpenApiCheck(factory) + .addCustomOpenApiChecks(null); + assertThat(checks.all()).isEmpty(); + } + + @Test + public void addCustomYamlChecks_requires_yaml_suffix() { + CheckFactory factory = factoryWithRule("my-custom", "PathMaskerading"); + OpenApiChecks checks = OpenApiChecks.createOpenApiCheck(factory) + .addCustomYamlChecks(new OpenApiCustomRuleRepository[]{repoWithKey("my-custom")}); + assertThat(checks.all()).isEmpty(); + } } diff --git a/sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiPluginTest.java b/sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiPluginTest.java index bc68fe8..5b2b1b7 100644 --- a/sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiPluginTest.java +++ b/sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiPluginTest.java @@ -36,6 +36,11 @@ public void defines_expected_extensions() { new OpenApiPlugin().define(context); assertThat(context.getExtensions()) - .contains(OpenApiScannerSensor.class, OpenApiRulesDefinition.class, OpenApiMetrics.class); + .contains( + OpenApi.class, + OpenApiProfileDefinition.class, + OpenApiScannerSensor.class, + OpenApiRulesDefinition.class, + OpenApiMetrics.class); } } diff --git a/sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiProfileDefinitionTest.java b/sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiProfileDefinitionTest.java new file mode 100644 index 0000000..72acf11 --- /dev/null +++ b/sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiProfileDefinitionTest.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.plugin; + +import org.junit.Test; +import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition; +import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition.BuiltInQualityProfile; +import org.apiaddicts.apitools.dosonarapi.checks.CheckList; + +import static org.assertj.core.api.Assertions.assertThat; + +public class OpenApiProfileDefinitionTest { + + @Test + public void defines_sonar_way_profile_for_openapi_language() { + OpenApiProfileDefinition definition = new OpenApiProfileDefinition(); + BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context(); + + definition.define(context); + + BuiltInQualityProfile profile = context.profile(CheckList.OPENAPI_LANGUAGE, OpenApiProfileDefinition.SONAR_WAY_PROFILE); + assertThat(profile).isNotNull(); + assertThat(profile.language()).isEqualTo(CheckList.OPENAPI_LANGUAGE); + assertThat(profile.name()).isEqualTo(OpenApiProfileDefinition.SONAR_WAY_PROFILE); + assertThat(profile.rules()).hasSize(CheckList.getChecks().size()); + } + + @Test + public void profile_rules_belong_to_openapi_repository() { + OpenApiProfileDefinition definition = new OpenApiProfileDefinition(); + BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context(); + + definition.define(context); + + BuiltInQualityProfile profile = context.profile(CheckList.OPENAPI_LANGUAGE, OpenApiProfileDefinition.SONAR_WAY_PROFILE); + assertThat(profile.rules()).isNotEmpty(); + assertThat(profile.rules()) + .allMatch(r -> r.repoKey().equals(CheckList.OPENAPI_REPOSITORY_KEY)); + } + + @Test + public void only_openapi_language_profile_is_created() { + OpenApiProfileDefinition definition = new OpenApiProfileDefinition(); + BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context(); + + definition.define(context); + + assertThat(context.profile(CheckList.YAML_LANGUAGE, OpenApiProfileDefinition.SONAR_WAY_PROFILE)).isNull(); + assertThat(context.profile(CheckList.JSON_LANGUAGE, OpenApiProfileDefinition.SONAR_WAY_PROFILE)).isNull(); + assertThat(context.profile(CheckList.OPENAPI_LANGUAGE, OpenApiProfileDefinition.SONAR_WAY_PROFILE)).isNotNull(); + } +} diff --git a/sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiRulesDefinitionTest.java b/sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiRulesDefinitionTest.java index 817814b..3d10f76 100644 --- a/sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiRulesDefinitionTest.java +++ b/sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiRulesDefinitionTest.java @@ -28,7 +28,7 @@ public class OpenApiRulesDefinitionTest { @Test - public void defines_yaml_and_json_repositories() { + public void defines_yaml_json_and_openapi_repositories() { OpenApiRulesDefinition definition = new OpenApiRulesDefinition(); RulesDefinition.Context context = new RulesDefinition.Context(); @@ -43,6 +43,11 @@ public void defines_yaml_and_json_repositories() { assertThat(jsonRepo).isNotNull(); assertThat(jsonRepo.language()).isEqualTo(CheckList.JSON_LANGUAGE); assertThat(jsonRepo.rules()).hasSizeGreaterThanOrEqualTo(1); + + RulesDefinition.Repository openapiRepo = context.repository(CheckList.OPENAPI_REPOSITORY_KEY); + assertThat(openapiRepo).isNotNull(); + assertThat(openapiRepo.language()).isEqualTo(CheckList.OPENAPI_LANGUAGE); + assertThat(openapiRepo.rules()).hasSizeGreaterThanOrEqualTo(1); } @Test @@ -58,13 +63,15 @@ public void check_classes_returns_all_checks() { } @Test - public void yaml_and_json_repos_have_same_rules() { + public void all_repos_have_same_rules() { OpenApiRulesDefinition definition = new OpenApiRulesDefinition(); RulesDefinition.Context context = new RulesDefinition.Context(); definition.define(context); RulesDefinition.Repository yamlRepo = context.repository(CheckList.YAML_REPOSITORY_KEY); RulesDefinition.Repository jsonRepo = context.repository(CheckList.JSON_REPOSITORY_KEY); + RulesDefinition.Repository openapiRepo = context.repository(CheckList.OPENAPI_REPOSITORY_KEY); assertThat(yamlRepo.rules()).hasSameSizeAs(jsonRepo.rules()); + assertThat(yamlRepo.rules()).hasSameSizeAs(openapiRepo.rules()); } } diff --git a/sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiScannerSensorTest.java b/sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiScannerSensorTest.java index 2d6b20b..8e87685 100644 --- a/sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiScannerSensorTest.java +++ b/sonar-openapi-plugin/src/test/java/org/apiaddicts/apitools/dosonarapi/plugin/OpenApiScannerSensorTest.java @@ -77,7 +77,7 @@ public void sensor_descriptor() { sensor().describe(descriptor); assertThat(descriptor.name()).isEqualTo("OpenAPI Scanner Sensor"); - assertThat(descriptor.languages()).containsOnly("yaml", "json"); + assertThat(descriptor.languages()).containsOnly("yaml", "json", "openapi"); assertThat(descriptor.type()).isEqualTo(InputFile.Type.MAIN); } @@ -144,6 +144,22 @@ public void test_yaml_rules_do_not_fire_on_json_files() { assertThat(context.allIssues()).isEmpty(); } + @Test + public void test_openapi_language_scan() { + activeRules = (new ActiveRulesBuilder()) + .create(RuleKey.of(CheckList.OPENAPI_REPOSITORY_KEY, "PathMaskerading")) + .activate() + .build(); + + inputFile("file1.yaml", CheckList.OPENAPI_LANGUAGE); + sensor().execute(context); + + assertThat(context.allIssues()).hasSize(1); + Issue issue = Iterables.get(context.allIssues(), 0); + assertThat(issue.ruleKey().repository()).isEqualTo(CheckList.OPENAPI_REPOSITORY_KEY); + assertThat(context.allAnalysisErrors()).isEmpty(); + } + @Test public void parse_error() { inputFile("parse-error.yaml"); @@ -253,6 +269,10 @@ private OpenApiScannerSensor sensor() { private InputFile inputFile(String name) { String language = name.endsWith(".json") ? CheckList.JSON_LANGUAGE : CheckList.YAML_LANGUAGE; + return inputFile(name, language); + } + + private InputFile inputFile(String name, String language) { DefaultInputFile inputFile = TestInputFileBuilder.create("moduleKey", name) .setModuleBaseDir(baseDir) .setCharset(StandardCharsets.UTF_8) From fd2c9f140708b60a5b3c9b01dc4f370f2000eefb Mon Sep 17 00:00:00 2001 From: Melsy Huamani Date: Thu, 28 May 2026 08:35:47 -0500 Subject: [PATCH 14/15] update changelog and version --- CHANGELOG.md | 6 ++++++ its/pom.xml | 2 +- openapi-checks/pom.xml | 2 +- openapi-front-end/pom.xml | 2 +- openapi-test-tools/pom.xml | 2 +- pom.xml | 2 +- sonar-openapi-plugin/pom.xml | 2 +- 7 files changed, 12 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f9b85ba..d4c927d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.2.1-beta-2] - 2026-05-28 + +### Added + +- Add OpenAPI language support without YAML and JSON conflicts. + ## [1.2.1-beta-1] - 2024-05-22 ### Security diff --git a/its/pom.xml b/its/pom.xml index cb9e214..3e60f51 100644 --- a/its/pom.xml +++ b/its/pom.xml @@ -5,7 +5,7 @@ org.apiaddicts.apitools.dosonarapi dosonarapi - 1.2.1-beta-1 + 1.2.1-beta-2 ../pom.xml 4.0.0 diff --git a/openapi-checks/pom.xml b/openapi-checks/pom.xml index 4244f0b..6ee409c 100644 --- a/openapi-checks/pom.xml +++ b/openapi-checks/pom.xml @@ -5,7 +5,7 @@ org.apiaddicts.apitools.dosonarapi dosonarapi - 1.2.1-beta-1 + 1.2.1-beta-2 ../pom.xml diff --git a/openapi-front-end/pom.xml b/openapi-front-end/pom.xml index cc4b3ba..00c742d 100644 --- a/openapi-front-end/pom.xml +++ b/openapi-front-end/pom.xml @@ -5,7 +5,7 @@ org.apiaddicts.apitools.dosonarapi dosonarapi - 1.2.1-beta-1 + 1.2.1-beta-2 ../pom.xml diff --git a/openapi-test-tools/pom.xml b/openapi-test-tools/pom.xml index 02cb4d1..13ab239 100644 --- a/openapi-test-tools/pom.xml +++ b/openapi-test-tools/pom.xml @@ -5,7 +5,7 @@ org.apiaddicts.apitools.dosonarapi dosonarapi - 1.2.1-beta-1 + 1.2.1-beta-2 ../pom.xml diff --git a/pom.xml b/pom.xml index 61dfb4c..619c838 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ org.apiaddicts.apitools.dosonarapi dosonarapi - 1.2.1-beta-1 + 1.2.1-beta-2 pom SonarOpenAPI diff --git a/sonar-openapi-plugin/pom.xml b/sonar-openapi-plugin/pom.xml index b9fbc86..d9a7e05 100644 --- a/sonar-openapi-plugin/pom.xml +++ b/sonar-openapi-plugin/pom.xml @@ -5,7 +5,7 @@ org.apiaddicts.apitools.dosonarapi dosonarapi - 1.2.1-beta-1 + 1.2.1-beta-2 ../pom.xml From d4d12c8c1e991b5bf5e5737d99775c364fa52a4e Mon Sep 17 00:00:00 2001 From: Sebastian Diaz Torres Date: Thu, 4 Jun 2026 11:02:05 -0500 Subject: [PATCH 15/15] feat: Release 1.2.1 --- CHANGELOG.md | 14 ++++++++++++++ its/pom.xml | 2 +- openapi-checks/pom.xml | 2 +- openapi-front-end/pom.xml | 2 +- openapi-test-tools/pom.xml | 2 +- pom.xml | 2 +- sonar-openapi-plugin/pom.xml | 2 +- 7 files changed, 20 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d4c927d..d97cfab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,20 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.2.1] - 2026-06-04 + +### Added + +- Add OpenAPI language support without YAML and JSON conflicts. + +### Security + +- Update `sslr-yaml-parser` to resolve transitive `guava` vulnerabilities. + +### Fixed + +- Resolve language suffix conflict between the plugin's custom YAML/JSON support and SonarQube's built-in language detection. + ## [1.2.1-beta-2] - 2026-05-28 ### Added diff --git a/its/pom.xml b/its/pom.xml index 3e60f51..49f6740 100644 --- a/its/pom.xml +++ b/its/pom.xml @@ -5,7 +5,7 @@ org.apiaddicts.apitools.dosonarapi dosonarapi - 1.2.1-beta-2 + 1.2.1 ../pom.xml 4.0.0 diff --git a/openapi-checks/pom.xml b/openapi-checks/pom.xml index 6ee409c..4ae3c61 100644 --- a/openapi-checks/pom.xml +++ b/openapi-checks/pom.xml @@ -5,7 +5,7 @@ org.apiaddicts.apitools.dosonarapi dosonarapi - 1.2.1-beta-2 + 1.2.1 ../pom.xml diff --git a/openapi-front-end/pom.xml b/openapi-front-end/pom.xml index 00c742d..9c28b76 100644 --- a/openapi-front-end/pom.xml +++ b/openapi-front-end/pom.xml @@ -5,7 +5,7 @@ org.apiaddicts.apitools.dosonarapi dosonarapi - 1.2.1-beta-2 + 1.2.1 ../pom.xml diff --git a/openapi-test-tools/pom.xml b/openapi-test-tools/pom.xml index 13ab239..cf684f6 100644 --- a/openapi-test-tools/pom.xml +++ b/openapi-test-tools/pom.xml @@ -5,7 +5,7 @@ org.apiaddicts.apitools.dosonarapi dosonarapi - 1.2.1-beta-2 + 1.2.1 ../pom.xml diff --git a/pom.xml b/pom.xml index 619c838..5719259 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ org.apiaddicts.apitools.dosonarapi dosonarapi - 1.2.1-beta-2 + 1.2.1 pom SonarOpenAPI diff --git a/sonar-openapi-plugin/pom.xml b/sonar-openapi-plugin/pom.xml index d9a7e05..957db01 100644 --- a/sonar-openapi-plugin/pom.xml +++ b/sonar-openapi-plugin/pom.xml @@ -5,7 +5,7 @@ org.apiaddicts.apitools.dosonarapi dosonarapi - 1.2.1-beta-2 + 1.2.1 ../pom.xml