Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: iterating between operations, channels and messages #948

Merged
merged 2 commits into from
Feb 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
});
});
});
Loading