Skip to content

Commit a1c2d61

Browse files
authored
feat!: improve anonym messages (#2367)
1 parent ee1c359 commit a1c2d61

File tree

5 files changed

+118
-22
lines changed

5 files changed

+118
-22
lines changed

docs/migrations/version-5-to-6.md

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,72 @@ const models = await generator.generateCompleteModels(schema, {
5151
```
5252

5353
The generated Python code behavior remains unchanged - all imports will continue to use the explicit style.
54+
55+
## AsyncAPI
56+
57+
### Multiple messages in operations now generate individual models
58+
59+
When processing AsyncAPI v3 documents with multiple messages in a single operation, Modelina now correctly generates individual models for each message payload in addition to the `oneOf` wrapper model.
60+
61+
**Example AsyncAPI Document:**
62+
```yaml
63+
asyncapi: 3.0.0
64+
info:
65+
title: User Service
66+
version: 1.0.0
67+
channels:
68+
userEvents:
69+
address: user/events
70+
messages:
71+
UserCreated:
72+
$ref: '#/components/messages/UserCreated'
73+
UserUpdated:
74+
$ref: '#/components/messages/UserUpdated'
75+
operations:
76+
onUserEvents:
77+
action: receive
78+
channel:
79+
$ref: '#/channels/userEvents'
80+
messages:
81+
- $ref: '#/channels/userEvents/messages/UserCreated'
82+
- $ref: '#/channels/userEvents/messages/UserUpdated'
83+
components:
84+
messages:
85+
UserCreated:
86+
payload:
87+
type: object
88+
properties:
89+
id:
90+
type: string
91+
name:
92+
type: string
93+
UserUpdated:
94+
payload:
95+
type: object
96+
properties:
97+
id:
98+
type: string
99+
name:
100+
type: string
101+
updatedAt:
102+
type: string
103+
```
104+
105+
**Before (v5):**
106+
```typescript
107+
const generator = new JavaGenerator();
108+
const models = await generator.generate(inputModel);
109+
// Problem: No UserCreated or UserUpdated classes were generated
110+
```
111+
112+
**After (v6):**
113+
```typescript
114+
const generator = new JavaGenerator();
115+
const models = await generator.generate(inputModel);
116+
// ✓ Now generates:
117+
// - UserCreatedPayload.java
118+
// - UserUpdatedPayload.java
119+
// - userEvents interface (discriminated union)
120+
```
121+
122+
No code changes are required. This is an enhancement that fixes incomplete model generation. If you have custom post-processing logic that filters generated models, you may need to adjust it to handle the additional models.

examples/file-uri-input/__snapshots__/index.spec.ts.snap

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
exports[`Should be able to render models using file URI as input with file generator and should log expected output to console 1`] = `
44
Array [
5-
"class AnonymousSchema_1 {
5+
"class UserSignedUpPayload {
66
private _displayName?: string;
77
private _email?: string;
88
private _additionalProperties?: Map<string, any>;
@@ -31,7 +31,7 @@ Array [
3131

3232
exports[`Should be able to render models using file URI as input with regular generator and should log expected output to console 1`] = `
3333
Array [
34-
"class AnonymousSchema_1 {
34+
"class UserSignedUpPayload {
3535
private _displayName?: string;
3636
private _email?: string;
3737
private _additionalProperties?: Map<string, any>;

src/processors/AsyncAPIInputProcessor.ts

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,15 @@ export class AsyncAPIInputProcessor extends AbstractInputProcessor {
102102

103103
inputModel.originalInput = doc;
104104

105-
const addToInputModel = (payload: AsyncAPISchemaInterface) => {
106-
const schema = AsyncAPIInputProcessor.convertToInternalSchema(payload);
105+
const addToInputModel = (
106+
payload: AsyncAPISchemaInterface,
107+
inferredName?: string
108+
) => {
109+
const schema = AsyncAPIInputProcessor.convertToInternalSchema(
110+
payload,
111+
new Map(),
112+
inferredName
113+
);
107114
const newMetaModel = JsonSchemaInputProcessor.convertSchemaToMetaModel(
108115
schema,
109116
options
@@ -150,9 +157,15 @@ export class AsyncAPIInputProcessor extends AbstractInputProcessor {
150157

151158
addToInputModel(payload);
152159
} else if (messages.length === 1) {
153-
const payload = messages[0].payload();
160+
const message = messages[0];
161+
const payload = message.payload();
154162
if (payload) {
155-
addToInputModel(payload);
163+
// Use message id with 'Payload' suffix as the inferred name for the payload schema
164+
// This avoids potential collisions with component schemas that might have the same name
165+
const messageName = message.id()
166+
? `${message.id()}Payload`
167+
: undefined;
168+
addToInputModel(payload, messageName);
156169
}
157170
}
158171
};
@@ -175,7 +188,12 @@ export class AsyncAPIInputProcessor extends AbstractInputProcessor {
175188
for (const message of doc.allMessages()) {
176189
const payload = message.payload();
177190
if (payload) {
178-
addToInputModel(payload);
191+
// Use message id with 'Payload' suffix as the inferred name for the payload schema
192+
// This avoids potential collisions with component schemas that might have the same name
193+
const messageName = message.id()
194+
? `${message.id()}Payload`
195+
: undefined;
196+
addToInputModel(payload, messageName);
179197
}
180198
}
181199
}
@@ -190,12 +208,15 @@ export class AsyncAPIInputProcessor extends AbstractInputProcessor {
190208
* This keeps the the id of the model deterministic if used in conjunction with other AsyncAPI tools such as the generator.
191209
*
192210
* @param schema to reflect name for
211+
* @param alreadyIteratedSchemas map of already processed schemas
212+
* @param inferredName optional name to use instead of the schema id (e.g., message name)
193213
*/
194214
/* eslint-disable @typescript-eslint/no-non-null-assertion */
195215
// eslint-disable-next-line sonarjs/cognitive-complexity
196216
static convertToInternalSchema(
197217
schema: AsyncAPISchemaInterface | boolean,
198-
alreadyIteratedSchemas: Map<string, AsyncapiV2Schema> = new Map()
218+
alreadyIteratedSchemas: Map<string, AsyncapiV2Schema> = new Map(),
219+
inferredName?: string
199220
): AsyncapiV2Schema | boolean {
200221
if (typeof schema === 'boolean') {
201222
return schema;
@@ -207,10 +228,16 @@ export class AsyncAPIInputProcessor extends AbstractInputProcessor {
207228
typeof schemaUid !== 'undefined' &&
208229
schemaUid.includes('<anonymous-schema')
209230
) {
210-
schemaUid = schemaUid
211-
.replace('<', '')
212-
.replace(/-/g, '_')
213-
.replace('>', '');
231+
// If an inferred name is provided and this is an anonymous schema, use the inferred name
232+
// Only use inferred name if it's not also an anonymous message
233+
if (inferredName && !inferredName.includes('<anonymous-message')) {
234+
schemaUid = inferredName;
235+
} else {
236+
schemaUid = schemaUid
237+
.replace('<', '')
238+
.replace(/-/g, '_')
239+
.replace('>', '');
240+
}
214241
}
215242

216243
if (alreadyIteratedSchemas.has(schemaUid)) {

test/generators/typescript/__snapshots__/TypeScriptGenerator.spec.ts.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
exports[`TypeScriptGenerator AsyncAPI with polymorphism should render 6 models (1 oneOf, 3 classes and 2 enums) 1`] = `
44
Array [
5-
"type AnonymousSchema_1 = Cat | Dog | StickInsect;",
5+
"type PetMessagePayload = Cat | Dog | StickInsect;",
66
"class Cat {
77
private _petType: PetType.CAT = PetType.CAT;
88
private _reservedName: string;

test/processors/__snapshots__/AsyncAPIInputProcessor.spec.ts.snap

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22

33
exports[`AsyncAPIInputProcessor process() should be able to process YAML file 1`] = `
44
Object {
5-
"anonymous_schema_1": ObjectModel {
6-
"name": "anonymous_schema_1",
5+
"UserSignedUpPayload": ObjectModel {
6+
"name": "UserSignedUpPayload",
77
"options": Object {
88
"isNullable": false,
99
},
@@ -24,7 +24,7 @@ Object {
2424
},
2525
},
2626
"type": "object",
27-
"x-modelgen-inferred-name": "anonymous_schema_1",
27+
"x-modelgen-inferred-name": "UserSignedUpPayload",
2828
"x-parser-schema-id": "<anonymous-schema-1>",
2929
},
3030
"properties": Object {
@@ -101,8 +101,8 @@ Object {
101101

102102
exports[`AsyncAPIInputProcessor process() should be able to process file 1`] = `
103103
Object {
104-
"anonymous_schema_1": ObjectModel {
105-
"name": "anonymous_schema_1",
104+
"UserSignedUpPayload": ObjectModel {
105+
"name": "UserSignedUpPayload",
106106
"options": Object {
107107
"isNullable": false,
108108
},
@@ -123,7 +123,7 @@ Object {
123123
},
124124
},
125125
"type": "object",
126-
"x-modelgen-inferred-name": "anonymous_schema_1",
126+
"x-modelgen-inferred-name": "UserSignedUpPayload",
127127
"x-parser-schema-id": "<anonymous-schema-1>",
128128
},
129129
"properties": Object {
@@ -6616,8 +6616,8 @@ InputMetaModel {
66166616
exports[`AsyncAPIInputProcessor process() should be able to process pure object for v3 1`] = `
66176617
InputMetaModel {
66186618
"models": Object {
6619-
"anonymous_schema_1": ObjectModel {
6620-
"name": "anonymous_schema_1",
6619+
"userSignUpMessagePayload": ObjectModel {
6620+
"name": "userSignUpMessagePayload",
66216621
"options": Object {
66226622
"isNullable": false,
66236623
},
@@ -6631,7 +6631,7 @@ InputMetaModel {
66316631
},
66326632
},
66336633
"type": "object",
6634-
"x-modelgen-inferred-name": "anonymous_schema_1",
6634+
"x-modelgen-inferred-name": "userSignUpMessagePayload",
66356635
"x-parser-schema-id": "<anonymous-schema-1>",
66366636
},
66376637
"properties": Object {

0 commit comments

Comments
 (0)