Skip to content

Commit 41ddb9d

Browse files
authored
fix: iterating between operations, channels and messages (#948)
* fix traits * update test and implementation
1 parent e5cfaad commit 41ddb9d

File tree

4 files changed

+130
-48
lines changed

4 files changed

+130
-48
lines changed

src/custom-operations/apply-unique-ids.ts

+24-11
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,36 @@ import { xParserObjectUniqueId } from '../constants';
33
/**
44
* This function applies unique ids for objects whose key's function as ids, ensuring that the key is part of the value.
55
*
6-
* For v3; Apply unique ids to channel's, and message's
6+
* For v3; Apply unique ids to all channel's, operations, and message's.
77
*/
88
export function applyUniqueIds(structure: any) {
99
const asyncapiVersion = structure.asyncapi.charAt(0);
1010
switch (asyncapiVersion) {
1111
case '3':
12-
if (structure.channels) {
13-
for (const [channelId, channel] of Object.entries(structure.channels as Record<string, any>)) {
14-
channel[xParserObjectUniqueId] = channelId;
15-
if (channel.messages) {
16-
for (const [messageId, message] of Object.entries(channel.messages as Record<string, any>)) {
17-
message[xParserObjectUniqueId] = messageId;
18-
}
19-
}
20-
}
12+
applyUniqueIdToChannels(structure.channels);
13+
applyUniqueIdToObjects(structure.operations);
14+
if (structure.components) {
15+
applyUniqueIdToObjects(structure.components.messages);
16+
applyUniqueIdToObjects(structure.components.operations);
17+
applyUniqueIdToChannels(structure.components.channels);
2118
}
2219
break;
2320
}
2421
}
25-
22+
23+
function applyUniqueIdToChannels(channels: any) {
24+
for (const [channelId, channel] of Object.entries((channels ?? {}) as Record<string, any>)) {
25+
if (!channel[xParserObjectUniqueId]) {
26+
channel[xParserObjectUniqueId] = channelId;
27+
}
28+
applyUniqueIdToObjects(channel.messages);
29+
}
30+
}
31+
32+
function applyUniqueIdToObjects(objects: any) {
33+
for (const [objectKey, object] of Object.entries((objects ?? {}) as Record<string, any>)) {
34+
if (!object[xParserObjectUniqueId]) {
35+
object[xParserObjectUniqueId] = objectKey;
36+
}
37+
}
38+
}

src/parse.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,6 @@ export async function parse(parser: Parser, spectral: Spectral, asyncapi: Input,
5555
} else {
5656
loadedObj = asyncapi;
5757
}
58-
// Apply unique ids before resolving references
59-
applyUniqueIds(loadedObj);
6058
const { validated, diagnostics, extras } = await validate(parser, spectral, loadedObj, { ...options.validateOptions, source: options.source, __unstable: options.__unstable });
6159
if (validated === undefined) {
6260
return {
@@ -71,6 +69,9 @@ export async function parse(parser: Parser, spectral: Spectral, asyncapi: Input,
7169

7270
// unfreeze the object - Spectral makes resolved document "freezed"
7371
const validatedDoc = copy(validated as Record<string, any>);
72+
73+
// Apply unique ids which are used as part of iterating between channels <-> operations <-> messages
74+
applyUniqueIds(validatedDoc);
7475
const detailed = createDetailedAsyncAPI(validatedDoc, loadedObj as DetailedAsyncAPI['input'], options.source);
7576
const document = createAsyncAPIDocument(detailed);
7677
setExtension(xParserSpecParsed, true, document);

test/custom-operations/apply-traits-v3.spec.ts

+67-35
Original file line numberDiff line numberDiff line change
@@ -55,13 +55,13 @@ describe('custom operations - apply traits v3', function() {
5555
const v3Document = document as AsyncAPIDocumentV3;
5656
expect(v3Document).toBeInstanceOf(AsyncAPIDocumentV3);
5757

58-
const someOperation1 = v3Document?.json()?.operations?.someOperation1;
58+
const someOperation1 = v3Document?.json()?.operations?.someOperation1 as v3.OperationObject;
5959
delete someOperation1?.traits;
60-
expect(someOperation1).toEqual({ action: 'send', channel: { 'x-parser-unique-object-id': 'channel1' }, description: 'another description' });
60+
expect(someOperation1).toEqual({ action: 'send', channel: { 'x-parser-unique-object-id': 'channel1' }, description: 'another description', 'x-parser-unique-object-id': 'someOperation1' });
6161

62-
const someOperation2 = v3Document?.json()?.operations?.someOperation2;
62+
const someOperation2 = v3Document?.json()?.operations?.someOperation2 as v3.OperationObject;
6363
delete someOperation2?.traits;
64-
expect(someOperation2).toEqual({ action: 'send', channel: { 'x-parser-unique-object-id': 'channel1' }, description: 'root description' });
64+
expect(someOperation2).toEqual({ action: 'send', channel: { 'x-parser-unique-object-id': 'channel1' }, description: 'root description', 'x-parser-unique-object-id': 'someOperation2' });
6565
});
6666

6767
it('should apply traits to messages (channels)', async function() {
@@ -111,11 +111,11 @@ describe('custom operations - apply traits v3', function() {
111111
const v3Document = document as AsyncAPIDocumentV3;
112112
expect(v3Document).toBeInstanceOf(AsyncAPIDocumentV3);
113113

114-
const message1 = v3Document?.json()?.channels?.someChannel1?.messages?.someMessage;
114+
const message1 = (v3Document?.json()?.channels?.someChannel1 as v3.ChannelObject).messages?.someMessage;
115115
delete (message1 as v3.MessageObject)?.traits;
116116
expect(message1).toEqual({ summary: 'some summary', description: 'another description', 'x-parser-message-name': 'someMessage', 'x-parser-unique-object-id': 'someMessage' });
117117

118-
const message2 = v3Document?.json()?.channels?.someChannel2?.messages?.someMessage;
118+
const message2 = (v3Document?.json()?.channels?.someChannel2 as v3.ChannelObject).messages?.someMessage;
119119
delete (message2 as v3.MessageObject)?.traits;
120120
expect(message2).toEqual({ summary: 'root summary', description: 'root description', 'x-parser-message-name': 'someMessage', 'x-parser-unique-object-id': 'someMessage' });
121121
});
@@ -163,14 +163,14 @@ describe('custom operations - apply traits v3', function() {
163163

164164
const message1 = v3Document?.json()?.components?.messages?.someMessage1;
165165
delete (message1 as v3.MessageObject)?.traits;
166-
expect(message1).toEqual({ summary: 'some summary', description: 'another description', 'x-parser-message-name': 'someMessage1' });
166+
expect(message1).toEqual({ summary: 'some summary', description: 'another description', 'x-parser-message-name': 'someMessage1', 'x-parser-unique-object-id': 'someMessage1' });
167167

168168
const message2 = v3Document?.json()?.components?.messages?.someMessage2;
169169
delete (message2 as v3.MessageObject)?.traits;
170-
expect(message2).toEqual({ summary: 'root summary', description: 'root description', 'x-parser-message-name': 'someMessage2' });
170+
expect(message2).toEqual({ summary: 'root summary', description: 'root description', 'x-parser-message-name': 'someMessage2', 'x-parser-unique-object-id': 'someMessage2' });
171171
});
172172

173-
it('iterative functions should still work after traits have been applied', async function() {
173+
describe('iterative functions should still work after traits have been applied', function() {
174174
const documentRaw = {
175175
asyncapi: '3.0.0',
176176
info: {
@@ -187,7 +187,7 @@ describe('custom operations - apply traits v3', function() {
187187
'smartylighting.streetlights.1.0.event.{streetlightId}.lighting.measured': {
188188
address: 'smartylighting.streetlights.1.0.event.{streetlightId}.lighting.measured',
189189
messages: {
190-
'receiveLightMeasurement.message': {
190+
lightMeasured: {
191191
$ref: '#/components/messages/lightMeasured'
192192
}
193193
},
@@ -213,7 +213,7 @@ describe('custom operations - apply traits v3', function() {
213213
],
214214
messages: [
215215
{
216-
$ref: '#/components/messages/lightMeasured'
216+
$ref: '#/channels/smartylighting.streetlights.1.0.event.{streetlightId}.lighting.measured/messages/lightMeasured'
217217
}
218218
]
219219
}
@@ -290,32 +290,64 @@ describe('custom operations - apply traits v3', function() {
290290
}
291291
}
292292
};
293-
const { document } = await parser.parse(documentRaw);
294-
295-
const v3Document = document as AsyncAPIDocumentV3;
296-
expect(v3Document).toBeInstanceOf(AsyncAPIDocumentV3);
293+
let v3Document: AsyncAPIDocumentV3;
297294
const expectedOperationId = 'receiveLightMeasurement';
298295
const expectedChannelId = 'smartylighting.streetlights.1.0.event.{streetlightId}.lighting.measured';
299-
const operations = v3Document.operations();
300-
expect(operations.length).toEqual(1);
301-
const operation = operations[0];
302-
expect(operation.id()).toEqual(expectedOperationId);
303-
const operationChannels = operation.channels().all();
304-
expect(operationChannels.length).toEqual(1);
305-
const lightMeasuredChannel = operationChannels[0];
306-
expect(lightMeasuredChannel.json()[xParserObjectUniqueId]).toEqual(expectedChannelId);
307-
const channelOperations = lightMeasuredChannel.operations().all();
308-
expect(channelOperations.length).toEqual(1);
309-
const circularOperation = channelOperations[0];
310-
expect(circularOperation.id()).toEqual(expectedOperationId);
296+
const expectedMessageId = 'lightMeasured';
297+
298+
beforeAll(async () => {
299+
const { document, diagnostics } = await parser.parse(documentRaw);
300+
expect(diagnostics.length).toEqual(0);
301+
v3Document = document as AsyncAPIDocumentV3;
302+
});
303+
304+
it('should be able to go from operation -> channel', () => {
305+
const operations = v3Document.operations().all();
306+
expect(operations.length).toEqual(1);
307+
const operation = operations[0];
308+
expect(operation.id()).toEqual(expectedOperationId);
309+
const operationChannels = operation.channels().all();
310+
expect(operationChannels.length).toEqual(1);
311+
const lightMeasuredChannel = operationChannels[0];
312+
expect(lightMeasuredChannel.json()[xParserObjectUniqueId]).toEqual(expectedChannelId);
313+
const messages = lightMeasuredChannel.messages().all();
314+
expect(messages.length).toEqual(1);
315+
const message = messages[0];
316+
expect(message.id()).toEqual(expectedMessageId);
317+
});
318+
319+
it('should be able to go from channel -> operation', () => {
320+
const channels = v3Document.channels().all();
321+
expect(channels.length).toEqual(1);
322+
const channel = channels[0];
323+
expect(channel.json()[xParserObjectUniqueId]).toEqual(expectedChannelId);
324+
const channelOperations = channel.operations().all();
325+
expect(channelOperations.length).toEqual(1);
326+
const operation = channelOperations[0];
327+
expect(operation.id()).toEqual(expectedOperationId);
328+
const messages = operation.messages().all();
329+
expect(messages.length).toEqual(1);
330+
const message = messages[0];
331+
expect(message.id()).toEqual(expectedMessageId);
332+
});
311333

312-
const channels = v3Document.channels();
313-
expect(channels.length).toEqual(1);
314-
const channel = channels[0];
315-
expect(channel.json()[xParserObjectUniqueId]).toEqual(expectedChannelId);
316-
const channelOperations2 = channel.operations().all();
317-
expect(channelOperations2.length).toEqual(1);
318-
const operation2 = channelOperations2[0];
319-
expect(operation2.id()).toEqual(expectedOperationId);
334+
it('should be able to go in full circle operation -> channel -> operation', () => {
335+
const operations = v3Document.operations().all();
336+
expect(operations.length).toEqual(1);
337+
const operation = operations[0];
338+
expect(operation.id()).toEqual(expectedOperationId);
339+
const operationChannels = operation.channels().all();
340+
expect(operationChannels.length).toEqual(1);
341+
const lightMeasuredChannel = operationChannels[0];
342+
expect(lightMeasuredChannel.json()[xParserObjectUniqueId]).toEqual(expectedChannelId);
343+
const channelOperations = lightMeasuredChannel.operations().all();
344+
expect(channelOperations.length).toEqual(1);
345+
const circularOperation = channelOperations[0];
346+
expect(circularOperation.id()).toEqual(expectedOperationId);
347+
const messages = circularOperation.messages().all();
348+
expect(messages.length).toEqual(1);
349+
const message = messages[0];
350+
expect(message.id()).toEqual(expectedMessageId);
351+
});
320352
});
321353
});

test/custom-operations/apply-unique-ids.spec.ts

+36
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,47 @@ describe('applying unique ids', function() {
2020
applyUniqueIds(input);
2121
expect(input).toEqual(output);
2222
});
23+
it('should set unique id when input has operations', async function() {
24+
const input = {asyncapi: '3.0.0', operations: {testOperation: {}}};
25+
const output = {...input, operations: {testOperation: {'x-parser-unique-object-id': 'testOperation'}}};
26+
applyUniqueIds(input);
27+
expect(input).toEqual(output);
28+
});
2329
it('should set unique id when input has messages in channels', async function() {
2430
const input = {asyncapi: '3.0.0', channels: {testChannel: {messages: {testMessage: {}}}}};
2531
const output = {...input, channels: {testChannel: {'x-parser-unique-object-id': 'testChannel', messages: {testMessage: {'x-parser-unique-object-id': 'testMessage'}}}}};
2632
applyUniqueIds(input);
2733
expect(input).toEqual(output);
2834
});
35+
it('should set unique id when input has channels under components', async function() {
36+
const input = {asyncapi: '3.0.0', components: {channels: {testChannel: {}}}};
37+
const output = {...input, components: {channels: {testChannel: {'x-parser-unique-object-id': 'testChannel'}}}};
38+
applyUniqueIds(input);
39+
expect(input).toEqual(output);
40+
});
41+
it('should set unique id when input has operations under components', async function() {
42+
const input = {asyncapi: '3.0.0', components: {operations: {testOperation: {}}}};
43+
const output = {...input, components: {operations: {testOperation: {'x-parser-unique-object-id': 'testOperation'}}}};
44+
applyUniqueIds(input);
45+
expect(input).toEqual(output);
46+
});
47+
it('should set unique id when input has messages in channels under components', async function() {
48+
const input = {asyncapi: '3.0.0', components: {channels: {testChannel: {messages: {testMessage: {}}}}}};
49+
const output = {...input, components: {channels: {testChannel: {'x-parser-unique-object-id': 'testChannel', messages: {testMessage: {'x-parser-unique-object-id': 'testMessage'}}}}}};
50+
applyUniqueIds(input);
51+
expect(input).toEqual(output);
52+
});
53+
it('should set unique id when input has messages under components', async function() {
54+
const input = {asyncapi: '3.0.0', components: { messages: {testMessage: {}}}};
55+
const output = {...input, components: { messages: {testMessage: {'x-parser-unique-object-id': 'testMessage'}}}};
56+
applyUniqueIds(input);
57+
expect(input).toEqual(output);
58+
});
59+
it('should not overwrite existing unique ids', async function() {
60+
const input = {asyncapi: '3.0.0', components: { messages: {testMessage: {'x-parser-unique-object-id': 'testMessage2'}}}};
61+
const output = {...input, components: { messages: {testMessage: {'x-parser-unique-object-id': 'testMessage2'}}}};
62+
applyUniqueIds(input);
63+
expect(input).toEqual(output);
64+
});
2965
});
3066
});

0 commit comments

Comments
 (0)