Skip to content

Commit

Permalink
fix: iterating between operations, channels and messages (#948)
Browse files Browse the repository at this point in the history
* fix traits

* update test and implementation
  • Loading branch information
jonaslagoni authored Feb 16, 2024
1 parent e5cfaad commit 41ddb9d
Show file tree
Hide file tree
Showing 4 changed files with 130 additions and 48 deletions.
35 changes: 24 additions & 11 deletions src/custom-operations/apply-unique-ids.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,36 @@ import { xParserObjectUniqueId } from '../constants';
/**
* This function applies unique ids for objects whose key's function as ids, ensuring that the key is part of the value.
*
* For v3; Apply unique ids to channel's, and message's
* For v3; Apply unique ids to all channel's, operations, and message's.
*/
export function applyUniqueIds(structure: any) {
const asyncapiVersion = structure.asyncapi.charAt(0);
switch (asyncapiVersion) {
case '3':
if (structure.channels) {
for (const [channelId, channel] of Object.entries(structure.channels as Record<string, any>)) {
channel[xParserObjectUniqueId] = channelId;
if (channel.messages) {
for (const [messageId, message] of Object.entries(channel.messages as Record<string, any>)) {
message[xParserObjectUniqueId] = messageId;
}
}
}
applyUniqueIdToChannels(structure.channels);
applyUniqueIdToObjects(structure.operations);
if (structure.components) {
applyUniqueIdToObjects(structure.components.messages);
applyUniqueIdToObjects(structure.components.operations);
applyUniqueIdToChannels(structure.components.channels);
}
break;
}
}


function applyUniqueIdToChannels(channels: any) {
for (const [channelId, channel] of Object.entries((channels ?? {}) as Record<string, any>)) {
if (!channel[xParserObjectUniqueId]) {
channel[xParserObjectUniqueId] = channelId;
}
applyUniqueIdToObjects(channel.messages);
}
}

function applyUniqueIdToObjects(objects: any) {
for (const [objectKey, object] of Object.entries((objects ?? {}) as Record<string, any>)) {
if (!object[xParserObjectUniqueId]) {
object[xParserObjectUniqueId] = objectKey;
}
}
}
5 changes: 3 additions & 2 deletions src/parse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,6 @@ export async function parse(parser: Parser, spectral: Spectral, asyncapi: Input,
} else {
loadedObj = asyncapi;
}
// Apply unique ids before resolving references
applyUniqueIds(loadedObj);
const { validated, diagnostics, extras } = await validate(parser, spectral, loadedObj, { ...options.validateOptions, source: options.source, __unstable: options.__unstable });
if (validated === undefined) {
return {
Expand All @@ -71,6 +69,9 @@ export async function parse(parser: Parser, spectral: Spectral, asyncapi: Input,

// unfreeze the object - Spectral makes resolved document "freezed"
const validatedDoc = copy(validated as Record<string, any>);

// Apply unique ids which are used as part of iterating between channels <-> operations <-> messages
applyUniqueIds(validatedDoc);
const detailed = createDetailedAsyncAPI(validatedDoc, loadedObj as DetailedAsyncAPI['input'], options.source);
const document = createAsyncAPIDocument(detailed);
setExtension(xParserSpecParsed, true, document);
Expand Down
102 changes: 67 additions & 35 deletions test/custom-operations/apply-traits-v3.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,13 @@ describe('custom operations - apply traits v3', function() {
const v3Document = document as AsyncAPIDocumentV3;
expect(v3Document).toBeInstanceOf(AsyncAPIDocumentV3);

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

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

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

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

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

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

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

it('iterative functions should still work after traits have been applied', async function() {
describe('iterative functions should still work after traits have been applied', function() {
const documentRaw = {
asyncapi: '3.0.0',
info: {
Expand All @@ -187,7 +187,7 @@ describe('custom operations - apply traits v3', function() {
'smartylighting.streetlights.1.0.event.{streetlightId}.lighting.measured': {
address: 'smartylighting.streetlights.1.0.event.{streetlightId}.lighting.measured',
messages: {
'receiveLightMeasurement.message': {
lightMeasured: {
$ref: '#/components/messages/lightMeasured'
}
},
Expand All @@ -213,7 +213,7 @@ describe('custom operations - apply traits v3', function() {
],
messages: [
{
$ref: '#/components/messages/lightMeasured'
$ref: '#/channels/smartylighting.streetlights.1.0.event.{streetlightId}.lighting.measured/messages/lightMeasured'
}
]
}
Expand Down Expand Up @@ -290,32 +290,64 @@ describe('custom operations - apply traits v3', function() {
}
}
};
const { document } = await parser.parse(documentRaw);

const v3Document = document as AsyncAPIDocumentV3;
expect(v3Document).toBeInstanceOf(AsyncAPIDocumentV3);
let v3Document: AsyncAPIDocumentV3;
const expectedOperationId = 'receiveLightMeasurement';
const expectedChannelId = 'smartylighting.streetlights.1.0.event.{streetlightId}.lighting.measured';
const operations = v3Document.operations();
expect(operations.length).toEqual(1);
const operation = operations[0];
expect(operation.id()).toEqual(expectedOperationId);
const operationChannels = operation.channels().all();
expect(operationChannels.length).toEqual(1);
const lightMeasuredChannel = operationChannels[0];
expect(lightMeasuredChannel.json()[xParserObjectUniqueId]).toEqual(expectedChannelId);
const channelOperations = lightMeasuredChannel.operations().all();
expect(channelOperations.length).toEqual(1);
const circularOperation = channelOperations[0];
expect(circularOperation.id()).toEqual(expectedOperationId);
const expectedMessageId = 'lightMeasured';

beforeAll(async () => {
const { document, diagnostics } = await parser.parse(documentRaw);
expect(diagnostics.length).toEqual(0);
v3Document = document as AsyncAPIDocumentV3;
});

it('should be able to go from operation -> channel', () => {
const operations = v3Document.operations().all();
expect(operations.length).toEqual(1);
const operation = operations[0];
expect(operation.id()).toEqual(expectedOperationId);
const operationChannels = operation.channels().all();
expect(operationChannels.length).toEqual(1);
const lightMeasuredChannel = operationChannels[0];
expect(lightMeasuredChannel.json()[xParserObjectUniqueId]).toEqual(expectedChannelId);
const messages = lightMeasuredChannel.messages().all();
expect(messages.length).toEqual(1);
const message = messages[0];
expect(message.id()).toEqual(expectedMessageId);
});

it('should be able to go from channel -> operation', () => {
const channels = v3Document.channels().all();
expect(channels.length).toEqual(1);
const channel = channels[0];
expect(channel.json()[xParserObjectUniqueId]).toEqual(expectedChannelId);
const channelOperations = channel.operations().all();
expect(channelOperations.length).toEqual(1);
const operation = channelOperations[0];
expect(operation.id()).toEqual(expectedOperationId);
const messages = operation.messages().all();
expect(messages.length).toEqual(1);
const message = messages[0];
expect(message.id()).toEqual(expectedMessageId);
});

const channels = v3Document.channels();
expect(channels.length).toEqual(1);
const channel = channels[0];
expect(channel.json()[xParserObjectUniqueId]).toEqual(expectedChannelId);
const channelOperations2 = channel.operations().all();
expect(channelOperations2.length).toEqual(1);
const operation2 = channelOperations2[0];
expect(operation2.id()).toEqual(expectedOperationId);
it('should be able to go in full circle operation -> channel -> operation', () => {
const operations = v3Document.operations().all();
expect(operations.length).toEqual(1);
const operation = operations[0];
expect(operation.id()).toEqual(expectedOperationId);
const operationChannels = operation.channels().all();
expect(operationChannels.length).toEqual(1);
const lightMeasuredChannel = operationChannels[0];
expect(lightMeasuredChannel.json()[xParserObjectUniqueId]).toEqual(expectedChannelId);
const channelOperations = lightMeasuredChannel.operations().all();
expect(channelOperations.length).toEqual(1);
const circularOperation = channelOperations[0];
expect(circularOperation.id()).toEqual(expectedOperationId);
const messages = circularOperation.messages().all();
expect(messages.length).toEqual(1);
const message = messages[0];
expect(message.id()).toEqual(expectedMessageId);
});
});
});
36 changes: 36 additions & 0 deletions test/custom-operations/apply-unique-ids.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,47 @@ describe('applying unique ids', function() {
applyUniqueIds(input);
expect(input).toEqual(output);
});
it('should set unique id when input has operations', async function() {
const input = {asyncapi: '3.0.0', operations: {testOperation: {}}};
const output = {...input, operations: {testOperation: {'x-parser-unique-object-id': 'testOperation'}}};
applyUniqueIds(input);
expect(input).toEqual(output);
});
it('should set unique id when input has messages in channels', async function() {
const input = {asyncapi: '3.0.0', channels: {testChannel: {messages: {testMessage: {}}}}};
const output = {...input, channels: {testChannel: {'x-parser-unique-object-id': 'testChannel', messages: {testMessage: {'x-parser-unique-object-id': 'testMessage'}}}}};
applyUniqueIds(input);
expect(input).toEqual(output);
});
it('should set unique id when input has channels under components', async function() {
const input = {asyncapi: '3.0.0', components: {channels: {testChannel: {}}}};
const output = {...input, components: {channels: {testChannel: {'x-parser-unique-object-id': 'testChannel'}}}};
applyUniqueIds(input);
expect(input).toEqual(output);
});
it('should set unique id when input has operations under components', async function() {
const input = {asyncapi: '3.0.0', components: {operations: {testOperation: {}}}};
const output = {...input, components: {operations: {testOperation: {'x-parser-unique-object-id': 'testOperation'}}}};
applyUniqueIds(input);
expect(input).toEqual(output);
});
it('should set unique id when input has messages in channels under components', async function() {
const input = {asyncapi: '3.0.0', components: {channels: {testChannel: {messages: {testMessage: {}}}}}};
const output = {...input, components: {channels: {testChannel: {'x-parser-unique-object-id': 'testChannel', messages: {testMessage: {'x-parser-unique-object-id': 'testMessage'}}}}}};
applyUniqueIds(input);
expect(input).toEqual(output);
});
it('should set unique id when input has messages under components', async function() {
const input = {asyncapi: '3.0.0', components: { messages: {testMessage: {}}}};
const output = {...input, components: { messages: {testMessage: {'x-parser-unique-object-id': 'testMessage'}}}};
applyUniqueIds(input);
expect(input).toEqual(output);
});
it('should not overwrite existing unique ids', async function() {
const input = {asyncapi: '3.0.0', components: { messages: {testMessage: {'x-parser-unique-object-id': 'testMessage2'}}}};
const output = {...input, components: { messages: {testMessage: {'x-parser-unique-object-id': 'testMessage2'}}}};
applyUniqueIds(input);
expect(input).toEqual(output);
});
});
});

0 comments on commit 41ddb9d

Please sign in to comment.