Skip to content

Commit 9acc633

Browse files
feat(rulesets): add rules for validation of server variables and channel parameters (stoplightio#2101)
Co-authored-by: Jakub Rożek <[email protected]>
1 parent 263e2b0 commit 9acc633

12 files changed

+505
-0
lines changed

docs/reference/asyncapi-rules.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@ Keep trailing slashes off of channel names, as it can cause some confusion. Most
2424

2525
**Recommended:** Yes
2626

27+
### asyncapi-channel-parameters
28+
29+
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.
30+
31+
**Recommended:** Yes
32+
2733
### asyncapi-headers-schema-type-object
2834

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

289295
**Recommended:** No
290296

297+
### asyncapi-server-variables
298+
299+
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.
300+
301+
**Recommended:** Yes
302+
291303
### asyncapi-servers
292304

293305
A non empty `servers` object is expected to be located at the root of the document.
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
import { DiagnosticSeverity } from '@stoplight/types';
2+
import testRule from './__helpers__/tester';
3+
4+
testRule('asyncapi-channel-parameters', [
5+
{
6+
name: 'valid case',
7+
document: {
8+
asyncapi: '2.0.0',
9+
channels: {
10+
'users/{userId}/signedUp': {
11+
parameters: {
12+
userId: {},
13+
},
14+
},
15+
},
16+
},
17+
errors: [],
18+
},
19+
20+
{
21+
name: 'channel has not defined definition for one of the parameters',
22+
document: {
23+
asyncapi: '2.0.0',
24+
channels: {
25+
'users/{userId}/{anotherParam}/signedUp': {
26+
parameters: {
27+
userId: {},
28+
},
29+
},
30+
},
31+
},
32+
errors: [
33+
{
34+
message: 'Not all channel\'s parameters are described with "parameters" object. Missed: anotherParam.',
35+
path: ['channels', 'users/{userId}/{anotherParam}/signedUp', 'parameters'],
36+
severity: DiagnosticSeverity.Error,
37+
},
38+
],
39+
},
40+
41+
{
42+
name: 'channel has not defined definition for two+ of the parameters',
43+
document: {
44+
asyncapi: '2.0.0',
45+
channels: {
46+
'users/{userId}/{anotherParam1}/{anotherParam2}/signedUp': {
47+
parameters: {
48+
userId: {},
49+
},
50+
},
51+
},
52+
},
53+
errors: [
54+
{
55+
message:
56+
'Not all channel\'s parameters are described with "parameters" object. Missed: anotherParam1, anotherParam2.',
57+
path: ['channels', 'users/{userId}/{anotherParam1}/{anotherParam2}/signedUp', 'parameters'],
58+
severity: DiagnosticSeverity.Error,
59+
},
60+
],
61+
},
62+
63+
{
64+
name: 'channel has not defined definition for one of the parameters (in the components.channels)',
65+
document: {
66+
asyncapi: '2.3.0',
67+
components: {
68+
channels: {
69+
'users/{userId}/{anotherParam}/signedUp': {
70+
parameters: {
71+
userId: {},
72+
},
73+
},
74+
},
75+
},
76+
},
77+
errors: [
78+
{
79+
message: 'Not all channel\'s parameters are described with "parameters" object. Missed: anotherParam.',
80+
path: ['components', 'channels', 'users/{userId}/{anotherParam}/signedUp', 'parameters'],
81+
severity: DiagnosticSeverity.Error,
82+
},
83+
],
84+
},
85+
86+
{
87+
name: 'channel has redundant paramaters',
88+
document: {
89+
asyncapi: '2.0.0',
90+
channels: {
91+
'users/{userId}/signedUp': {
92+
parameters: {
93+
userId: {},
94+
anotherParam1: {},
95+
anotherParam2: {},
96+
},
97+
},
98+
},
99+
},
100+
errors: [
101+
{
102+
message: 'Channel\'s "parameters" object has redundant defined "anotherParam1" parameter.',
103+
path: ['channels', 'users/{userId}/signedUp', 'parameters', 'anotherParam1'],
104+
severity: DiagnosticSeverity.Error,
105+
},
106+
{
107+
message: 'Channel\'s "parameters" object has redundant defined "anotherParam2" parameter.',
108+
path: ['channels', 'users/{userId}/signedUp', 'parameters', 'anotherParam2'],
109+
severity: DiagnosticSeverity.Error,
110+
},
111+
],
112+
},
113+
114+
{
115+
name: 'channel has redundant paramaters (in the components.channels)',
116+
document: {
117+
asyncapi: '2.3.0',
118+
components: {
119+
channels: {
120+
'users/{userId}/signedUp': {
121+
parameters: {
122+
userId: {},
123+
anotherParam1: {},
124+
anotherParam2: {},
125+
},
126+
},
127+
},
128+
},
129+
},
130+
errors: [
131+
{
132+
message: 'Channel\'s "parameters" object has redundant defined "anotherParam1" parameter.',
133+
path: ['components', 'channels', 'users/{userId}/signedUp', 'parameters', 'anotherParam1'],
134+
severity: DiagnosticSeverity.Error,
135+
},
136+
{
137+
message: 'Channel\'s "parameters" object has redundant defined "anotherParam2" parameter.',
138+
path: ['components', 'channels', 'users/{userId}/signedUp', 'parameters', 'anotherParam2'],
139+
severity: DiagnosticSeverity.Error,
140+
},
141+
],
142+
},
143+
]);
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
import { DiagnosticSeverity } from '@stoplight/types';
2+
import testRule from './__helpers__/tester';
3+
4+
testRule('asyncapi-server-variables', [
5+
{
6+
name: 'valid case',
7+
document: {
8+
asyncapi: '2.0.0',
9+
servers: {
10+
production: {
11+
url: '{sub}.stoplight.io',
12+
protocol: 'https',
13+
variables: {
14+
sub: {},
15+
},
16+
},
17+
},
18+
},
19+
errors: [],
20+
},
21+
22+
{
23+
name: 'server has not defined definition for one of the url variables',
24+
document: {
25+
asyncapi: '2.0.0',
26+
servers: {
27+
production: {
28+
url: '{sub}.{anotherParam}.stoplight.io',
29+
protocol: 'https',
30+
variables: {
31+
sub: {},
32+
},
33+
},
34+
},
35+
},
36+
errors: [
37+
{
38+
message: 'Not all server\'s variables are described with "variables" object. Missed: anotherParam.',
39+
path: ['servers', 'production', 'variables'],
40+
severity: DiagnosticSeverity.Error,
41+
},
42+
],
43+
},
44+
45+
{
46+
name: 'server has not defined definition for two of the url variables',
47+
document: {
48+
asyncapi: '2.0.0',
49+
servers: {
50+
production: {
51+
url: '{sub}.{anotherParam1}.{anotherParam2}.stoplight.io',
52+
protocol: 'https',
53+
variables: {
54+
sub: {},
55+
},
56+
},
57+
},
58+
},
59+
errors: [
60+
{
61+
message:
62+
'Not all server\'s variables are described with "variables" object. Missed: anotherParam1, anotherParam2.',
63+
path: ['servers', 'production', 'variables'],
64+
severity: DiagnosticSeverity.Error,
65+
},
66+
],
67+
},
68+
69+
{
70+
name: 'server has not defined definition for one of the url variables (in the components.servers)',
71+
document: {
72+
asyncapi: '2.3.0',
73+
components: {
74+
servers: {
75+
production: {
76+
url: '{sub}.{anotherParam}.stoplight.io',
77+
protocol: 'https',
78+
variables: {
79+
sub: {},
80+
},
81+
},
82+
},
83+
},
84+
},
85+
errors: [
86+
{
87+
message: 'Not all server\'s variables are described with "variables" object. Missed: anotherParam.',
88+
path: ['components', 'servers', 'production', 'variables'],
89+
severity: DiagnosticSeverity.Error,
90+
},
91+
],
92+
},
93+
94+
{
95+
name: 'server has redundant url variables',
96+
document: {
97+
asyncapi: '2.0.0',
98+
servers: {
99+
production: {
100+
url: '{sub}.stoplight.io',
101+
protocol: 'https',
102+
variables: {
103+
sub: {},
104+
anotherParam1: {},
105+
anotherParam2: {},
106+
},
107+
},
108+
},
109+
},
110+
errors: [
111+
{
112+
message: 'Server\'s "variables" object has redundant defined "anotherParam1" url variable.',
113+
path: ['servers', 'production', 'variables', 'anotherParam1'],
114+
severity: DiagnosticSeverity.Error,
115+
},
116+
{
117+
message: 'Server\'s "variables" object has redundant defined "anotherParam2" url variable.',
118+
path: ['servers', 'production', 'variables', 'anotherParam2'],
119+
severity: DiagnosticSeverity.Error,
120+
},
121+
],
122+
},
123+
124+
{
125+
name: 'server has redundant url variables (in the components.servers)',
126+
document: {
127+
asyncapi: '2.3.0',
128+
components: {
129+
servers: {
130+
production: {
131+
url: '{sub}.stoplight.io',
132+
protocol: 'https',
133+
variables: {
134+
sub: {},
135+
anotherParam1: {},
136+
anotherParam2: {},
137+
},
138+
},
139+
},
140+
},
141+
},
142+
errors: [
143+
{
144+
message: 'Server\'s "variables" object has redundant defined "anotherParam1" url variable.',
145+
path: ['components', 'servers', 'production', 'variables', 'anotherParam1'],
146+
severity: DiagnosticSeverity.Error,
147+
},
148+
{
149+
message: 'Server\'s "variables" object has redundant defined "anotherParam2" url variable.',
150+
path: ['components', 'servers', 'production', 'variables', 'anotherParam2'],
151+
severity: DiagnosticSeverity.Error,
152+
},
153+
],
154+
},
155+
]);
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { createRulesetFunction } from '@stoplight/spectral-core';
2+
3+
import { parseUrlVariables } from './utils/parseUrlVariables';
4+
import { getMissingProps } from './utils/getMissingProps';
5+
import { getRedundantProps } from './utils/getRedundantProps';
6+
7+
import type { IFunctionResult } from '@stoplight/spectral-core';
8+
9+
export default createRulesetFunction<{ parameters: Record<string, unknown> }, null>(
10+
{
11+
input: {
12+
type: 'object',
13+
properties: {
14+
parameters: {
15+
type: 'object',
16+
},
17+
},
18+
required: ['parameters'],
19+
},
20+
options: null,
21+
},
22+
function asyncApi2ChannelParameters(targetVal, _, ctx) {
23+
const path = ctx.path[ctx.path.length - 1] as string;
24+
const results: IFunctionResult[] = [];
25+
26+
const parameters = parseUrlVariables(path);
27+
if (parameters.length === 0) return;
28+
29+
const missingParameters = getMissingProps(parameters, targetVal.parameters);
30+
if (missingParameters.length) {
31+
results.push({
32+
message: `Not all channel's parameters are described with "parameters" object. Missed: ${missingParameters.join(
33+
', ',
34+
)}.`,
35+
path: [...ctx.path, 'parameters'],
36+
});
37+
}
38+
39+
const redundantParameters = getRedundantProps(parameters, targetVal.parameters);
40+
if (redundantParameters.length) {
41+
redundantParameters.forEach(param => {
42+
results.push({
43+
message: `Channel's "parameters" object has redundant defined "${param}" parameter.`,
44+
path: [...ctx.path, 'parameters', param],
45+
});
46+
});
47+
}
48+
49+
return results;
50+
},
51+
);

0 commit comments

Comments
 (0)