Skip to content

Commit

Permalink
fix: use JSON Pointer last element as ID for reply.channel and reply.…
Browse files Browse the repository at this point in the history
…messages[]
  • Loading branch information
smoya committed Nov 10, 2023
1 parent 0a765c1 commit 918b5fe
Show file tree
Hide file tree
Showing 5 changed files with 107 additions and 10 deletions.
18 changes: 18 additions & 0 deletions src/models/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,21 @@ export type InferModelMetadata<T> = T extends BaseModel<infer _, infer M> ? M :
export function createModel<T extends BaseModel>(Model: Constructor<T>, value: InferModelData<T>, meta: Omit<ModelMetadata, 'asyncapi'> & { asyncapi?: DetailedAsyncAPI } & InferModelMetadata<T>, parent?: BaseModel): T {
return new Model(value, { ...meta, asyncapi: meta.asyncapi || parent?.meta().asyncapi });
}

export function objectIdFromJSONPointer(path: string, data: any): string {
if (typeof data === 'string') {
data = JSON.parse(data);
}

if (!path.endsWith('/$ref')) path += '/$ref';

path = path.replace(/^\//, ''); // removing the leading slash
path.split('/').forEach((subpath) => {
const key = subpath.replace(/~1/, '/'); // recover escaped slashes
if (data[key] !== undefined) {
data = data[key];
}
});

return data.split('/').pop().replace(/~1/, '/');
}
10 changes: 6 additions & 4 deletions src/models/v3/operation-reply.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import type { ChannelInterface } from '../channel';

import type { v3 } from '../../spec-types';

import { objectIdFromJSONPointer } from '../utils';

export class OperationReply extends BaseModel<v3.OperationReplyObject, { id?: string }> implements OperationReplyInterface {
id(): string | undefined {
return this._meta.id;
Expand All @@ -35,14 +37,14 @@ export class OperationReply extends BaseModel<v3.OperationReplyObject, { id?: st

channel(): ChannelInterface | undefined {
if (this._json.channel) {
return this.createModel(Channel, this._json.channel as v3.ChannelObject, { id: '', pointer: this.jsonPath('channel') });
return this.createModel(Channel, this._json.channel as v3.ChannelObject, { id: objectIdFromJSONPointer(this.jsonPath('channel'), this._meta.asyncapi.input), pointer: this.jsonPath('channel') });
}
return this._json.channel;
}
messages(): MessagesInterface {
return new Messages(
Object.entries(this._json.messages || {}).map(([messageName, message]) => {
return this.createModel(Message, message as v3.MessageObject, { id: messageName, pointer: this.jsonPath(`messages/${messageName}`) });
return new Messages(
Object.entries(this._json.messages || {}).map(([i, message]) => {
return this.createModel(Message, message as v3.MessageObject, { id: objectIdFromJSONPointer(this.jsonPath(`messages/${i}`), this._meta.asyncapi.input), pointer: this.jsonPath(`messages/${i}`) });
})
);
}
Expand Down
2 changes: 1 addition & 1 deletion src/spec-types/v3.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ export interface OperationTraitObject extends SpecificationExtensions {

export interface OperationReplyObject extends SpecificationExtensions {
channel?: ChannelObject | ReferenceObject;
messages?: MessagesObject;
messages?: Array<MessageObject | ReferenceObject>;
address?: OperationReplyAddressObject | ReferenceObject;
}

Expand Down
8 changes: 4 additions & 4 deletions test/models/v3/operation-reply.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,20 +64,20 @@ describe('OperationReply model', function() {

describe('.messages()', function() {
it('should return collection of messages - single message', function() {
const d = new OperationReply({ messages: { someMessage: {} } });
const d = new OperationReply({ messages: [{}] });
expect(d.messages()).toBeInstanceOf(Messages);
expect(d.messages().all()).toHaveLength(1);
expect(d.messages().all()[0]).toBeInstanceOf(Message);
});

it('should return collection of messages - more than one messages', function() {
const d = new OperationReply({ messages: { someMessage1: {}, someMessage2: {} } });
const d = new OperationReply({ messages: [{name: 'someMessage1'}, {name: 'someMessage2'}] });
expect(d.messages()).toBeInstanceOf(Messages);
expect(d.messages().all()).toHaveLength(2);
expect(d.messages().all()[0]).toBeInstanceOf(Message);
expect(d.messages().all()[0].id()).toEqual('someMessage1');
expect(d.messages().all()[0].name()).toEqual('someMessage1');
expect(d.messages().all()[1]).toBeInstanceOf(Message);
expect(d.messages().all()[1].id()).toEqual('someMessage2');
expect(d.messages().all()[1].name()).toEqual('someMessage2');
});

it('should return undefined if address is not present', function() {
Expand Down
79 changes: 78 additions & 1 deletion test/resolver.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,84 @@
import { AsyncAPIDocumentV2 } from '../src/models';
import { AsyncAPIDocumentV2, AsyncAPIDocumentV3 } from '../src/models';
import { Parser } from '../src/parser';

describe('custom resolver', function() {
it('Reply channel + messages[] Ids come from JSON Pointer', async function() {
const parser = new Parser();

const documentRaw = `{
"asyncapi": "3.0.0",
"info": {
"title": "Account Service",
"version": "1.0.0",
"description": "This service is in charge of processing user signups"
},
"channels": {
"user/signedup": {
"address": "user/signedup",
"messages": {
"subscribe.message": {
"$ref": "#/components/messages/UserSignedUp"
}
}
}
},
"operations": {
"user/signedup.subscribe": {
"action": "send",
"channel": {
"$ref": "#/channels/user~1signedup"
},
"messages": [
{
"$ref": "#/components/messages/UserSignedUp"
}
],
"reply": {
"channel": {
"$ref": "#/channels/user~1signedup"
},
"messages": [
{
"$ref": "#/components/messages/UserSignedUp"
}
]
}
}
},
"components": {
"messages": {
"UserSignedUp": {
"payload": {
"type": "object",
"properties": {
"displayName": {
"type": "string",
"description": "Name of the user"
},
"email": {
"type": "string",
"format": "email",
"description": "Email of the user"
}
}
}
}
}
}
}`;
const { document } = await parser.parse(documentRaw);

expect(document).toBeInstanceOf(AsyncAPIDocumentV3);

const operation = document?.operations().get('user/signedup.subscribe');
const replyObj = operation?.reply();
expect(replyObj?.channel()?.id()).toEqual('user/signedup');

const message = replyObj?.messages()?.get('UserSignedUp');
expect(message).toBeDefined();
expect(message?.id()).toEqual('UserSignedUp');
});

it('should resolve document references', async function() {
const parser = new Parser();

Expand Down

0 comments on commit 918b5fe

Please sign in to comment.