Skip to content
This repository was archived by the owner on Jun 18, 2023. It is now read-only.

Commit 81c2c5d

Browse files
committed
feat: Add new tab with message binding to channel documentation and add header and message binding example to example tab
1 parent 9c3cf8e commit 81c2c5d

File tree

7 files changed

+214
-69
lines changed

7 files changed

+214
-69
lines changed

src/app/channels/channel-main/channel-main.component.html

Lines changed: 35 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,42 @@ <h4>{{ operation.message.description }}</h4>
77
<mat-tab-group animationDuration="0ms">
88
<mat-tab label="Example">
99
<div fxLayout="column">
10-
<textarea spellcheck="false"
11-
#exampleTextArea
12-
[rows]="exampleTextAreaLineCount"
13-
[value]="defaultExample?.value"
14-
(keyup)="recalculateLineCount('example', exampleTextArea.value)"
15-
></textarea>
10+
<div *ngIf="isEmptyObject(operation.message.bindings.get(protocolName))" fxLayout="column" fxLayoutGap="5px">
11+
<h4>Binding</h4>
12+
<textarea spellcheck="false"
13+
#bindingTextArea
14+
[rows]="messageBindingExampleTextAreaLineCount"
15+
[value]="createMessageBindingExample(operation.message.bindings.get(protocolName))?.value"
16+
(keyup)="recalculateLineCount('massageBindingExample', bindingTextArea.value)"
17+
></textarea>
18+
</div>
19+
<div *ngIf="headers" fxLayout="column" fxLayoutGap="0px">
20+
<h4>Header</h4>
21+
<textarea spellcheck="false"
22+
#headersTextArea
23+
[rows]="headersTextAreaLineCount"
24+
[value]="headersExample?.value"
25+
(keyup)="recalculateLineCount('headers', headersTextArea.value)"
26+
></textarea>
27+
</div>
28+
<div fxLayout="column" fxLayoutGap="5px">
29+
<h4>Message</h4>
30+
<textarea spellcheck="false"
31+
#messageTextArea
32+
[rows]="exampleTextAreaLineCount"
33+
[value]="defaultExample?.value"
34+
(keyup)="recalculateLineCount('example', messageTextArea.value)"
35+
></textarea>
36+
</div>
1637
<div fxLayout fxLayoutGap="8px">
17-
<button mat-raised-button color="primary" (click)="publish(exampleTextArea.value, headersTextArea.value)">
38+
<button mat-raised-button color="primary" (click)="publish(messageTextArea.value, headersTextArea?.value)">
1839
Publish
1940
</button>
2041
<button mat-raised-button color="primary"
21-
(click)="exampleTextArea.value = defaultExample.value; exampleTextAreaLineCount=defaultExample.lineCount">
42+
(click)="messageTextArea.value = defaultExample.value; exampleTextAreaLineCount=defaultExample.lineCount">
2243
Default
2344
</button>
24-
<button mat-raised-button color="primary" [cdkCopyToClipboard]="exampleTextArea.value">Copy</button>
45+
<button mat-raised-button color="primary" [cdkCopyToClipboard]="messageTextArea.value">Copy</button>
2546
</div>
2647
</div>
2748
</mat-tab>
@@ -43,16 +64,14 @@ <h4>
4364
</h4>
4465
<app-schema *ngIf="headers" [schema]="headers"></app-schema>
4566
<div fxLayout="column">
46-
<textarea spellcheck="false"
47-
#headersTextArea
48-
[rows]="headersTextAreaLineCount"
49-
[value]="headersExample?.value"
50-
(keyup)="recalculateLineCount('headers', headersTextArea.value)"
51-
></textarea>
67+
<app-json [json]="headersExample?.value"></app-json>
5268
</div>
5369
</mat-tab>
54-
<mat-tab label="Bindings">
70+
<mat-tab label="Operation Bindings">
5571
<app-json [data]="operation.bindings[protocolName]"></app-json>
5672
</mat-tab>
73+
<mat-tab label="Message Bindings">
74+
<app-json [data]="operation.message.bindings.get(protocolName)"></app-json>
75+
</mat-tab>
5776
</mat-tab-group>
5877
</section>

src/app/channels/channel-main/channel-main.component.ts

Lines changed: 47 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { Example } from 'src/app/shared/models/example.model';
44
import { Schema } from 'src/app/shared/models/schema.model';
55
import { PublisherService } from 'src/app/shared/publisher.service';
66
import { MatSnackBar } from '@angular/material/snack-bar';
7-
import { Operation } from 'src/app/shared/models/channel.model';
7+
import {MessageBinding, Operation} from 'src/app/shared/models/channel.model';
88
import { STATUS } from 'angular-in-memory-web-api';
99

1010
@Component({
@@ -27,6 +27,9 @@ export class ChannelMainComponent implements OnInit {
2727
headersExample: Example;
2828
headersTextAreaLineCount: number;
2929
protocolName: string;
30+
messageBindingExample?: Example;
31+
messageBindingExampleTextAreaLineCount: number;
32+
headersTextArea?: HTMLTextAreaElement;
3033

3134
constructor(
3235
private asyncApiService: AsyncApiService,
@@ -38,47 +41,82 @@ export class ChannelMainComponent implements OnInit {
3841
ngOnInit(): void {
3942
this.asyncApiService.getAsyncApi().subscribe(
4043
asyncapi => {
41-
let schemas: Map<string, Schema> = asyncapi.components.schemas;
42-
this.schemaName = this.operation.message.payload.name.slice(this.operation.message.payload.name.lastIndexOf('/') + 1)
44+
const schemas: Map<string, Schema> = asyncapi.components.schemas;
45+
this.schemaName = this.operation.message.payload.name.slice(this.operation.message.payload.name.lastIndexOf('/') + 1);
4346
this.schema = schemas.get(this.schemaName);
4447

4548
this.defaultExample = this.schema.example;
4649
this.exampleTextAreaLineCount = this.defaultExample?.lineCount || 0;
4750

48-
this.headersSchemaName = this.operation.message.headers.name.slice(this.operation.message.headers.name.lastIndexOf('/') + 1)
51+
this.headersSchemaName = this.operation.message.headers.name.slice(this.operation.message.headers.name.lastIndexOf('/') + 1);
4952
this.headers = schemas.get(this.headersSchemaName);
5053
this.headersExample = this.headers.example;
5154
this.headersTextAreaLineCount = this.headersExample?.lineCount || 0;
55+
this.messageBindingExampleTextAreaLineCount = this.messageBindingExample?.lineCount || 0;
5256
}
5357
);
5458

5559
this.protocolName = Object.keys(this.operation.bindings)[0];
5660
}
5761

62+
isEmptyObject(object?: any): boolean {
63+
return (object === undefined || object === null) || Object.keys(object).length > 0;
64+
}
65+
66+
createMessageBindingExample(messageBinding?: MessageBinding): Example | undefined {
67+
if (messageBinding === undefined || messageBinding === null) {
68+
return undefined;
69+
}
70+
71+
const bindingExampleObject = {};
72+
Object.keys(messageBinding).forEach((bindingKey) => {
73+
if (bindingKey !== 'bindingVersion') {
74+
bindingExampleObject[bindingKey] = this.getExampleValue(messageBinding[bindingKey]);
75+
}
76+
});
77+
78+
const bindingExample = new Example(bindingExampleObject);
79+
80+
this.messageBindingExampleTextAreaLineCount = bindingExample.lineCount;
81+
82+
return bindingExample;
83+
}
84+
85+
getExampleValue(bindingValue: string | Schema): any {
86+
if (typeof bindingValue === 'string') {
87+
return bindingValue;
88+
} else {
89+
return bindingValue.example.value;
90+
}
91+
}
92+
5893
recalculateLineCount(field: string, text: string): void {
5994
switch (field) {
6095
case 'example':
6196
this.exampleTextAreaLineCount = text.split('\n').length;
6297
break;
6398
case 'headers':
64-
this.headersTextAreaLineCount = text.split('\n').length
99+
this.headersTextAreaLineCount = text.split('\n').length;
100+
break;
101+
case 'massageBindingExample':
102+
this.messageBindingExampleTextAreaLineCount = text.split('\n').length;
65103
break;
66104
}
67105
}
68106

69-
publish(example: string, headers: string): void {
107+
publish(example: string, headers?: string): void {
70108
try {
71109
const payloadJson = JSON.parse(example);
72-
const headersJson = JSON.parse(headers)
110+
const headersJson = JSON.parse(headers);
73111

74112
this.publisherService.publish(this.protocolName, this.channelName, payloadJson, headersJson).subscribe(
75113
_ => this.handlePublishSuccess(),
76114
err => this.handlePublishError(err)
77115
);
78-
} catch(error) {
116+
} catch (error) {
79117
this.snackBar.open('Example payload is not valid', 'ERROR', {
80118
duration: 3000
81-
})
119+
});
82120
}
83121
}
84122

src/app/shared/asyncapi-mapper.service.ts

Lines changed: 73 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
import { AsyncApi } from './models/asyncapi.model';
2-
import { Server } from './models/server.model';
3-
import {Channel, CHANNEL_ANCHOR_PREFIX, Message, Operation, OperationType} from './models/channel.model';
4-
import { Schema } from './models/schema.model';
5-
import { Injectable } from '@angular/core';
6-
import {Example} from "./models/example.model";
7-
import {Info} from "./models/info.model";
1+
import {AsyncApi} from './models/asyncapi.model';
2+
import {Server} from './models/server.model';
3+
import {Channel, CHANNEL_ANCHOR_PREFIX, Message, MessageBinding, Operation, OperationType} from './models/channel.model';
4+
import {Schema} from './models/schema.model';
5+
import {Injectable} from '@angular/core';
6+
import {Example} from './models/example.model';
7+
import {Info} from './models/info.model';
88

99
interface ServerAsyncApiSchema {
1010
description?: string;
@@ -26,6 +26,10 @@ interface ServerAsyncApiMessage {
2626
description?: string;
2727
payload: { $ref: string };
2828
headers: { $ref: string };
29+
bindings: {[key: string]: ServerAsyncApiMessageBinding};
30+
}
31+
interface ServerAsyncApiMessageBinding {
32+
[key: string]: ServerAsyncApiSchema | string;
2933
}
3034

3135
interface ServerAsyncApiInfo {
@@ -64,7 +68,7 @@ export interface ServerAsyncApi {
6468

6569
@Injectable()
6670
export class AsyncApiMapperService {
67-
static BASE_URL = window.location.pathname + window.location.search + "#";
71+
static BASE_URL = window.location.pathname + window.location.search + '#';
6872

6973
constructor() {
7074
}
@@ -89,49 +93,52 @@ export class AsyncApiMapperService {
8993
};
9094
}
9195

92-
private mapServers(servers: ServerAsyncApi["servers"]): Map<string, Server> {
96+
private mapServers(servers: ServerAsyncApi['servers']): Map<string, Server> {
9397
const s = new Map<string, Server>();
9498
Object.entries(servers).forEach(([k, v]) => s.set(k, v));
9599
return s;
96100
}
97101

98-
private mapChannels(channels: ServerAsyncApi["channels"]): Channel[] {
102+
private mapChannels(channels: ServerAsyncApi['channels']): Channel[] {
99103
const s = new Array<Channel>();
100104
Object.entries(channels).forEach(([k, v]) => {
101-
const subscriberChannels = this.mapChannel(k, v.description, v.subscribe, "subscribe")
102-
subscriberChannels.forEach(channel => s.push(channel))
105+
const subscriberChannels = this.mapChannel(k, v.description, v.subscribe, 'subscribe');
106+
subscriberChannels.forEach(channel => s.push(channel));
103107

104-
const publisherChannels = this.mapChannel(k, v.description, v.publish, "publish")
105-
publisherChannels.forEach(channel => s.push(channel))
108+
const publisherChannels = this.mapChannel(k, v.description, v.publish, 'publish');
109+
publisherChannels.forEach(channel => s.push(channel));
106110
});
107111
return s;
108112
}
109113

110114
private mapChannel(
111115
topicName: string,
112-
description: ServerAsyncApi["channels"][""]["description"],
113-
serverOperation: ServerAsyncApi["channels"][""]["subscribe"] | ServerAsyncApi["channels"][""]["publish"],
114-
operationType: OperationType): Channel[]
116+
description: ServerAsyncApi['channels']['']['description'],
117+
serverOperation: ServerAsyncApi['channels']['']['subscribe'] | ServerAsyncApi['channels']['']['publish'],
118+
operationType: OperationType
119+
): Channel[]
115120
{
116-
if(serverOperation !== undefined) {
117-
let messages: Message[] = this.mapMessages(serverOperation.message)
121+
if (serverOperation !== undefined) {
122+
const messages: Message[] = this.mapMessages(serverOperation.message);
118123

119124
return messages.map(message => {
120-
const operation = this.mapOperation(operationType, message, serverOperation.bindings)
125+
const operation = this.mapOperation(operationType, message, serverOperation.bindings);
121126
return {
122127
name: topicName,
123-
anchorIdentifier: CHANNEL_ANCHOR_PREFIX + [operation.protocol, topicName, operation.operation, operation.message.title].join( "-"),
124-
description: description,
125-
operation: operation,
126-
}
127-
})
128+
anchorIdentifier: CHANNEL_ANCHOR_PREFIX + [
129+
operation.protocol, topicName, operation.operation, operation.message.title
130+
].join( '-'),
131+
description,
132+
operation,
133+
};
134+
});
128135
}
129136
return [];
130137
}
131138

132139
private mapMessages(message: ServerAsyncApiChannelMessage): Message[] {
133-
if('oneOf' in message) {
134-
return this.mapServerAsyncApiMessages(message.oneOf)
140+
if ('oneOf' in message) {
141+
return this.mapServerAsyncApiMessages(message.oneOf);
135142
}
136143
return this.mapServerAsyncApiMessages([message]);
137144
}
@@ -144,23 +151,50 @@ export class AsyncApiMapperService {
144151
description: v.description,
145152
payload: {
146153
name: v.payload.$ref,
147-
anchorUrl: AsyncApiMapperService.BASE_URL +v.payload.$ref?.split('/')?.pop()
154+
anchorUrl: AsyncApiMapperService.BASE_URL + v.payload.$ref?.split('/')?.pop()
148155
},
149156
headers: {
150157
name: v.headers.$ref,
151158
anchorUrl: AsyncApiMapperService.BASE_URL + v.headers.$ref?.split('/')?.pop()
152-
}
153-
}
154-
})
159+
},
160+
bindings: this.mapServerAsyncApiMessageBindings(v.bindings)
161+
};
162+
});
155163
}
156164

157-
private mapOperation(operationType: OperationType, message: Message, bindings?: any): Operation {
165+
private mapServerAsyncApiMessageBindings(
166+
serverMessageBindings: { [type: string]: ServerAsyncApiMessageBinding }
167+
): Map<string, MessageBinding> {
168+
const messageBindings = new Map<string, MessageBinding>();
169+
Object.keys(serverMessageBindings).forEach((protocol) => {
170+
messageBindings.set(protocol, this.mapServerAsyncApiMessageBinding(serverMessageBindings[protocol]));
171+
});
172+
return messageBindings;
173+
}
174+
175+
private mapServerAsyncApiMessageBinding(serverMessageBinding: ServerAsyncApiMessageBinding): MessageBinding {
176+
const messageBinding: MessageBinding = {};
177+
178+
Object.keys(serverMessageBinding).forEach((key) => {
179+
const value = serverMessageBinding[key];
180+
if (typeof value === 'object') {
181+
messageBinding[key] = this.mapSchema('MessageBinding', value);
182+
} else {
183+
messageBinding[key] = value;
184+
}
185+
});
186+
187+
return messageBinding;
188+
}
189+
190+
191+
private mapOperation(operationType: OperationType, message: Message, bindings?: any): Operation {
158192
return {
159193
protocol: this.getProtocol(bindings),
160194
operation: operationType,
161-
message: message,
162-
bindings: bindings
163-
}
195+
message,
196+
bindings
197+
};
164198
}
165199

166200
private getProtocol(bindings?: any): string {
@@ -184,12 +218,12 @@ export class AsyncApiMapperService {
184218
anchorIdentifier: '#' + schemaName,
185219
anchorUrl: anchorUrl,
186220
type: schema.type,
187-
items: items,
221+
items,
188222
format: schema.format,
189223
enum: schema.enum,
190-
properties: properties,
224+
properties,
191225
required: schema.required,
192-
example: example,
193-
}
226+
example,
227+
};
194228
}
195229
}

src/app/shared/components/json/json.component.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@ import { Component, OnInit, Input } from '@angular/core';
1212
export class JsonComponent implements OnInit {
1313

1414
@Input() data: any;
15-
json: string;
15+
@Input() json: string;
1616

1717
ngOnInit(): void {
18-
this.json = JSON.stringify(this.data, null, 2);
18+
this.json = this.json === undefined ? JSON.stringify(this.data, null, 2) : this.json;
1919
}
2020

2121
}

0 commit comments

Comments
 (0)