diff --git a/src/app/channels/channel-main/channel-main.component.css b/src/app/channels/channel-main/channel-main.component.css
index 4d904c5..a0874fe 100644
--- a/src/app/channels/channel-main/channel-main.component.css
+++ b/src/app/channels/channel-main/channel-main.component.css
@@ -33,3 +33,7 @@ button {
padding: 6px;
font-weight: normal;
}
+
+[hidden] {
+ display: none !important;
+}
diff --git a/src/app/channels/channel-main/channel-main.component.html b/src/app/channels/channel-main/channel-main.component.html
index f2a94bd..75c0d41 100644
--- a/src/app/channels/channel-main/channel-main.component.html
+++ b/src/app/channels/channel-main/channel-main.component.html
@@ -7,21 +7,51 @@
{{ operation.message.description }}
-
+
+
Message Binding
+
+
+
1)" fxLayout="column" fxLayoutGap="0px">
+
+
Header
+
+
+
+
+
Message
+
+
-
@@ -43,16 +73,14 @@
-
+
+
+
+
diff --git a/src/app/channels/channel-main/channel-main.component.ts b/src/app/channels/channel-main/channel-main.component.ts
index 02189d1..d8facb5 100644
--- a/src/app/channels/channel-main/channel-main.component.ts
+++ b/src/app/channels/channel-main/channel-main.component.ts
@@ -4,7 +4,7 @@ import { Example } from 'src/app/shared/models/example.model';
import { Schema } from 'src/app/shared/models/schema.model';
import { PublisherService } from 'src/app/shared/publisher.service';
import { MatSnackBar } from '@angular/material/snack-bar';
-import { Operation } from 'src/app/shared/models/channel.model';
+import {MessageBinding, Operation} from 'src/app/shared/models/channel.model';
import { STATUS } from 'angular-in-memory-web-api';
@Component({
@@ -27,6 +27,8 @@ export class ChannelMainComponent implements OnInit {
headersExample: Example;
headersTextAreaLineCount: number;
protocolName: string;
+ messageBindingExample?: Example;
+ messageBindingExampleTextAreaLineCount: number;
constructor(
private asyncApiService: AsyncApiService,
@@ -38,47 +40,83 @@ export class ChannelMainComponent implements OnInit {
ngOnInit(): void {
this.asyncApiService.getAsyncApi().subscribe(
asyncapi => {
- let schemas: Map = asyncapi.components.schemas;
- this.schemaName = this.operation.message.payload.name.slice(this.operation.message.payload.name.lastIndexOf('/') + 1)
+ const schemas: Map = asyncapi.components.schemas;
+ this.schemaName = this.operation.message.payload.name.slice(this.operation.message.payload.name.lastIndexOf('/') + 1);
this.schema = schemas.get(this.schemaName);
this.defaultExample = this.schema.example;
this.exampleTextAreaLineCount = this.defaultExample?.lineCount || 0;
- this.headersSchemaName = this.operation.message.headers.name.slice(this.operation.message.headers.name.lastIndexOf('/') + 1)
+ this.headersSchemaName = this.operation.message.headers.name.slice(this.operation.message.headers.name.lastIndexOf('/') + 1);
this.headers = schemas.get(this.headersSchemaName);
this.headersExample = this.headers.example;
this.headersTextAreaLineCount = this.headersExample?.lineCount || 0;
+ this.messageBindingExampleTextAreaLineCount = this.messageBindingExample?.lineCount || 0;
}
);
this.protocolName = Object.keys(this.operation.bindings)[0];
}
+ isEmptyObject(object?: any): boolean {
+ return (object === undefined || object === null) || Object.keys(object).length === 0;
+ }
+
+ createMessageBindingExample(messageBinding?: MessageBinding): Example | undefined {
+ if (messageBinding === undefined || messageBinding === null) {
+ return undefined;
+ }
+
+ const bindingExampleObject = {};
+ Object.keys(messageBinding).forEach((bindingKey) => {
+ if (bindingKey !== 'bindingVersion') {
+ bindingExampleObject[bindingKey] = this.getExampleValue(messageBinding[bindingKey]);
+ }
+ });
+
+ const bindingExample = new Example(bindingExampleObject);
+
+ this.messageBindingExampleTextAreaLineCount = bindingExample.lineCount;
+
+ return bindingExample;
+ }
+
+ getExampleValue(bindingValue: string | Schema): any {
+ if (typeof bindingValue === 'string') {
+ return bindingValue;
+ } else {
+ return bindingValue.example.value;
+ }
+ }
+
recalculateLineCount(field: string, text: string): void {
switch (field) {
case 'example':
this.exampleTextAreaLineCount = text.split('\n').length;
break;
case 'headers':
- this.headersTextAreaLineCount = text.split('\n').length
+ this.headersTextAreaLineCount = text.split('\n').length;
+ break;
+ case 'massageBindingExample':
+ this.messageBindingExampleTextAreaLineCount = text.split('\n').length;
break;
}
}
- publish(example: string, headers: string): void {
+ publish(example: string, headers?: string, bindings?: string): void {
try {
const payloadJson = JSON.parse(example);
- const headersJson = JSON.parse(headers)
+ const headersJson = JSON.parse(headers);
+ const bindingsJson = JSON.parse(bindings);
- this.publisherService.publish(this.protocolName, this.channelName, payloadJson, headersJson).subscribe(
+ this.publisherService.publish(this.protocolName, this.channelName, payloadJson, headersJson, bindingsJson).subscribe(
_ => this.handlePublishSuccess(),
err => this.handlePublishError(err)
);
- } catch(error) {
+ } catch (error) {
this.snackBar.open('Example payload is not valid', 'ERROR', {
duration: 3000
- })
+ });
}
}
diff --git a/src/app/shared/asyncapi-mapper.service.ts b/src/app/shared/asyncapi-mapper.service.ts
index 6a6aa8c..0b06251 100644
--- a/src/app/shared/asyncapi-mapper.service.ts
+++ b/src/app/shared/asyncapi-mapper.service.ts
@@ -1,10 +1,10 @@
-import { AsyncApi } from './models/asyncapi.model';
-import { Server } from './models/server.model';
-import {Channel, CHANNEL_ANCHOR_PREFIX, Message, Operation, OperationType} from './models/channel.model';
-import { Schema } from './models/schema.model';
-import { Injectable } from '@angular/core';
-import {Example} from "./models/example.model";
-import {Info} from "./models/info.model";
+import {AsyncApi} from './models/asyncapi.model';
+import {Server} from './models/server.model';
+import {Channel, CHANNEL_ANCHOR_PREFIX, Message, MessageBinding, Operation, OperationType} from './models/channel.model';
+import {Schema} from './models/schema.model';
+import {Injectable} from '@angular/core';
+import {Example} from './models/example.model';
+import {Info} from './models/info.model';
interface ServerAsyncApiSchema {
description?: string;
@@ -26,6 +26,10 @@ interface ServerAsyncApiMessage {
description?: string;
payload: { $ref: string };
headers: { $ref: string };
+ bindings: {[protocol: string]: ServerAsyncApiMessageBinding};
+}
+interface ServerAsyncApiMessageBinding {
+ [protocol: string]: ServerAsyncApiSchema | string;
}
interface ServerAsyncApiInfo {
@@ -39,7 +43,7 @@ export interface ServerAsyncApi {
asyncapi: string;
info: ServerAsyncApiInfo;
servers: {
- [key: string]: {
+ [server: string]: {
url: string;
protocol: string;
};
@@ -49,11 +53,11 @@ export interface ServerAsyncApi {
description?: string;
subscribe?: {
message: ServerAsyncApiChannelMessage;
- bindings?: any;
+ bindings?: {[protocol: string]: object};
};
publish?: {
message: ServerAsyncApiChannelMessage;
- bindings?: any;
+ bindings?: {[protocol: string]: object};
};
};
};
@@ -64,7 +68,7 @@ export interface ServerAsyncApi {
@Injectable()
export class AsyncApiMapperService {
- static BASE_URL = window.location.pathname + window.location.search + "#";
+ static BASE_URL = window.location.pathname + window.location.search + '#';
constructor() {
}
@@ -89,49 +93,52 @@ export class AsyncApiMapperService {
};
}
- private mapServers(servers: ServerAsyncApi["servers"]): Map {
+ private mapServers(servers: ServerAsyncApi['servers']): Map {
const s = new Map();
Object.entries(servers).forEach(([k, v]) => s.set(k, v));
return s;
}
- private mapChannels(channels: ServerAsyncApi["channels"]): Channel[] {
+ private mapChannels(channels: ServerAsyncApi['channels']): Channel[] {
const s = new Array();
Object.entries(channels).forEach(([k, v]) => {
- const subscriberChannels = this.mapChannel(k, v.description, v.subscribe, "subscribe")
- subscriberChannels.forEach(channel => s.push(channel))
+ const subscriberChannels = this.mapChannel(k, v.description, v.subscribe, 'subscribe');
+ subscriberChannels.forEach(channel => s.push(channel));
- const publisherChannels = this.mapChannel(k, v.description, v.publish, "publish")
- publisherChannels.forEach(channel => s.push(channel))
+ const publisherChannels = this.mapChannel(k, v.description, v.publish, 'publish');
+ publisherChannels.forEach(channel => s.push(channel));
});
return s;
}
private mapChannel(
topicName: string,
- description: ServerAsyncApi["channels"][""]["description"],
- serverOperation: ServerAsyncApi["channels"][""]["subscribe"] | ServerAsyncApi["channels"][""]["publish"],
- operationType: OperationType): Channel[]
+ description: ServerAsyncApi['channels']['']['description'],
+ serverOperation: ServerAsyncApi['channels']['']['subscribe'] | ServerAsyncApi['channels']['']['publish'],
+ operationType: OperationType
+ ): Channel[]
{
- if(serverOperation !== undefined) {
- let messages: Message[] = this.mapMessages(serverOperation.message)
+ if (serverOperation !== undefined) {
+ const messages: Message[] = this.mapMessages(serverOperation.message);
return messages.map(message => {
- const operation = this.mapOperation(operationType, message, serverOperation.bindings)
+ const operation = this.mapOperation(operationType, message, serverOperation.bindings);
return {
name: topicName,
- anchorIdentifier: CHANNEL_ANCHOR_PREFIX + [operation.protocol, topicName, operation.operation, operation.message.title].join( "-"),
- description: description,
- operation: operation,
- }
- })
+ anchorIdentifier: CHANNEL_ANCHOR_PREFIX + [
+ operation.protocol, topicName, operation.operation, operation.message.title
+ ].join( '-'),
+ description,
+ operation,
+ };
+ });
}
return [];
}
private mapMessages(message: ServerAsyncApiChannelMessage): Message[] {
- if('oneOf' in message) {
- return this.mapServerAsyncApiMessages(message.oneOf)
+ if ('oneOf' in message) {
+ return this.mapServerAsyncApiMessages(message.oneOf);
}
return this.mapServerAsyncApiMessages([message]);
}
@@ -144,26 +151,56 @@ export class AsyncApiMapperService {
description: v.description,
payload: {
name: v.payload.$ref,
- anchorUrl: AsyncApiMapperService.BASE_URL +v.payload.$ref?.split('/')?.pop()
+ anchorUrl: AsyncApiMapperService.BASE_URL + v.payload.$ref?.split('/')?.pop()
},
headers: {
name: v.headers.$ref,
anchorUrl: AsyncApiMapperService.BASE_URL + v.headers.$ref?.split('/')?.pop()
- }
- }
- })
+ },
+ bindings: this.mapServerAsyncApiMessageBindings(v.bindings),
+ rawBindings: v.bindings,
+ };
+ });
}
- private mapOperation(operationType: OperationType, message: Message, bindings?: any): Operation {
+ private mapServerAsyncApiMessageBindings(
+ serverMessageBindings?: { [protocol: string]: ServerAsyncApiMessageBinding }
+ ): Map {
+ const messageBindings = new Map();
+ if (serverMessageBindings !== undefined) {
+ Object.keys(serverMessageBindings).forEach((protocol) => {
+ messageBindings.set(protocol, this.mapServerAsyncApiMessageBinding(serverMessageBindings[protocol]));
+ });
+ }
+ return messageBindings;
+ }
+
+ private mapServerAsyncApiMessageBinding(serverMessageBinding: ServerAsyncApiMessageBinding): MessageBinding {
+ const messageBinding: MessageBinding = {};
+
+ Object.keys(serverMessageBinding).forEach((key) => {
+ const value = serverMessageBinding[key];
+ if (typeof value === 'object') {
+ messageBinding[key] = this.mapSchema('MessageBinding', value);
+ } else {
+ messageBinding[key] = value;
+ }
+ });
+
+ return messageBinding;
+ }
+
+
+ private mapOperation(operationType: OperationType, message: Message, bindings?: {[protocol: string]: object}): Operation {
return {
protocol: this.getProtocol(bindings),
operation: operationType,
- message: message,
- bindings: bindings
- }
+ message,
+ bindings
+ };
}
- private getProtocol(bindings?: any): string {
+ private getProtocol(bindings?: {[protocol: string]: object}): string {
return Object.keys(bindings)[0];
}
@@ -184,12 +221,12 @@ export class AsyncApiMapperService {
anchorIdentifier: '#' + schemaName,
anchorUrl: anchorUrl,
type: schema.type,
- items: items,
+ items,
format: schema.format,
enum: schema.enum,
- properties: properties,
+ properties,
required: schema.required,
- example: example,
- }
+ example,
+ };
}
}
diff --git a/src/app/shared/components/json/json.component.ts b/src/app/shared/components/json/json.component.ts
index e587d00..3fd595d 100644
--- a/src/app/shared/components/json/json.component.ts
+++ b/src/app/shared/components/json/json.component.ts
@@ -12,10 +12,10 @@ import { Component, OnInit, Input } from '@angular/core';
export class JsonComponent implements OnInit {
@Input() data: any;
- json: string;
+ @Input() json: string;
ngOnInit(): void {
- this.json = JSON.stringify(this.data, null, 2);
+ this.json = this.json === undefined ? JSON.stringify(this.data, null, 2) : this.json;
}
}
diff --git a/src/app/shared/mock/mock.springwolf-kafka-example.json b/src/app/shared/mock/mock.springwolf-kafka-example.json
index 24df8d7..03d6347 100644
--- a/src/app/shared/mock/mock.springwolf-kafka-example.json
+++ b/src/app/shared/mock/mock.springwolf-kafka-example.json
@@ -40,6 +40,9 @@
},
"headers" : {
"$ref" : "#/components/schemas/HeadersNotDocumented"
+ },
+ "bindings" : {
+ "kafka" : { }
}
}
},
@@ -63,6 +66,9 @@
},
"headers" : {
"$ref" : "#/components/schemas/HeadersNotDocumented"
+ },
+ "bindings" : {
+ "kafka" : { }
}
}
},
@@ -87,6 +93,18 @@
},
"headers" : {
"$ref" : "#/components/schemas/CloudEventHeadersForAnotherPayloadDtoEndpoint"
+ },
+ "bindings" : {
+ "kafka" : {
+ "key" : {
+ "type" : "string",
+ "description" : "Kafka Producer Message Key",
+ "example" : "example-key",
+ "exampleSetFlag" : true,
+ "types" : [ "string" ]
+ },
+ "bindingVersion" : "1"
+ }
}
}, {
"name" : "io.github.stavshamir.springwolf.example.dtos.ExamplePayloadDto",
@@ -96,6 +114,18 @@
},
"headers" : {
"$ref" : "#/components/schemas/SpringDefaultHeaders"
+ },
+ "bindings" : {
+ "kafka" : {
+ "key" : {
+ "type" : "string",
+ "description" : "Kafka Producer Message Key",
+ "example" : "example-key",
+ "exampleSetFlag" : true,
+ "types" : [ "string" ]
+ },
+ "bindingVersion" : "1"
+ }
}
} ]
}
@@ -119,6 +149,9 @@
},
"headers" : {
"$ref" : "#/components/schemas/HeadersNotDocumented"
+ },
+ "bindings" : {
+ "kafka" : { }
}
}
},
@@ -142,6 +175,9 @@
},
"headers" : {
"$ref" : "#/components/schemas/SpringDefaultHeaders-AnotherPayloadDto"
+ },
+ "bindings" : {
+ "kafka" : { }
}
}, {
"name" : "io.github.stavshamir.springwolf.example.dtos.ExamplePayloadDto",
@@ -151,6 +187,9 @@
},
"headers" : {
"$ref" : "#/components/schemas/SpringDefaultHeaders-ExamplePayloadDto"
+ },
+ "bindings" : {
+ "kafka" : { }
}
}, {
"name" : "javax.money.MonetaryAmount",
@@ -160,6 +199,9 @@
},
"headers" : {
"$ref" : "#/components/schemas/SpringDefaultHeaders-MonetaryAmount"
+ },
+ "bindings" : {
+ "kafka" : { }
}
} ]
}
diff --git a/src/app/shared/models/channel.model.ts b/src/app/shared/models/channel.model.ts
index f4d6aea..b88ad03 100644
--- a/src/app/shared/models/channel.model.ts
+++ b/src/app/shared/models/channel.model.ts
@@ -1,4 +1,6 @@
-export const CHANNEL_ANCHOR_PREFIX = "#channel-"
+import {Schema} from './schema.model';
+
+export const CHANNEL_ANCHOR_PREFIX = '#channel-';
export interface Channel {
name: string;
anchorIdentifier: string;
@@ -6,10 +8,10 @@ export interface Channel {
operation: Operation;
}
-export type OperationType = "publish" | "subscribe";
+export type OperationType = 'publish' | 'subscribe';
export interface Operation {
message: Message;
- bindings?: { [type: string]: any };
+ bindings?: { [protocol: string]: any };
protocol: string;
operation: OperationType;
}
@@ -26,4 +28,10 @@ export interface Message {
name: string
anchorUrl: string;
};
+ bindings?: Map;
+ rawBindings?: {[protocol: string]: object};
+}
+
+export interface MessageBinding {
+ [protocol: string]: string | Schema;
}
diff --git a/src/app/shared/models/example.model.ts b/src/app/shared/models/example.model.ts
index f60b3e1..76c2f3f 100644
--- a/src/app/shared/models/example.model.ts
+++ b/src/app/shared/models/example.model.ts
@@ -3,9 +3,14 @@ export class Example {
public value: string;
public lineCount: number;
- constructor(exampleObject: object) {
- this.value = JSON.stringify(exampleObject, null, 2);
+ constructor(exampleObject: object | string) {
+ if (typeof exampleObject === 'string') {
+ this.value = exampleObject;
+ } else {
+ this.value = JSON.stringify(exampleObject, null, 2);
+ }
+
this.lineCount = this.value.split('\n').length;
}
-}
\ No newline at end of file
+}
diff --git a/src/app/shared/publisher.service.ts b/src/app/shared/publisher.service.ts
index 436dea9..8c82462 100644
--- a/src/app/shared/publisher.service.ts
+++ b/src/app/shared/publisher.service.ts
@@ -1,18 +1,18 @@
-import { Injectable } from '@angular/core';
+import {Injectable} from '@angular/core';
import {HttpClient, HttpParams} from '@angular/common/http';
-import { Observable } from 'rxjs';
-import { Endpoints } from './endpoints';
+import {Observable} from 'rxjs';
+import {Endpoints} from './endpoints';
@Injectable()
export class PublisherService {
constructor(private http: HttpClient) { }
- publish(protocol: string, topic: string, payload: object, headers: object): Observable {
+ publish(protocol: string, topic: string, payload: object, headers: object, bindings: object): Observable {
const url = Endpoints.getPublishEndpoint(protocol);
const params = new HttpParams().set('topic', topic);
- const body = {"payload" : payload, "headers" : headers }
- console.log(`Publishing to ${url}`);
+ const body = {payload, headers, bindings};
+ console.log(`Publishing to ${url} with messageBinding ${bindings} and headers ${headers}: ${body}`);
return this.http.post(url, body, { params });
}