diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..a19167f
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,14 @@
+# Changelog
+
+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.1.0] - 2026-04-09
+
+### Added
+- New support for Avro schemas.
+- Added support for `operations` section in AsyncAPI documents, allowing actions with channels and bindings to be defined.
+- Extended AsyncAPI grammar to handle bindings and payload definitions in messages.
\ No newline at end of file
diff --git a/README.md b/README.md
index db88b68..318dcf3 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,5 @@
-# 🛠️ Sonar AsyncApi (plugin)    [](https://www.gnu.org/licenses/lgpl-3.0)
+# 🛠️ Sonar AsyncApi (plugin)    [](https://www.gnu.org/licenses/lgpl-3.0)
Sonar AsyncApi (plugin) is a code analyzer for AsyncAPI specifications. Starting the project from scratch, with support from the Apiaddicts community.
diff --git a/asyncapi-checks/pom.xml b/asyncapi-checks/pom.xml
index c1f5134..e39ec48 100644
--- a/asyncapi-checks/pom.xml
+++ b/asyncapi-checks/pom.xml
@@ -5,7 +5,7 @@
org.apiaddicts.apitools.dosonarapi
dosonarapi-asyncapi
- 1.0.2
+ 1.1.0
../pom.xml
diff --git a/asyncapi-front-end/pom.xml b/asyncapi-front-end/pom.xml
index fa091f3..abc6fa8 100644
--- a/asyncapi-front-end/pom.xml
+++ b/asyncapi-front-end/pom.xml
@@ -5,7 +5,7 @@
org.apiaddicts.apitools.dosonarapi
dosonarapi-asyncapi
- 1.0.2
+ 1.1.0
../pom.xml
diff --git a/asyncapi-front-end/src/main/java/org/apiaddicts/apitools/dosonarapi/api/v4/AsyncApiGrammar.java b/asyncapi-front-end/src/main/java/org/apiaddicts/apitools/dosonarapi/api/v4/AsyncApiGrammar.java
index 2a8186e..b9ea7e6 100644
--- a/asyncapi-front-end/src/main/java/org/apiaddicts/apitools/dosonarapi/api/v4/AsyncApiGrammar.java
+++ b/asyncapi-front-end/src/main/java/org/apiaddicts/apitools/dosonarapi/api/v4/AsyncApiGrammar.java
@@ -93,7 +93,8 @@ public enum AsyncApiGrammar implements GrammarRuleKey {
CORRELATION_ID,
BINDING_DEFINITION,
HEADERS_SCHEMA,
- PAYLOAD_SCHEMA;
+ PAYLOAD_SCHEMA,
+ OPERATIONS;
private static final String EXTENSION_PATTERN = "^x-.*";
@@ -102,12 +103,13 @@ public static YamlGrammarBuilder create() {
b.setRootRule(ROOT);
b.rule(ROOT).is(b.object(
- b.mandatoryProperty("asyncapi", b.firstOf("2.0.0", "2.1.0", "2.2.0", "2.3.0", "2.4.0", "2.5.0", "2.6.0")),
+ b.mandatoryProperty("asyncapi", b.firstOf("2.0.0", "2.1.0", "2.2.0", "2.3.0", "2.4.0", "2.5.0", "2.6.0", "3.0.0", "3.1.0")),
b.property("id", b.string()),
b.property("defaultContentType", b.string()),
b.mandatoryProperty("info", INFO),
b.property("servers", SERVERS),
- b.mandatoryProperty("channels", CHANNELS),
+ b.property("channels", CHANNELS),
+ b.property("operations", OPERATIONS),
b.property("components", COMPONENTS),
b.property("tags", b.array(TAG)),
b.property("externalDocs", EXTERNAL_DOC),
@@ -130,6 +132,7 @@ public static YamlGrammarBuilder create() {
buildSecurityDefinitions(b);
buildTags(b);
buildCallbacks(b);
+ buildBindings(b);
return b;
}
@@ -214,9 +217,12 @@ private static void buildChannels(YamlGrammarBuilder b) {
b.rule(CHANNEL).is(b.object(
b.property("$ref", b.string()),
b.property("description", DESCRIPTION),
+ b.property("address", b.string()),
b.property("subscribe", OPERATION),
b.property("publish", OPERATION),
- b.property("servers", b.string()),
+ b.property("messages", b.object(
+ b.patternProperty(".*", b.firstOf(REF, MESSAGE)))),
+ b.property("servers", b.firstOf(b.string(), b.array(b.firstOf(REF, b.string())))),
b.property("parameters", b.object(
b.patternProperty(".*", PARAMETER))),
b.property("bindings", CHANNEL_BINDINGS),
@@ -225,6 +231,8 @@ private static void buildChannels(YamlGrammarBuilder b) {
b.rule(OPERATION).is(b.object(
b.property("operationId", b.string()),
+ b.property("action", b.string()),
+ b.property("channel", b.firstOf(REF, b.anything())),
b.property("summary", b.string()),
b.property("description", DESCRIPTION),
b.property("tags", b.array(TAG)),
@@ -232,6 +240,10 @@ private static void buildChannels(YamlGrammarBuilder b) {
b.property("message", b.firstOf(REF, MESSAGE)),
b.property("bindings", OPERATION_BINDINGS),
b.patternProperty(EXTENSION_PATTERN, b.anything())));
+
+ b.rule(OPERATIONS).is(b.object(
+ b.patternProperty(".*", OPERATION),
+ b.patternProperty(EXTENSION_PATTERN, b.anything())));
}
// Components
@@ -310,6 +322,13 @@ private static void buildOperationTraits(YamlGrammarBuilder b) {
// Messages
private static void buildMessages(YamlGrammarBuilder b) {
+ b.rule(PAYLOAD_SCHEMA).is(b.firstOf(
+ REF,
+ b.object(
+ b.mandatoryProperty("schemaFormat", b.string()),
+ b.property("schema", b.anything())),
+ SCHEMA));
+
b.rule(MESSAGE).is(b.object(
b.property("name", b.string()),
b.property("title", b.string()),
@@ -318,7 +337,8 @@ private static void buildMessages(YamlGrammarBuilder b) {
b.property("location", b.string()),
b.property("contentType", b.string()),
b.property("headers", b.firstOf(REF, SCHEMA)),
- b.property("payload", b.firstOf(REF, SCHEMA)),
+ b.property("payload", PAYLOAD_SCHEMA),
+ b.property("bindings", MESSAGE_BINDINGS),
b.property("description", DESCRIPTION),
b.property("examples", b.array(
b.object(
@@ -387,12 +407,14 @@ private static void buildSchema(YamlGrammarBuilder b) {
// Servers
private static void buildServers(YamlGrammarBuilder b) {
b.rule(SERVER).is(b.object(
- b.mandatoryProperty("url", b.string()),
+ b.property("url", b.string()),
+ b.property("host", b.string()),
b.mandatoryProperty("protocol", b.string()),
b.property("description", DESCRIPTION),
b.property("variables", b.object(
b.patternProperty(".*", SERVER_VARIABLE))),
b.property("security", b.array(SECURITY_REQUIREMENT)),
+ b.property("bindings", SERVER_BINDINGS),
b.patternProperty(EXTENSION_PATTERN, b.anything())));
b.rule(SERVER_VARIABLE).is(b.object(
@@ -428,6 +450,17 @@ private static void buildInfo(YamlGrammarBuilder b) {
b.patternProperty(EXTENSION_PATTERN, b.anything())));
}
+ private static void buildBindings(YamlGrammarBuilder b) {
+ b.rule(SERVER_BINDINGS).is(b.object(b.patternProperty(".*", b.anything())));
+ b.rule(CHANNEL_BINDINGS).is(b.object(b.patternProperty(".*", b.anything())));
+ b.rule(OPERATION_BINDINGS).is(b.object(b.patternProperty(".*", b.anything())));
+ b.rule(MESSAGE_BINDINGS).is(b.object(b.patternProperty(".*", b.anything())));
+ b.rule(SERVER_BINDING).is(b.object(b.patternProperty(".*", b.anything())));
+ b.rule(CHANNEL_BINDING).is(b.object(b.patternProperty(".*", b.anything())));
+ b.rule(OPERATION_BINDING).is(b.object(b.patternProperty(".*", b.anything())));
+ b.rule(MESSAGE_BINDING).is(b.object(b.patternProperty(".*", b.anything())));
+ }
+
// Callbacks
private static void buildCallbacks(YamlGrammarBuilder b) {
b.rule(CALLBACKS).is(b.object(
diff --git a/asyncapi-front-end/src/main/java/org/apiaddicts/apitools/dosonarapi/asyncapi/metrics/AsyncApiComplexityVisitor.java b/asyncapi-front-end/src/main/java/org/apiaddicts/apitools/dosonarapi/asyncapi/metrics/AsyncApiComplexityVisitor.java
index 51d33d0..58d2f3d 100644
--- a/asyncapi-front-end/src/main/java/org/apiaddicts/apitools/dosonarapi/asyncapi/metrics/AsyncApiComplexityVisitor.java
+++ b/asyncapi-front-end/src/main/java/org/apiaddicts/apitools/dosonarapi/asyncapi/metrics/AsyncApiComplexityVisitor.java
@@ -31,9 +31,9 @@ public class AsyncApiComplexityVisitor extends AsyncApiVisitor {
private static final HashSet COMPLEXITY_TYPES = Sets.newHashSet(
AsyncApiGrammar.CHANNEL, AsyncApiGrammar.OPERATION, AsyncApiGrammar.MESSAGE,
- AsyncApiGrammar.SCHEMA, AsyncApiGrammar.PARAMETER,
+ AsyncApiGrammar.SCHEMA, AsyncApiGrammar.PAYLOAD_SCHEMA, AsyncApiGrammar.PARAMETER,
AsyncApiGrammar.CHANNEL, AsyncApiGrammar.OPERATION, AsyncApiGrammar.MESSAGE,
- AsyncApiGrammar.SCHEMA, AsyncApiGrammar.PARAMETER
+ AsyncApiGrammar.SCHEMA, AsyncApiGrammar.PAYLOAD_SCHEMA, AsyncApiGrammar.PARAMETER
);
private int complexity;
diff --git a/asyncapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/api/v4/MessageTest.java b/asyncapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/api/v4/MessageTest.java
new file mode 100644
index 0000000..28de6d8
--- /dev/null
+++ b/asyncapi-front-end/src/test/java/org/apiaddicts/apitools/dosonarapi/api/v4/MessageTest.java
@@ -0,0 +1,45 @@
+/*
+ * doSonarAPI: SonarQube AsyncAPI Plugin
+ * Copyright (C) 2024-2024 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.v4;
+
+import org.apiaddicts.apitools.dosonarapi.asyncapi.BaseNodeTest;
+import org.junit.Test;
+import org.apiaddicts.apitools.dosonarapi.sslr.yaml.grammar.JsonNode;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class MessageTest extends BaseNodeTest {
+
+ @Test
+ public void can_parse_avro_inline_payload() {
+ JsonNode node = parseResource(AsyncApiGrammar.PAYLOAD_SCHEMA, "/models/v4/avroPayload.yaml");
+ assertEquals("application/vnd.apache.avro;version=1.9.0", node, "/schemaFormat");
+ assertThat(node.at("/schema").isMissing()).isFalse();
+ assertThat(issues).isEmpty();
+ }
+
+ @Test
+ public void can_parse_avro_ref_payload() {
+ JsonNode node = parseResource(AsyncApiGrammar.PAYLOAD_SCHEMA, "/models/v4/externalAvroPayload.yaml");
+ assertEquals("application/vnd.apache.avro+json;version=1.9.0", node, "/schemaFormat");
+ assertEquals("./userSchema.json", node, "/schema/$ref");
+ assertThat(issues).isEmpty();
+ }
+}
diff --git a/asyncapi-front-end/src/test/resources/models/v4/avroPayload.yaml b/asyncapi-front-end/src/test/resources/models/v4/avroPayload.yaml
new file mode 100644
index 0000000..f3500fa
--- /dev/null
+++ b/asyncapi-front-end/src/test/resources/models/v4/avroPayload.yaml
@@ -0,0 +1,11 @@
+schemaFormat: "application/vnd.apache.avro;version=1.9.0"
+schema:
+ type: record
+ name: UserSignedUp
+ namespace: com.company
+ doc: User sign-up information
+ fields:
+ - name: userId
+ type: int
+ - name: userEmail
+ type: string
diff --git a/asyncapi-front-end/src/test/resources/models/v4/externalAvroPayload.yaml b/asyncapi-front-end/src/test/resources/models/v4/externalAvroPayload.yaml
new file mode 100644
index 0000000..41468e1
--- /dev/null
+++ b/asyncapi-front-end/src/test/resources/models/v4/externalAvroPayload.yaml
@@ -0,0 +1,3 @@
+schemaFormat: "application/vnd.apache.avro+json;version=1.9.0"
+schema:
+ $ref: "./userSchema.json"
diff --git a/asyncapi-test-tools/pom.xml b/asyncapi-test-tools/pom.xml
index 8955310..920c762 100644
--- a/asyncapi-test-tools/pom.xml
+++ b/asyncapi-test-tools/pom.xml
@@ -5,7 +5,7 @@
org.apiaddicts.apitools.dosonarapi
dosonarapi-asyncapi
- 1.0.2
+ 1.1.0
../pom.xml
diff --git a/pom.xml b/pom.xml
index 46726ab..801f87c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -9,7 +9,7 @@
org.apiaddicts.apitools.dosonarapi
dosonarapi-asyncapi
- 1.0.2
+ 1.1.0
pom
SonarAsyncAPI
diff --git a/sonar-asyncapi-plugin/pom.xml b/sonar-asyncapi-plugin/pom.xml
index 4b075dc..4a5765e 100644
--- a/sonar-asyncapi-plugin/pom.xml
+++ b/sonar-asyncapi-plugin/pom.xml
@@ -5,7 +5,7 @@
org.apiaddicts.apitools.dosonarapi
dosonarapi-asyncapi
- 1.0.2
+ 1.1.0
../pom.xml