Skip to content

Commit

Permalink
feat(rulesets): add rules for validation of server variables and chan…
Browse files Browse the repository at this point in the history
…nel parameters
  • Loading branch information
magicmatatjahu committed Mar 22, 2022
1 parent 71b312e commit be69a69
Show file tree
Hide file tree
Showing 7 changed files with 433 additions and 0 deletions.
12 changes: 12 additions & 0 deletions docs/reference/asyncapi-rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ Keep trailing slashes off of channel names, as it can cause some confusion. Most

**Recommended:** Yes

### asyncapi-channel-parameters-defined

All channel parameters should be defined in the `parameters` object of the channel. They should also not contain redundant parameters that do not exist in the channel address.

**Recommended:** Yes

### asyncapi-headers-schema-type-object

The schema definition of the application headers must be of type “object”.
Expand Down Expand Up @@ -288,6 +294,12 @@ Server URL should not point at example.com.

**Recommended:** No

### asyncapi-server-variables-defined

All server URL variables should be defined in the `variables` object of the server. They should also not contain redundant variables that do not exist in the server address.

**Recommended:** Yes

### asyncapi-servers

A non empty `servers` object is expected to be located at the root of the document.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import { DiagnosticSeverity } from '@stoplight/types';
import testRule from './__helpers__/tester';

testRule('asyncapi-channel-parameters-defined', [
{
name: 'valid case',
document: {
asyncapi: '2.0.0',
channels: {
'users/{userId}/signedUp': {
parameters: {
userId: {}
}
},
},
},
errors: [],
},

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

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

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

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

{
name: 'channel has redundant paramaters (in the components.channels)',
document: {
asyncapi: '2.3.0',
components: {
channels: {
'users/{userId}/signedUp': {
parameters: {
userId: {},
anotherParam1: {},
anotherParam2: {},
}
},
},
},
},
errors: [
{
message: 'Channel\'s \"parameters\" object has redundant defined \"anotherParam1\" parameter.',
path: ['components', 'channels', 'users/{userId}/signedUp', 'parameters', 'anotherParam1'],
severity: DiagnosticSeverity.Error,
},
{
message: 'Channel\'s \"parameters\" object has redundant defined \"anotherParam2\" parameter.',
path: ['components', 'channels', 'users/{userId}/signedUp', 'parameters', 'anotherParam2'],
severity: DiagnosticSeverity.Error,
},
],
},
]);
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import { DiagnosticSeverity } from '@stoplight/types';
import testRule from './__helpers__/tester';

testRule('asyncapi-server-variables-defined', [
{
name: 'valid case',
document: {
asyncapi: '2.0.0',
servers: {
production: {
url: '{sub}.stoplight.io',
protocol: 'https',
variables: {
sub: {}
}
},
},
},
errors: [],
},

{
name: 'server has not defined definition for one of the url variables',
document: {
asyncapi: '2.0.0',
servers: {
production: {
url: '{sub}.{anotherParam}.stoplight.io',
protocol: 'https',
variables: {
sub: {}
}
},
},
},
errors: [
{
message: 'Not all server\'s variables are described with \"variables\" object. Missed: anotherParam.',
path: ['servers', 'production', 'variables'],
severity: DiagnosticSeverity.Error,
},
],
},

{
name: 'server has not defined definition for two of the url variables',
document: {
asyncapi: '2.0.0',
servers: {
production: {
url: '{sub}.{anotherParam1}.{anotherParam2}.stoplight.io',
protocol: 'https',
variables: {
sub: {}
}
},
},
},
errors: [
{
message: 'Not all server\'s variables are described with \"variables\" object. Missed: anotherParam1, anotherParam2.',
path: ['servers', 'production', 'variables'],
severity: DiagnosticSeverity.Error,
},
],
},

{
name: 'server has not defined definition for one of the url variables (in the components.servers)',
document: {
asyncapi: '2.3.0',
components: {
servers: {
production: {
url: '{sub}.{anotherParam}.stoplight.io',
protocol: 'https',
variables: {
sub: {}
}
},
},
}
},
errors: [
{
message: 'Not all server\'s variables are described with \"variables\" object. Missed: anotherParam.',
path: ['components', 'servers', 'production', 'variables'],
severity: DiagnosticSeverity.Error,
},
],
},

{
name: 'server has redundant url variables',
document: {
asyncapi: '2.0.0',
servers: {
production: {
url: '{sub}.stoplight.io',
protocol: 'https',
variables: {
sub: {},
anotherParam1: {},
anotherParam2: {},
}
},
},
},
errors: [
{
message: 'Server\'s \"variables\" object has redundant defined \"anotherParam1\" url variable.',
path: ['servers', 'production', 'variables', 'anotherParam1'],
severity: DiagnosticSeverity.Error,
},
{
message: 'Server\'s \"variables\" object has redundant defined \"anotherParam2\" url variable.',
path: ['servers', 'production', 'variables', 'anotherParam2'],
severity: DiagnosticSeverity.Error,
},
],
},

{
name: 'server has redundant url variables (in the components.servers)',
document: {
asyncapi: '2.3.0',
components: {
servers: {
production: {
url: '{sub}.stoplight.io',
protocol: 'https',
variables: {
sub: {},
anotherParam1: {},
anotherParam2: {},
}
},
},
}
},
errors: [
{
message: 'Server\'s \"variables\" object has redundant defined \"anotherParam1\" url variable.',
path: ['components', 'servers', 'production', 'variables', 'anotherParam1'],
severity: DiagnosticSeverity.Error,
},
{
message: 'Server\'s \"variables\" object has redundant defined \"anotherParam2\" url variable.',
path: ['components', 'servers', 'production', 'variables', 'anotherParam2'],
severity: DiagnosticSeverity.Error,
},
],
},
]);
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { createRulesetFunction } from '@stoplight/spectral-core';
import { parseUrlVariables, getMissingProps, getRedundantProps } from './utils';

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

export default createRulesetFunction<{ parameters: Record<string, unknown> }, null>(
{
input: null,
options: null,
},
function asyncApi2ChannelParameters(targetVal, _, ctx) {
const path = ctx.path[ctx.path.length - 1] as string;
const results: IFunctionResult[] = [];

let parameters = parseUrlVariables(path);
if (!parameters || parameters.length === 0) return;

const missingParameters = getMissingProps(parameters, targetVal.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(parameters, targetVal.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;
},
);
Loading

0 comments on commit be69a69

Please sign in to comment.