Skip to content

Commit 2f1d7bf

Browse files
magicmatatjahuP0lip
authored andcommitted
feat(rulesets): support 2.1.0, 2.2.0, 2.3.0 AsyncAPI versions (stoplightio#2067)
1 parent 731d40a commit 2f1d7bf

File tree

9 files changed

+483
-1508
lines changed

9 files changed

+483
-1508
lines changed

packages/rulesets/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,11 @@
2121
"release": "semantic-release -e semantic-release-monorepo"
2222
},
2323
"dependencies": {
24+
"@asyncapi/specs": "^2.13.0",
2425
"@stoplight/better-ajv-errors": "1.0.1",
2526
"@stoplight/json": "^3.17.0",
2627
"@stoplight/spectral-core": "^1.8.1",
27-
"@stoplight/spectral-formats": "^1.0.2",
28+
"@stoplight/spectral-formats": "^1.1.0",
2829
"@stoplight/spectral-functions": "^1.5.1",
2930
"@stoplight/spectral-runtime": "^1.1.1",
3031
"@stoplight/types": "^12.3.0",
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
import testRule from '../../../__tests__/__helpers__/tester';
1+
import testRule, { createWithRules } from '../../../__tests__/__helpers__/tester';
22

3-
export { testRule as default };
3+
export { testRule as default, createWithRules };

packages/rulesets/src/asyncapi/__tests__/asyncapi-schema.test.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ testRule('asyncapi-schema', [
1414
},
1515
errors: [],
1616
},
17-
1817
{
1918
name: 'channels property is missing',
2019
document: {
@@ -24,8 +23,6 @@ testRule('asyncapi-schema', [
2423
version: '1.0',
2524
},
2625
},
27-
errors: [
28-
{ message: 'Object must have required property "channels"', path: [], severity: DiagnosticSeverity.Error },
29-
],
26+
errors: [{ message: 'Object must have required property "channels"', severity: DiagnosticSeverity.Error }],
3027
},
3128
]);
Lines changed: 349 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,349 @@
1+
import { DiagnosticSeverity } from '@stoplight/types';
2+
import { Spectral } from '@stoplight/spectral-core';
3+
import { prepareResults } from '../asyncApi2DocumentSchema';
4+
5+
import { ErrorObject } from 'ajv';
6+
import { createWithRules } from '../../__tests__/__helpers__/tester';
7+
8+
describe('asyncApi2DocumentSchema', () => {
9+
let s: Spectral;
10+
11+
beforeEach(async () => {
12+
s = createWithRules(['asyncapi-schema']);
13+
});
14+
15+
describe('given AsyncAPI 2.0.0 document', () => {
16+
test('validate invalid info object', async () => {
17+
expect(
18+
await s.run({
19+
asyncapi: '2.0.0',
20+
info: {
21+
version: '1.0.1',
22+
description: 'This is a sample server.',
23+
termsOfService: 'http://asyncapi.org/terms/',
24+
},
25+
channels: {
26+
'/user/signedup': {
27+
subscribe: {
28+
message: {
29+
payload: {
30+
type: 'object',
31+
properties: {
32+
email: {
33+
type: 'string',
34+
format: 'email',
35+
},
36+
},
37+
},
38+
},
39+
},
40+
},
41+
},
42+
}),
43+
).toEqual([
44+
{
45+
code: 'asyncapi-schema',
46+
message: '"info" property must have required property "title"',
47+
path: ['info'],
48+
severity: DiagnosticSeverity.Error,
49+
range: expect.any(Object),
50+
},
51+
]);
52+
});
53+
});
54+
55+
describe('given AsyncAPI 2.1.0 document', () => {
56+
test('validate with message examples', async () => {
57+
expect(
58+
await s.run({
59+
asyncapi: '2.1.0',
60+
info: {
61+
title: 'Signup service example (internal)',
62+
version: '0.1.0',
63+
},
64+
channels: {
65+
'/user/signedup': {
66+
subscribe: {
67+
message: {
68+
payload: {
69+
type: 'object',
70+
properties: {
71+
email: {
72+
type: 'string',
73+
format: 'email',
74+
},
75+
},
76+
},
77+
examples: [
78+
{
79+
name: 'Example 1',
80+
summary: 'Example summary for example 1',
81+
payload: {
82+
83+
},
84+
},
85+
],
86+
},
87+
},
88+
},
89+
},
90+
}),
91+
).toEqual([]);
92+
});
93+
});
94+
95+
describe('given AsyncAPI 2.2.0 document', () => {
96+
test('validate channel with connected server', async () => {
97+
expect(
98+
await s.run({
99+
asyncapi: '2.2.0',
100+
info: {
101+
title: 'Signup service example (internal)',
102+
version: '0.1.0',
103+
},
104+
servers: {
105+
kafka: {
106+
url: 'development.gigantic-server.com',
107+
description: 'Development server',
108+
protocol: 'kafka',
109+
protocolVersion: '1.0.0',
110+
},
111+
},
112+
channels: {
113+
'/user/signedup': {
114+
servers: [1, 'foobar', 3],
115+
subscribe: {
116+
message: {
117+
payload: {
118+
type: 'object',
119+
properties: {
120+
email: {
121+
type: 'string',
122+
format: 'email',
123+
},
124+
},
125+
},
126+
},
127+
},
128+
},
129+
},
130+
}),
131+
).toEqual([
132+
{
133+
code: 'asyncapi-schema',
134+
message: '"0" property type must be string',
135+
path: ['channels', '/user/signedup', 'servers', '0'],
136+
severity: DiagnosticSeverity.Error,
137+
range: expect.any(Object),
138+
},
139+
{
140+
code: 'asyncapi-schema',
141+
message: '"2" property type must be string',
142+
path: ['channels', '/user/signedup', 'servers', '2'],
143+
severity: DiagnosticSeverity.Error,
144+
range: expect.any(Object),
145+
},
146+
]);
147+
});
148+
});
149+
150+
describe('given AsyncAPI 2.3.0 document', () => {
151+
test('validate reusable server', async () => {
152+
expect(
153+
await s.run({
154+
asyncapi: '2.3.0',
155+
info: {
156+
title: 'Signup service example (internal)',
157+
version: '0.1.0',
158+
},
159+
channels: {
160+
'/user/signedup': {
161+
subscribe: {
162+
message: {
163+
payload: {
164+
type: 'object',
165+
properties: {
166+
email: {
167+
type: 'string',
168+
format: 'email',
169+
},
170+
},
171+
},
172+
},
173+
},
174+
},
175+
},
176+
components: {
177+
servers: {
178+
kafka: {
179+
description: 'Development server',
180+
},
181+
},
182+
},
183+
}),
184+
).toEqual([
185+
{
186+
code: 'asyncapi-schema',
187+
message: '"kafka" property must have required property "url"',
188+
path: ['components', 'servers', 'kafka'],
189+
severity: DiagnosticSeverity.Error,
190+
range: expect.any(Object),
191+
},
192+
]);
193+
});
194+
});
195+
196+
describe('prepareResults', () => {
197+
test('given oneOf error one of which is required $ref property missing, picks only one error', () => {
198+
const errors: ErrorObject[] = [
199+
{
200+
keyword: 'type',
201+
instancePath: '/paths/test/post/parameters/0/schema/type',
202+
schemaPath: '#/properties/type/type',
203+
params: { type: 'string' },
204+
message: 'must be string',
205+
},
206+
{
207+
keyword: 'required',
208+
instancePath: '/paths/test/post/parameters/0/schema',
209+
schemaPath: '#/definitions/Reference/required',
210+
params: { missingProperty: '$ref' },
211+
message: "must have required property '$ref'",
212+
},
213+
{
214+
keyword: 'oneOf',
215+
instancePath: '/paths/test/post/parameters/0/schema',
216+
schemaPath: '#/properties/schema/oneOf',
217+
params: { passingSchemas: null },
218+
message: 'must match exactly one schema in oneOf',
219+
},
220+
];
221+
222+
prepareResults(errors);
223+
224+
expect(errors).toStrictEqual([
225+
{
226+
keyword: 'type',
227+
instancePath: '/paths/test/post/parameters/0/schema/type',
228+
schemaPath: '#/properties/type/type',
229+
params: { type: 'string' },
230+
message: 'must be string',
231+
},
232+
]);
233+
});
234+
235+
test('given oneOf error one without any $ref property missing, picks all errors', () => {
236+
const errors: ErrorObject[] = [
237+
{
238+
keyword: 'type',
239+
instancePath: '/paths/test/post/parameters/0/schema/type',
240+
schemaPath: '#/properties/type/type',
241+
params: { type: 'string' },
242+
message: 'must be string',
243+
},
244+
{
245+
keyword: 'type',
246+
instancePath: '/paths/test/post/parameters/1/schema/type',
247+
schemaPath: '#/properties/type/type',
248+
params: { type: 'string' },
249+
message: 'must be string',
250+
},
251+
{
252+
keyword: 'oneOf',
253+
instancePath: '/paths/test/post/parameters/0/schema',
254+
schemaPath: '#/properties/schema/oneOf',
255+
params: { passingSchemas: null },
256+
message: 'must match exactly one schema in oneOf',
257+
},
258+
];
259+
260+
prepareResults(errors);
261+
262+
expect(errors).toStrictEqual([
263+
{
264+
keyword: 'type',
265+
instancePath: '/paths/test/post/parameters/0/schema/type',
266+
schemaPath: '#/properties/type/type',
267+
params: { type: 'string' },
268+
message: 'must be string',
269+
},
270+
{
271+
instancePath: '/paths/test/post/parameters/1/schema/type',
272+
keyword: 'type',
273+
message: 'must be string',
274+
params: {
275+
type: 'string',
276+
},
277+
schemaPath: '#/properties/type/type',
278+
},
279+
{
280+
instancePath: '/paths/test/post/parameters/0/schema',
281+
keyword: 'oneOf',
282+
message: 'must match exactly one schema in oneOf',
283+
params: {
284+
passingSchemas: null,
285+
},
286+
schemaPath: '#/properties/schema/oneOf',
287+
},
288+
]);
289+
});
290+
291+
test('given errors with different data paths, picks all errors', () => {
292+
const errors: ErrorObject[] = [
293+
{
294+
keyword: 'type',
295+
instancePath: '/paths/test/post/parameters/0/schema/type',
296+
schemaPath: '#/properties/type/type',
297+
params: { type: 'string' },
298+
message: 'must be string',
299+
},
300+
{
301+
keyword: 'required',
302+
instancePath: '/paths/foo/post/parameters/0/schema',
303+
schemaPath: '#/definitions/Reference/required',
304+
params: { missingProperty: '$ref' },
305+
message: "must have required property '$ref'",
306+
},
307+
{
308+
keyword: 'oneOf',
309+
instancePath: '/paths/baz/post/parameters/0/schema',
310+
schemaPath: '#/properties/schema/oneOf',
311+
params: { passingSchemas: null },
312+
message: 'must match exactly one schema in oneOf',
313+
},
314+
];
315+
316+
prepareResults(errors);
317+
318+
expect(errors).toStrictEqual([
319+
{
320+
instancePath: '/paths/test/post/parameters/0/schema/type',
321+
keyword: 'type',
322+
message: 'must be string',
323+
params: {
324+
type: 'string',
325+
},
326+
schemaPath: '#/properties/type/type',
327+
},
328+
{
329+
instancePath: '/paths/foo/post/parameters/0/schema',
330+
keyword: 'required',
331+
message: "must have required property '$ref'",
332+
params: {
333+
missingProperty: '$ref',
334+
},
335+
schemaPath: '#/definitions/Reference/required',
336+
},
337+
{
338+
instancePath: '/paths/baz/post/parameters/0/schema',
339+
keyword: 'oneOf',
340+
message: 'must match exactly one schema in oneOf',
341+
params: {
342+
passingSchemas: null,
343+
},
344+
schemaPath: '#/properties/schema/oneOf',
345+
},
346+
]);
347+
});
348+
});
349+
});

0 commit comments

Comments
 (0)