Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/fix-channel-parameters-validation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@asyncapi/parser": patch
---

fix: validate v3 channel parameters against address placeholders
55 changes: 55 additions & 0 deletions packages/parser/src/ruleset/v3/functions/channelParameters.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { createRulesetFunction } from '@stoplight/spectral-core';

import { getMissingProps, getRedundantProps, parseUrlVariables } from '../../utils';

import type { IFunctionResult } from '@stoplight/spectral-core';

export const v3ChannelParameters = createRulesetFunction<
{ address?: string | null; parameters?: Record<string, unknown> },
null
>(
{
input: {
type: 'object',
properties: {
address: {
type: ['string', 'null'],
},
parameters: {
type: 'object',
},
},
required: ['parameters'],
},
options: null,
},
(targetVal, _, ctx) => {
const { address, parameters } = targetVal;
if (!parameters || Object.keys(parameters).length === 0) return;

const variables = parseUrlVariables(address ?? '');
const results: IFunctionResult[] = [];

const missingParameters = getMissingProps(variables, parameters);
if (missingParameters.length) {
results.push({
message: `Not all channel's parameters are described with "parameters" object. Missed: ${missingParameters.join(
', ',
)}.`,
path: [...ctx.path, 'parameters'],
});
}

const redundantParameters = getRedundantProps(variables, parameters);
if (redundantParameters.length) {
redundantParameters.forEach(param => {
results.push({
message: `Channel's "parameters" object has redundant defined "${param}" parameter.`,
path: [...ctx.path, 'parameters', param],
});
});
}

return results;
},
);
14 changes: 14 additions & 0 deletions packages/parser/src/ruleset/v3/ruleset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import { AsyncAPIFormats } from '../formats';
import { operationMessagesUnambiguity } from './functions/operationMessagesUnambiguity';
import { v3ChannelParameters } from './functions/channelParameters';
import { pattern } from '@stoplight/spectral-functions';
import { channelServers } from '../functions/channelServers';

Expand Down Expand Up @@ -82,5 +83,18 @@ export const v3CoreRuleset = {
},
},
},
'asyncapi3-channel-parameters': {
description: 'Channel parameters must be defined and there must be no redundant parameters.',
message: '{{error}}',
severity: 'error',
recommended: true,
given: [
'$.channels.*',
'$.components.channels.*',
],
then: { // NOSONAR
function: v3ChannelParameters,
},
},
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
import { testRule, DiagnosticSeverity } from '../../tester';

testRule('asyncapi3-channel-parameters', [
{
name: 'valid case',
document: {
asyncapi: '3.0.0',
channels: {
userChannel: {
address: 'users/{userId}/signedUp',
parameters: {
userId: {},
},
},
},
},
errors: [],
},

{
name: 'valid case - without parameters',
document: {
asyncapi: '3.0.0',
channels: {
userChannel: {
address: 'users/{userId}/signedUp',
},
},
},
errors: [],
},

{
name: 'valid case - empty parameters object',
document: {
asyncapi: '3.0.0',
channels: {
userChannel: {
address: 'users/{userId}/signedUp',
parameters: {},
},
},
},
errors: [],
},

{
name: 'invalid case - parameters defined but address has no placeholders',
document: {
asyncapi: '3.0.0',
channels: {
userSignedup: {
address: 'user/signedup',
parameters: {
test: {
description: 'I should get an error that I provide a parameter but there are no parameters in the address',
},
},
},
},
},
errors: [
{
message: 'Channel\'s "parameters" object has redundant defined "test" parameter.',
path: ['channels', 'userSignedup', 'parameters', 'test'],
severity: DiagnosticSeverity.Error,
},
],
},

{
name: 'invalid case - parameters defined but address is null',
document: {
asyncapi: '3.0.0',
channels: {
userSignedup: {
address: null,
parameters: {
test: {},
},
},
},
},
errors: [
{
message: 'Channel\'s "parameters" object has redundant defined "test" parameter.',
path: ['channels', 'userSignedup', 'parameters', 'test'],
severity: DiagnosticSeverity.Error,
},
],
},

{
name: 'invalid case - parameters defined but address is absent',
document: {
asyncapi: '3.0.0',
channels: {
userSignedup: {
parameters: {
test: {},
},
},
},
},
errors: [
{
message: 'Channel\'s "parameters" object has redundant defined "test" parameter.',
path: ['channels', 'userSignedup', 'parameters', 'test'],
severity: DiagnosticSeverity.Error,
},
],
},

{
name: 'channel has not defined definition for one of the parameters',
document: {
asyncapi: '3.0.0',
channels: {
userChannel: {
address: 'users/{userId}/{anotherParam}/signedUp',
parameters: {
userId: {},
},
},
},
},
errors: [
{
message: 'Not all channel\'s parameters are described with "parameters" object. Missed: anotherParam.',
path: ['channels', 'userChannel', 'parameters'],
severity: DiagnosticSeverity.Error,
},
],
},

{
name: 'channel has not defined definition for two+ of the parameters',
document: {
asyncapi: '3.0.0',
channels: {
userChannel: {
address: 'users/{userId}/{anotherParam1}/{anotherParam2}/signedUp',
parameters: {
userId: {},
},
},
},
},
errors: [
{
message:
'Not all channel\'s parameters are described with "parameters" object. Missed: anotherParam1, anotherParam2.',
path: ['channels', 'userChannel', 'parameters'],
severity: DiagnosticSeverity.Error,
},
],
},

{
name: 'channel has redundant parameters',
document: {
asyncapi: '3.0.0',
channels: {
userChannel: {
address: 'users/{userId}/signedUp',
parameters: {
userId: {},
anotherParam1: {},
anotherParam2: {},
},
},
},
},
errors: [
{
message: 'Channel\'s "parameters" object has redundant defined "anotherParam1" parameter.',
path: ['channels', 'userChannel', 'parameters', 'anotherParam1'],
severity: DiagnosticSeverity.Error,
},
{
message: 'Channel\'s "parameters" object has redundant defined "anotherParam2" parameter.',
path: ['channels', 'userChannel', 'parameters', 'anotherParam2'],
severity: DiagnosticSeverity.Error,
},
],
},

{
name: 'invalid case - parameters in components.channels',
document: {
asyncapi: '3.0.0',
components: {
channels: {
userSignedup: {
address: 'user/signedup',
parameters: {
test: {},
},
},
},
},
},
errors: [
{
message: 'Channel\'s "parameters" object has redundant defined "test" parameter.',
path: ['components', 'channels', 'userSignedup', 'parameters', 'test'],
severity: DiagnosticSeverity.Error,
},
],
},
]);
Loading