diff --git a/.changeset/strict-icons-flash.md b/.changeset/strict-icons-flash.md new file mode 100644 index 000000000..1bd755919 --- /dev/null +++ b/.changeset/strict-icons-flash.md @@ -0,0 +1,5 @@ +--- +"@asyncapi/parser": patch +--- + +fix: validate channel address when parameters are defined diff --git a/package-lock.json b/package-lock.json index 21662a00f..45f2bfb96 100644 --- a/package-lock.json +++ b/package-lock.json @@ -120,6 +120,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.2.tgz", "integrity": "sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==", "dev": true, + "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.24.7", @@ -1783,6 +1784,7 @@ "integrity": "sha512-+wSycNxOw9QQz81AJAZlNS34EtOIifwUXMPACg05PWjECsjOKDTXLCVPx6J0lRaxhHSGBU2OYs9mRfIvxGt3CA==", "dev": true, "hasInstallScript": true, + "peer": true, "dependencies": { "@swc/counter": "^0.1.3", "@swc/types": "^0.1.12" @@ -2105,6 +2107,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", "dev": true, + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "5.62.0", "@typescript-eslint/types": "5.62.0", @@ -2481,6 +2484,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", "dev": true, + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2542,6 +2546,7 @@ "version": "8.17.1", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -3332,6 +3337,7 @@ "url": "https://github.com/sponsors/ai" } ], + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001640", "electron-to-chromium": "^1.4.820", @@ -4547,6 +4553,7 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", "dev": true, + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -4602,6 +4609,7 @@ "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", "dev": true, + "peer": true, "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -4779,6 +4787,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.18.0.tgz", "integrity": "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==", "dev": true, + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "7.18.0", "@typescript-eslint/types": "7.18.0", @@ -7831,6 +7840,7 @@ "version": "1.3.9", "resolved": "https://registry.npmjs.org/jsep/-/jsep-1.3.9.tgz", "integrity": "sha512-i1rBX5N7VPl0eYb6+mHNp52sEuaS2Wi8CDYx1X5sn9naevL78+265XJqy1qENEk7mRKwS06NHpUqiBwR7qeodw==", + "peer": true, "engines": { "node": ">= 10.16.0" } @@ -9204,6 +9214,7 @@ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", "dev": true, + "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -9907,6 +9918,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -11383,6 +11395,7 @@ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz", "integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==", "dev": true, + "peer": true, "dependencies": { "@types/estree": "^1.0.5", "@webassemblyjs/ast": "^1.12.1", @@ -11497,6 +11510,7 @@ "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.10.0.tgz", "integrity": "sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w==", "dev": true, + "peer": true, "dependencies": { "@discoveryjs/json-ext": "^0.5.0", "@webpack-cli/configtest": "^1.2.0", diff --git a/packages/parser/src/ruleset/v3/ruleset.ts b/packages/parser/src/ruleset/v3/ruleset.ts index efc719483..6734298af 100644 --- a/packages/parser/src/ruleset/v3/ruleset.ts +++ b/packages/parser/src/ruleset/v3/ruleset.ts @@ -3,7 +3,7 @@ import { AsyncAPIFormats } from '../formats'; import { operationMessagesUnambiguity } from './functions/operationMessagesUnambiguity'; -import { pattern } from '@stoplight/spectral-functions'; +import { pattern, truthy } from '@stoplight/spectral-functions'; import { channelServers } from '../functions/channelServers'; export const v3CoreRuleset = { @@ -13,18 +13,18 @@ export const v3CoreRuleset = { /** * Operation Object rules */ - 'asyncapi3-operation-messages-from-referred-channel': { - description: 'Operation "messages" must be a subset of the messages defined in the channel referenced in this operation.', - message: '{{error}}', + 'channel-parameters-require-address': { + description: 'Channel address must not be null when parameters are defined.', + message: 'Channel address must not be null when parameters are defined.', severity: 'error', recommended: true, - resolved: false, // We use the JSON pointer to match the channel. given: [ - '$.operations.*', - '$.components.operations.*', + '$.channels[?(@.parameters)]', + '$.components.channels[?(@.parameters)]' ], then: { - function: operationMessagesUnambiguity, + field: 'address', + function: truthy, }, }, 'asyncapi3-required-operation-channel-unambiguity': { diff --git a/packages/parser/test/ruleset/rules/v3/channel-parameters.spec.ts b/packages/parser/test/ruleset/rules/v3/channel-parameters.spec.ts new file mode 100644 index 000000000..715d122b8 --- /dev/null +++ b/packages/parser/test/ruleset/rules/v3/channel-parameters.spec.ts @@ -0,0 +1,62 @@ +import { testRule, DiagnosticSeverity } from '../../tester'; + +testRule('channel-parameters-require-address', [ + { + name: 'valid case - no parameters', + document: { + asyncapi: '3.0.0', + channels: { + channel: { + address: null, + }, + }, + }, + errors: [], + }, + + { + name: 'valid case - parameters with address', + document: { + asyncapi: '3.0.0', + channels: { + channel: { + address: 'some/address', + parameters: { + param: { + description: 'test', + }, + }, + }, + }, + }, + errors: [], + }, + + { + name: 'invalid case - parameters but address null', + document: { + asyncapi: '3.0.0', + info: { + title: 'Test', + version: '1.0.0', + }, + channels: { + userSignedup: { + address: null, + parameters: { + test: { + description: 'This should fail validation', + }, + }, + }, + }, + }, + errors: [ + { + message: 'Channel address must not be null when parameters are defined.', + path: ['channels', 'userSignedup', 'address'], + severity: DiagnosticSeverity.Error, + }, +], + }, +]); \ No newline at end of file