diff --git a/components/Common.js b/components/Common.js
index 77e00d1..7602d52 100644
--- a/components/Common.js
+++ b/components/Common.js
@@ -15,7 +15,7 @@
*/
import { createJavaArgsFromProperties } from '../utils/Types.utils';
-import { collateModelNames } from '../utils/Models.utils';
+import { collateModelNames, getMessagePayload } from '../utils/Models.utils';
import { MQCipherToJava } from './Connection/MQTLS';
export function Class({ childrenContent, name, implementsClass, extendsClass }) {
@@ -180,7 +180,7 @@ import ${params.package}.models.${messageName};`;
/* Used to resolve a channel object to message name */
export function ChannelToMessage(channel, asyncapi) {
const message = channel.messages().all()[0];
- const targetPayloadProperties = message.payload().properties();
+ const targetPayloadProperties = getMessagePayload(message).properties();
const targetMessageName = message.name();
const messageNameTitleCase = targetMessageName.charAt(0).toUpperCase() + targetMessageName.slice(1);
diff --git a/components/Demo/Demo.js b/components/Demo/Demo.js
index 04ef95b..47d88f3 100644
--- a/components/Demo/Demo.js
+++ b/components/Demo/Demo.js
@@ -19,6 +19,7 @@ import { DemoProducer } from './DemoProducer';
import { javaPackageToPath, toJavaClassName } from '../../utils/String.utils';
import { File } from '@asyncapi/generator-react-sdk';
import { createJavaConstructorArgs } from '../../utils/Types.utils';
+import { getMessagePayload } from '../../utils/Models.utils';
import { PackageDeclaration } from '../Common';
export function Demo(asyncapi, params) {
@@ -39,7 +40,7 @@ export function Demo(asyncapi, params) {
// Get payload from either publish or subscribe
const message = channel.messages().all()[0];
const targetMessageName = message.id() || message.name();
- const targetPayloadProperties = message.payload().properties();
+ const targetPayloadProperties = getMessagePayload(message).properties();
const messageNameTitleCase = toJavaClassName(targetMessageName);
diff --git a/components/Files/Models.js b/components/Files/Models.js
index 018882c..8fe7d6f 100644
--- a/components/Files/Models.js
+++ b/components/Files/Models.js
@@ -19,7 +19,7 @@ import { PackageDeclaration, ImportDeclaration, Class, ClassConstructor } from '
import { ModelClassVariables, ModelConstructor } from '../Model';
import { javaPackageToPath } from '../../utils/String.utils';
import { Indent, IndentationTypes } from '@asyncapi/generator-react-sdk';
-import { collateModels } from '../../utils/Models.utils';
+import { collateModels, getMessagePayload } from '../../utils/Models.utils';
export function Models(asyncapi, params) {
const models = collateModels(asyncapi);
@@ -40,7 +40,7 @@ export function Models(asyncapi, params) {
-
+
diff --git a/components/Model.js b/components/Model.js
index 18ed077..0a47f15 100644
--- a/components/Model.js
+++ b/components/Model.js
@@ -15,15 +15,16 @@
*/
import { setLocalVariables, defineVariablesForProperties } from '../utils/Types.utils';
+import { getMessagePayload } from '../utils/Models.utils';
export function ModelConstructor({ message }) {
// TODO: Supoort ofMany messages
- return (setLocalVariables(message.payload().properties()).join(''));
+ return (setLocalVariables(getMessagePayload(message).properties()).join(''));
}
export function ModelClassVariables({ message }) {
// TODO: Supoort ofMany messages
- const argsString = defineVariablesForProperties(message.payload());
+ const argsString = defineVariablesForProperties(getMessagePayload(message));
return argsString.join(`
`);
diff --git a/package-lock.json b/package-lock.json
index 7a42ce6..1bcaaca 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -11,7 +11,8 @@
"dependencies": {
"@asyncapi/generator-filters": "^2.1.0",
"@asyncapi/generator-hooks": "^0.1.0",
- "@asyncapi/generator-react-sdk": "^1.0.11"
+ "@asyncapi/generator-react-sdk": "^1.0.11",
+ "generate-schema": "^2.6.0"
},
"devDependencies": {
"@asyncapi/generator": "^1.17.7",
@@ -7084,6 +7085,23 @@
"node": "^12.13.0 || ^14.15.0 || >=16.0.0"
}
},
+ "node_modules/generate-schema": {
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/generate-schema/-/generate-schema-2.6.0.tgz",
+ "integrity": "sha512-EUBKfJNzT8f91xUk5X5gKtnbdejZeE065UAJ3BCzE8VEbvwKI9Pm5jaWmqVeK1MYc1g5weAVFDTSJzN7ymtTqA==",
+ "dependencies": {
+ "commander": "^2.9.0",
+ "type-of-is": "^3.4.0"
+ },
+ "bin": {
+ "generate-schema": "bin/generate-schema"
+ }
+ },
+ "node_modules/generate-schema/node_modules/commander": {
+ "version": "2.20.3",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
+ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
+ },
"node_modules/gensync": {
"version": "1.0.0-beta.2",
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
@@ -17335,6 +17353,14 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/type-of-is": {
+ "version": "3.5.1",
+ "resolved": "https://registry.npmjs.org/type-of-is/-/type-of-is-3.5.1.tgz",
+ "integrity": "sha512-SOnx8xygcAh8lvDU2exnK2bomASfNjzB3Qz71s2tw9QnX8fkAo7aC+D0H7FV0HjRKj94CKV2Hi71kVkkO6nOxg==",
+ "engines": {
+ "node": ">=0.10.5"
+ }
+ },
"node_modules/typed-array-length": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz",
@@ -23351,6 +23377,22 @@
"wide-align": "^1.1.5"
}
},
+ "generate-schema": {
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/generate-schema/-/generate-schema-2.6.0.tgz",
+ "integrity": "sha512-EUBKfJNzT8f91xUk5X5gKtnbdejZeE065UAJ3BCzE8VEbvwKI9Pm5jaWmqVeK1MYc1g5weAVFDTSJzN7ymtTqA==",
+ "requires": {
+ "commander": "^2.9.0",
+ "type-of-is": "^3.4.0"
+ },
+ "dependencies": {
+ "commander": {
+ "version": "2.20.3",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
+ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
+ }
+ }
+ },
"gensync": {
"version": "1.0.0-beta.2",
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
@@ -31003,6 +31045,11 @@
"integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==",
"dev": true
},
+ "type-of-is": {
+ "version": "3.5.1",
+ "resolved": "https://registry.npmjs.org/type-of-is/-/type-of-is-3.5.1.tgz",
+ "integrity": "sha512-SOnx8xygcAh8lvDU2exnK2bomASfNjzB3Qz71s2tw9QnX8fkAo7aC+D0H7FV0HjRKj94CKV2Hi71kVkkO6nOxg=="
+ },
"typed-array-length": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz",
diff --git a/package.json b/package.json
index bee1d73..b141415 100644
--- a/package.json
+++ b/package.json
@@ -33,7 +33,8 @@
"dependencies": {
"@asyncapi/generator-filters": "^2.1.0",
"@asyncapi/generator-hooks": "^0.1.0",
- "@asyncapi/generator-react-sdk": "^1.0.11"
+ "@asyncapi/generator-react-sdk": "^1.0.11",
+ "generate-schema": "^2.6.0"
},
"release": {
"branches": [
diff --git a/test/Kafka.test.js b/test/Kafka.test.js
index 1dd3b8b..273e138 100644
--- a/test/Kafka.test.js
+++ b/test/Kafka.test.js
@@ -33,6 +33,7 @@ describe('kafka integration tests using the generator', () => {
`${PACKAGE_PATH}/ConnectionHelper.java`,
`${PACKAGE_PATH}/LoggingHelper.java`,
`${PACKAGE_PATH}/PubSubBase.java`,
+ `${PACKAGE_PATH}/models/ModelContract.java`,
];
for (const file of commonFiles) {
expect(existsSync(path.join(OUTPUT_DIR, file))).toBe(true);
@@ -64,7 +65,6 @@ describe('kafka integration tests using the generator', () => {
'DemoSubscriber.java',
'SongReleasedProducer.java',
'SongReleasedSubscriber.java',
- 'models/ModelContract.java',
'models/Song.java',
],
[
@@ -84,7 +84,6 @@ describe('kafka integration tests using the generator', () => {
[
'DemoProducer.java',
'SongReleasedProducer.java',
- 'models/ModelContract.java',
'models/Song.java',
],
[
@@ -105,7 +104,6 @@ describe('kafka integration tests using the generator', () => {
'DemoSubscriber.java',
'SongReleasedProducer.java',
'SongReleasedSubscriber.java',
- 'models/ModelContract.java',
'models/Song.java',
],
[
@@ -124,7 +122,6 @@ describe('kafka integration tests using the generator', () => {
[
'DemoSubscriber.java',
'SongReleasedSubscriber.java',
- 'models/ModelContract.java',
'models/Song.java',
],
[
@@ -149,7 +146,6 @@ describe('kafka integration tests using the generator', () => {
'SmartylightingStreetlights10EventStreetlightIdLightingMeasuredSubscriber.java',
'models/DimLight.java',
'models/LightMeasured.java',
- 'models/ModelContract.java',
'models/TurnOnOff.java',
],
[
@@ -173,7 +169,6 @@ describe('kafka integration tests using the generator', () => {
'LightTurnOnProducer.java',
'models/DimLight.java',
'models/LightMeasured.java',
- 'models/ModelContract.java',
'models/TurnOn.java',
],
[
@@ -182,4 +177,23 @@ describe('kafka integration tests using the generator', () => {
]);
expect(verified).toBe(true);
});
+
+ it('should generate code for an AsyncAPI doc without payload schema', async () => {
+ const verified = await generateJavaProject(
+ 'com.eem',
+ {
+ server: 'gateway-group',
+ },
+ 'mocks/kafka-orders-v3.yml',
+ [
+ 'DemoSubscriber.java',
+ 'ORDERSJSONSubscriber.java',
+ 'models/Message.java',
+ ],
+ [
+ 'props.put("security.protocol", "SASL_SSL")',
+ 'props.put("sasl.mechanism", "PLAIN")',
+ ]);
+ expect(verified).toBe(true);
+ });
});
diff --git a/test/mocks/kafka-orders-v3.yml b/test/mocks/kafka-orders-v3.yml
new file mode 100644
index 0000000..5642cce
--- /dev/null
+++ b/test/mocks/kafka-orders-v3.yml
@@ -0,0 +1,34 @@
+asyncapi: 3.0.0
+info:
+ title: ORDERS.JSON
+ version: 1.0.0
+ contact:
+ email: username@example.com
+channels:
+ ORDERS.JSON:
+ address: ORDERS.JSON
+ bindings:
+ kafka:
+ partitions: 3
+ replicas: 3
+ messages:
+ message:
+ examples:
+ - payload: {"id":"973fb57a-4fcc-42df-8710-440c7c3ec32c","customer":"Dionne Howell","customerid":"26b87be0-2be7-4e2d-b5de-43d83d51ee49","description":"M Acid-washed Capri Jeans","price":47.85,"quantity":7,"region":"EMEA","ordertime":"2024-03-09 15:37:19.769"}
+operations:
+ receiveMessage:
+ action: receive
+ channel:
+ $ref: '#/channels/ORDERS.JSON'
+ messages:
+ - $ref: '#/channels/ORDERS.JSON/messages/message'
+servers:
+ gateway-group:
+ host: my-kafka-hostname:9092
+ protocol: kafka-secure
+ security:
+ - $ref: '#/components/securitySchemes/EGW-SECURITY'
+components:
+ securitySchemes:
+ EGW-SECURITY:
+ type: plain
diff --git a/utils/Models.utils.js b/utils/Models.utils.js
index 989fce9..d2c7f5c 100644
--- a/utils/Models.utils.js
+++ b/utils/Models.utils.js
@@ -1,4 +1,5 @@
import { toJavaClassName } from './String.utils';
+import { json } from 'generate-schema';
export function collateModelNames(asyncapi) {
return Object.keys(collateModels(asyncapi));
@@ -13,4 +14,44 @@ export function collateModels(asyncapi) {
}
return models;
+}
+
+
+// The rest of the generator depends on a message object
+// having a payload with properties. This is needed to
+// be able to generate Java classes with attributes
+// matching the expected properties.
+//
+// Some AsyncAPI documents don't include payload properties
+// but provide a sample message instead. For these
+// documents, we can attempt to derive a schema from
+// the sample, and use that schema to generate a usable
+// set of properties.
+export function getMessagePayload(message) {
+ let payload = message.payload();
+ if (!payload) {
+ payload = {
+ required: () => { return false; }
+ };
+ }
+ if (!payload.properties || !payload.properties()) {
+ const generatedProperties = {};
+
+ const examples = message.examples().all();
+ if (examples && examples.length > 0) {
+ const example = examples[0];
+ const examplePayload = example.payload();
+ const jsonSchema = json('schema', examplePayload).properties;
+ Object.keys(jsonSchema).forEach((propertyName) => {
+ generatedProperties[propertyName] = {
+ type: () => { return jsonSchema[propertyName].type; },
+ format: () => { return; },
+ required: () => { return false; }
+ };
+ });
+ }
+
+ payload.properties = () => { return generatedProperties; };
+ }
+ return payload;
}
\ No newline at end of file