@ioka-technologies/asyncapi-rust-client-template 0.0.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +262 -0
- package/package.json +52 -0
- package/template/Cargo.toml.js +100 -0
- package/template/helpers/index.js +501 -0
- package/template/index.js +244 -0
- package/template/package.json +48 -0
- package/template/src/auth.rs.js +221 -0
- package/template/src/client.rs.js +681 -0
- package/template/src/envelope.rs.js +199 -0
- package/template/src/errors.rs.js +49 -0
- package/template/src/lib.rs.js +112 -0
- package/template/src/models.rs.js +625 -0
|
@@ -0,0 +1,625 @@
|
|
|
1
|
+
/* eslint-disable no-unused-vars */
|
|
2
|
+
import { File } from '@asyncapi/generator-react-sdk';
|
|
3
|
+
import {
|
|
4
|
+
toRustIdentifier,
|
|
5
|
+
toRustTypeName,
|
|
6
|
+
toRustFieldName,
|
|
7
|
+
toRustEnumVariant,
|
|
8
|
+
toRustEnumVariantWithSerde
|
|
9
|
+
} from '../helpers/index.js';
|
|
10
|
+
|
|
11
|
+
export default function ModelsRs({ asyncapi }) {
|
|
12
|
+
|
|
13
|
+
// Extract message schemas and build channel mapping
|
|
14
|
+
const components = asyncapi.components();
|
|
15
|
+
const messageSchemas = [];
|
|
16
|
+
const componentSchemas = [];
|
|
17
|
+
const messageToChannels = new Map();
|
|
18
|
+
const generatedTypes = new Set();
|
|
19
|
+
const nestedSchemas = new Map();
|
|
20
|
+
const schemaRegistry = new Map();
|
|
21
|
+
|
|
22
|
+
// First, build channel to message mapping and extract inline message schemas
|
|
23
|
+
if (asyncapi.channels) {
|
|
24
|
+
const channels = asyncapi.channels();
|
|
25
|
+
if (channels) {
|
|
26
|
+
// Use proper iteration for AsyncAPI collection
|
|
27
|
+
for (const channel of channels) {
|
|
28
|
+
try {
|
|
29
|
+
const channelName = channel.id();
|
|
30
|
+
|
|
31
|
+
// Handle AsyncAPI 3.x format - extract inline messages from channels
|
|
32
|
+
if (channel.messages) {
|
|
33
|
+
const messages = channel.messages();
|
|
34
|
+
if (messages) {
|
|
35
|
+
// Check if messages is an object with message names as keys
|
|
36
|
+
if (typeof messages === 'object' && !Array.isArray(messages)) {
|
|
37
|
+
// Iterate through message entries (messageName -> messageObject)
|
|
38
|
+
Object.entries(messages).forEach(([messageName, message]) => {
|
|
39
|
+
if (message && messageName) {
|
|
40
|
+
let payload = null;
|
|
41
|
+
let description = null;
|
|
42
|
+
|
|
43
|
+
// Get payload schema
|
|
44
|
+
if (message.payload && typeof message.payload === 'function') {
|
|
45
|
+
payload = message.payload();
|
|
46
|
+
if (payload && payload.json && typeof payload.json === 'function') {
|
|
47
|
+
payload = payload.json();
|
|
48
|
+
}
|
|
49
|
+
} else if (message.payload) {
|
|
50
|
+
payload = message.payload;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Get description
|
|
54
|
+
if (message.description && typeof message.description === 'function') {
|
|
55
|
+
description = message.description();
|
|
56
|
+
} else if (message.description) {
|
|
57
|
+
description = message.description;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Add to channel mapping
|
|
61
|
+
if (!messageToChannels.has(messageName)) {
|
|
62
|
+
messageToChannels.set(messageName, []);
|
|
63
|
+
}
|
|
64
|
+
messageToChannels.get(messageName).push(channelName);
|
|
65
|
+
|
|
66
|
+
// Add to message schemas for inline messages
|
|
67
|
+
messageSchemas.push({
|
|
68
|
+
name: messageName,
|
|
69
|
+
rustName: toRustTypeName(messageName),
|
|
70
|
+
payload,
|
|
71
|
+
rawPayload: payload,
|
|
72
|
+
description,
|
|
73
|
+
channels: [channelName]
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
} else {
|
|
78
|
+
// Try iterating as a collection
|
|
79
|
+
for (const message of messages) {
|
|
80
|
+
if (message) {
|
|
81
|
+
let messageName = null;
|
|
82
|
+
let payload = null;
|
|
83
|
+
let description = null;
|
|
84
|
+
|
|
85
|
+
// Get message name - try multiple approaches
|
|
86
|
+
if (message._meta && message._meta.id) {
|
|
87
|
+
messageName = message._meta.id;
|
|
88
|
+
} else if (message._json && message._json['x-parser-message-name']) {
|
|
89
|
+
messageName = message._json['x-parser-message-name'];
|
|
90
|
+
} else if (message._json && message._json['x-parser-unique-object-id']) {
|
|
91
|
+
messageName = message._json['x-parser-unique-object-id'];
|
|
92
|
+
} else if (message.name && typeof message.name === 'function') {
|
|
93
|
+
messageName = message.name();
|
|
94
|
+
} else if (message.name) {
|
|
95
|
+
messageName = message.name;
|
|
96
|
+
} else if (message.$ref) {
|
|
97
|
+
messageName = message.$ref.split('/').pop();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Get payload schema
|
|
101
|
+
if (message.payload && typeof message.payload === 'function') {
|
|
102
|
+
payload = message.payload();
|
|
103
|
+
if (payload && payload.json && typeof payload.json === 'function') {
|
|
104
|
+
payload = payload.json();
|
|
105
|
+
}
|
|
106
|
+
} else if (message.payload) {
|
|
107
|
+
payload = message.payload;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Get description
|
|
111
|
+
if (message.description && typeof message.description === 'function') {
|
|
112
|
+
description = message.description();
|
|
113
|
+
} else if (message.description) {
|
|
114
|
+
description = message.description;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (messageName) {
|
|
118
|
+
// Add to channel mapping
|
|
119
|
+
if (!messageToChannels.has(messageName)) {
|
|
120
|
+
messageToChannels.set(messageName, []);
|
|
121
|
+
}
|
|
122
|
+
messageToChannels.get(messageName).push(channelName);
|
|
123
|
+
|
|
124
|
+
// Add to message schemas for inline messages
|
|
125
|
+
messageSchemas.push({
|
|
126
|
+
name: messageName,
|
|
127
|
+
rustName: toRustTypeName(messageName),
|
|
128
|
+
payload,
|
|
129
|
+
rawPayload: payload,
|
|
130
|
+
description,
|
|
131
|
+
channels: [channelName]
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
} catch (e) {
|
|
140
|
+
// Ignore channel processing errors
|
|
141
|
+
console.warn(`Error processing channel: ${e.message}`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Build schema registry from components.schemas
|
|
148
|
+
// Try to access the raw AsyncAPI document
|
|
149
|
+
let rawDoc = null;
|
|
150
|
+
try {
|
|
151
|
+
if (asyncapi.json && typeof asyncapi.json === 'function') {
|
|
152
|
+
rawDoc = asyncapi.json();
|
|
153
|
+
} else if (asyncapi._json) {
|
|
154
|
+
rawDoc = asyncapi._json;
|
|
155
|
+
}
|
|
156
|
+
} catch (e) {
|
|
157
|
+
// Ignore
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Extract schemas from raw document if available
|
|
161
|
+
if (rawDoc && rawDoc.components && rawDoc.components.schemas) {
|
|
162
|
+
Object.entries(rawDoc.components.schemas).forEach(([name, schema]) => {
|
|
163
|
+
if (name && typeof name === 'string' && schema && typeof schema === 'object') {
|
|
164
|
+
schemaRegistry.set(name, schema);
|
|
165
|
+
componentSchemas.push({
|
|
166
|
+
name,
|
|
167
|
+
rustName: toRustTypeName(name),
|
|
168
|
+
schema: schema,
|
|
169
|
+
description: schema.description
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Fallback: try the components.schemas() method
|
|
176
|
+
if (componentSchemas.length === 0 && components && components.schemas) {
|
|
177
|
+
try {
|
|
178
|
+
const schemas = components.schemas();
|
|
179
|
+
if (schemas) {
|
|
180
|
+
// Try different ways to access schemas
|
|
181
|
+
let schemaEntries = [];
|
|
182
|
+
|
|
183
|
+
if (schemas instanceof Map) {
|
|
184
|
+
schemaEntries = Array.from(schemas.entries());
|
|
185
|
+
} else if (typeof schemas === 'object') {
|
|
186
|
+
schemaEntries = Object.entries(schemas);
|
|
187
|
+
} else if (schemas.all && typeof schemas.all === 'function') {
|
|
188
|
+
// AsyncAPI parser might have an all() method
|
|
189
|
+
const allSchemas = schemas.all();
|
|
190
|
+
if (Array.isArray(allSchemas)) {
|
|
191
|
+
schemaEntries = allSchemas.map(schema => {
|
|
192
|
+
const name = schema.uid ? schema.uid() : (schema.id ? schema.id() : null);
|
|
193
|
+
return [name, schema];
|
|
194
|
+
}).filter(([name]) => name);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
schemaEntries.forEach(([name, schema]) => {
|
|
199
|
+
// Skip internal AsyncAPI parser objects and numeric keys
|
|
200
|
+
if (!name || name === 'collections' || name === '_meta' || name.startsWith('_') || /^\d+$/.test(name)) {
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
let schemaData = null;
|
|
205
|
+
let description = null;
|
|
206
|
+
|
|
207
|
+
try {
|
|
208
|
+
// Handle different schema object types
|
|
209
|
+
if (schema && typeof schema.json === 'function') {
|
|
210
|
+
schemaData = schema.json();
|
|
211
|
+
} else if (schema && typeof schema === 'object') {
|
|
212
|
+
schemaData = schema;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (schema && typeof schema.description === 'function') {
|
|
216
|
+
description = schema.description();
|
|
217
|
+
} else if (schema && schema.description) {
|
|
218
|
+
description = schema.description;
|
|
219
|
+
}
|
|
220
|
+
} catch (e) {
|
|
221
|
+
// Ignore schema extraction errors
|
|
222
|
+
console.warn(`Failed to extract schema for ${name}:`, e.message);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (schemaData && typeof name === 'string' && name.length > 0) {
|
|
226
|
+
schemaRegistry.set(name, schemaData);
|
|
227
|
+
componentSchemas.push({
|
|
228
|
+
name,
|
|
229
|
+
rustName: toRustTypeName(name),
|
|
230
|
+
schema: schemaData,
|
|
231
|
+
description
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
} catch (e) {
|
|
237
|
+
console.warn('Failed to extract component schemas:', e.message);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Extract messages from components
|
|
242
|
+
if (components && components.messages) {
|
|
243
|
+
const messages = components.messages();
|
|
244
|
+
if (messages) {
|
|
245
|
+
Object.entries(messages).forEach(([name, message]) => {
|
|
246
|
+
let payload = null;
|
|
247
|
+
let description = null;
|
|
248
|
+
let title = null;
|
|
249
|
+
let messageName = name;
|
|
250
|
+
|
|
251
|
+
try {
|
|
252
|
+
let rawPayload = null;
|
|
253
|
+
if (message.payload && typeof message.payload === 'function') {
|
|
254
|
+
const payloadSchema = message.payload();
|
|
255
|
+
payload = payloadSchema && payloadSchema.json ? payloadSchema.json() : payloadSchema;
|
|
256
|
+
// Try to get the raw payload reference from the message
|
|
257
|
+
if (message._json && message._json.payload) {
|
|
258
|
+
rawPayload = message._json.payload;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
description = message.description && typeof message.description === 'function' ? message.description() : null;
|
|
262
|
+
title = message.title && typeof message.title === 'function' ? message.title() : null;
|
|
263
|
+
|
|
264
|
+
// Try to get the actual message name
|
|
265
|
+
if (message.name && typeof message.name === 'function') {
|
|
266
|
+
messageName = message.name();
|
|
267
|
+
} else if (message.name) {
|
|
268
|
+
messageName = message.name;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// If we have raw document access, try to get the payload reference from there
|
|
272
|
+
if (!rawPayload && rawDoc && rawDoc.components && rawDoc.components.messages && rawDoc.components.messages[name]) {
|
|
273
|
+
rawPayload = rawDoc.components.messages[name].payload;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const channels = messageToChannels.get(messageName) || messageToChannels.get(name) || [];
|
|
277
|
+
messageSchemas.push({
|
|
278
|
+
name: messageName,
|
|
279
|
+
rustName: toRustTypeName(messageName),
|
|
280
|
+
payload,
|
|
281
|
+
rawPayload,
|
|
282
|
+
description: description || title,
|
|
283
|
+
channels
|
|
284
|
+
});
|
|
285
|
+
} catch (e) {
|
|
286
|
+
// Ignore payload extraction errors
|
|
287
|
+
const channels = messageToChannels.get(messageName) || messageToChannels.get(name) || [];
|
|
288
|
+
messageSchemas.push({
|
|
289
|
+
name: messageName,
|
|
290
|
+
rustName: toRustTypeName(messageName),
|
|
291
|
+
payload,
|
|
292
|
+
rawPayload: null,
|
|
293
|
+
description: description || title,
|
|
294
|
+
channels
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Helper function to convert JSON schema to Rust type
|
|
302
|
+
function jsonSchemaToRustType(schema, typeName = null) {
|
|
303
|
+
if (!schema) return 'serde_json::Value';
|
|
304
|
+
|
|
305
|
+
// Handle $ref
|
|
306
|
+
if (schema.$ref) {
|
|
307
|
+
const refName = schema.$ref.split('/').pop();
|
|
308
|
+
const rustTypeName = toRustTypeName(refName);
|
|
309
|
+
|
|
310
|
+
// Generate the referenced schema if we have access to components
|
|
311
|
+
if (components && components.schemas) {
|
|
312
|
+
const schemas = components.schemas();
|
|
313
|
+
if (schemas && schemas[refName] && !generatedTypes.has(rustTypeName)) {
|
|
314
|
+
generatedTypes.add(rustTypeName);
|
|
315
|
+
const referencedSchema = schemas[refName];
|
|
316
|
+
const schemaJson = referencedSchema.json ? referencedSchema.json() : referencedSchema;
|
|
317
|
+
nestedSchemas.set(rustTypeName, {
|
|
318
|
+
type: 'struct',
|
|
319
|
+
schema: schemaJson,
|
|
320
|
+
description: referencedSchema.description && typeof referencedSchema.description === 'function' ? referencedSchema.description() : null
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
return rustTypeName;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Handle resolved $ref - check for x-parser-schema-id which indicates original schema name
|
|
329
|
+
if (schema['x-parser-schema-id'] && typeof schema['x-parser-schema-id'] === 'string') {
|
|
330
|
+
const schemaId = schema['x-parser-schema-id'];
|
|
331
|
+
// Check if this matches a known component schema
|
|
332
|
+
if (schemaRegistry.has(schemaId)) {
|
|
333
|
+
const rustTypeName = toRustTypeName(schemaId);
|
|
334
|
+
return rustTypeName;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
if (!schema.type) {
|
|
339
|
+
// If no type specified, check for properties (object) or items (array)
|
|
340
|
+
if (schema.properties) {
|
|
341
|
+
schema.type = 'object';
|
|
342
|
+
} else if (schema.items) {
|
|
343
|
+
schema.type = 'array';
|
|
344
|
+
} else {
|
|
345
|
+
return 'serde_json::Value';
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
switch (schema.type) {
|
|
350
|
+
case 'string':
|
|
351
|
+
if (schema.enum && schema.enum.length > 0) {
|
|
352
|
+
// Generate enum type
|
|
353
|
+
if (typeName) {
|
|
354
|
+
const enumName = `${typeName}Enum`;
|
|
355
|
+
if (!generatedTypes.has(enumName)) {
|
|
356
|
+
generatedTypes.add(enumName);
|
|
357
|
+
nestedSchemas.set(enumName, {
|
|
358
|
+
type: 'enum',
|
|
359
|
+
variants: schema.enum,
|
|
360
|
+
description: schema.description
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
return enumName;
|
|
364
|
+
}
|
|
365
|
+
return 'String'; // Fallback if no type name provided
|
|
366
|
+
}
|
|
367
|
+
if (schema.format === 'date-time') return 'chrono::DateTime<chrono::Utc>';
|
|
368
|
+
if (schema.format === 'uuid') return 'uuid::Uuid';
|
|
369
|
+
if (schema.format === 'email') return 'String';
|
|
370
|
+
if (schema.format === 'uri') return 'String';
|
|
371
|
+
return 'String';
|
|
372
|
+
case 'integer':
|
|
373
|
+
switch (schema.format) {
|
|
374
|
+
case 'int32':
|
|
375
|
+
return 'i32';
|
|
376
|
+
case 'int64':
|
|
377
|
+
return 'i64';
|
|
378
|
+
case 'uint32':
|
|
379
|
+
return 'u32';
|
|
380
|
+
case 'uint64':
|
|
381
|
+
return 'u64';
|
|
382
|
+
default:
|
|
383
|
+
// Default to i32 for unspecified format (maintains backward compatibility)
|
|
384
|
+
return 'i32';
|
|
385
|
+
}
|
|
386
|
+
case 'number':
|
|
387
|
+
return 'f64';
|
|
388
|
+
case 'boolean':
|
|
389
|
+
return 'bool';
|
|
390
|
+
case 'array': {
|
|
391
|
+
const itemType = jsonSchemaToRustType(schema.items);
|
|
392
|
+
return `Vec<${itemType}>`;
|
|
393
|
+
}
|
|
394
|
+
case 'object':
|
|
395
|
+
if (schema.properties && Object.keys(schema.properties).length > 0) {
|
|
396
|
+
// Generate nested struct
|
|
397
|
+
if (typeName) {
|
|
398
|
+
const structName = toRustTypeName(typeName);
|
|
399
|
+
if (!generatedTypes.has(structName)) {
|
|
400
|
+
generatedTypes.add(structName);
|
|
401
|
+
nestedSchemas.set(structName, {
|
|
402
|
+
type: 'struct',
|
|
403
|
+
schema: schema,
|
|
404
|
+
description: schema.description
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
return structName;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
return 'serde_json::Value';
|
|
411
|
+
default:
|
|
412
|
+
return 'serde_json::Value';
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// Generate message structs
|
|
417
|
+
function generateMessageStruct(schema, messageName) {
|
|
418
|
+
if (!schema || !schema.properties) {
|
|
419
|
+
return ' pub data: serde_json::Value,';
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
const fields = Object.entries(schema.properties).map(([fieldName, fieldSchema]) => {
|
|
423
|
+
const rustFieldName = toRustFieldName(fieldName);
|
|
424
|
+
const fieldTypeName = `${messageName}${toRustTypeName(fieldName)}`;
|
|
425
|
+
const rustType = jsonSchemaToRustType(fieldSchema, fieldTypeName);
|
|
426
|
+
const requiredFields = schema.required;
|
|
427
|
+
const optional = !requiredFields || !Array.isArray(requiredFields) || requiredFields.indexOf(fieldName) === -1;
|
|
428
|
+
const finalType = optional ? `Option<${rustType}>` : rustType;
|
|
429
|
+
|
|
430
|
+
let fieldDoc = '';
|
|
431
|
+
if (fieldSchema.description) {
|
|
432
|
+
fieldDoc = ` /// ${fieldSchema.description}\n`;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
let serdeRename = '';
|
|
436
|
+
if (rustFieldName !== fieldName) {
|
|
437
|
+
serdeRename = ` #[serde(rename = "${fieldName}")]\n`;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
let skipSerializing = '';
|
|
441
|
+
if (optional) {
|
|
442
|
+
skipSerializing = ' #[serde(skip_serializing_if = "Option::is_none")]\n';
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
return `${fieldDoc}${serdeRename}${skipSerializing} pub ${rustFieldName}: ${finalType},`;
|
|
446
|
+
}).join('\n');
|
|
447
|
+
|
|
448
|
+
return fields;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// Process all component schemas first to ensure they are available for references
|
|
452
|
+
componentSchemas.forEach(schema => {
|
|
453
|
+
jsonSchemaToRustType(schema.schema, schema.rustName);
|
|
454
|
+
generatedTypes.add(schema.rustName);
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
// Process all message schemas to ensure all referenced types are generated
|
|
458
|
+
messageSchemas.forEach(schema => {
|
|
459
|
+
if (schema.payload) {
|
|
460
|
+
jsonSchemaToRustType(schema.payload, schema.rustName);
|
|
461
|
+
}
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
// Generate component schema definitions
|
|
465
|
+
function generateComponentSchemas() {
|
|
466
|
+
let result = '';
|
|
467
|
+
|
|
468
|
+
componentSchemas.forEach(schema => {
|
|
469
|
+
const doc = schema.description ? `/// ${schema.description}\n` : `/// ${schema.name}\n`;
|
|
470
|
+
|
|
471
|
+
// Check if this is a standalone enum schema
|
|
472
|
+
if (schema.schema.type === 'string' && schema.schema.enum && Array.isArray(schema.schema.enum)) {
|
|
473
|
+
// Generate enum definition with serde rename attributes for lowercase serialization
|
|
474
|
+
const variants = schema.schema.enum.map(variant => {
|
|
475
|
+
const { rustName, serializedName } = toRustEnumVariantWithSerde(variant);
|
|
476
|
+
return ` #[serde(rename = "${serializedName}")]\n ${rustName}`;
|
|
477
|
+
}).join(',\n');
|
|
478
|
+
result += `
|
|
479
|
+
${doc}#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
|
480
|
+
pub enum ${schema.rustName} {
|
|
481
|
+
${variants},
|
|
482
|
+
}
|
|
483
|
+
`;
|
|
484
|
+
} else {
|
|
485
|
+
// Generate struct definition
|
|
486
|
+
const fields = generateMessageStruct(schema.schema, schema.rustName);
|
|
487
|
+
result += `
|
|
488
|
+
${doc}#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
489
|
+
pub struct ${schema.rustName} {
|
|
490
|
+
${fields}
|
|
491
|
+
}
|
|
492
|
+
`;
|
|
493
|
+
}
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
return result;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// Generate nested type definitions
|
|
500
|
+
function generateNestedTypes() {
|
|
501
|
+
let result = '';
|
|
502
|
+
|
|
503
|
+
for (const [typeName, typeInfo] of nestedSchemas.entries()) {
|
|
504
|
+
// Don't skip enums - they need to be generated even if the parent type exists
|
|
505
|
+
const isEnum = typeInfo.type === 'enum';
|
|
506
|
+
const isComponentSchema = componentSchemas.some(cs => cs.rustName === typeName);
|
|
507
|
+
|
|
508
|
+
// Skip if this type was already generated as a component schema (but not enums)
|
|
509
|
+
if (!isEnum && isComponentSchema) {
|
|
510
|
+
continue;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
if (typeInfo.type === 'enum') {
|
|
514
|
+
const variants = typeInfo.variants.map(variant => {
|
|
515
|
+
const { rustName, serializedName } = toRustEnumVariantWithSerde(variant);
|
|
516
|
+
return ` #[serde(rename = "${serializedName}")]\n ${rustName}`;
|
|
517
|
+
}).join(',\n');
|
|
518
|
+
const doc = typeInfo.description ? `/// ${typeInfo.description}\n` : '';
|
|
519
|
+
result += `
|
|
520
|
+
${doc}#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
|
521
|
+
pub enum ${typeName} {
|
|
522
|
+
${variants},
|
|
523
|
+
}
|
|
524
|
+
`;
|
|
525
|
+
} else if (typeInfo.type === 'struct') {
|
|
526
|
+
const fields = generateMessageStruct(typeInfo.schema, typeName);
|
|
527
|
+
const doc = typeInfo.description ? `/// ${typeInfo.description}\n` : '';
|
|
528
|
+
result += `
|
|
529
|
+
${doc}#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
530
|
+
pub struct ${typeName} {
|
|
531
|
+
${fields}
|
|
532
|
+
}
|
|
533
|
+
`;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
return result;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
return (
|
|
541
|
+
<File name="models.rs">
|
|
542
|
+
{`//! Generated data models from AsyncAPI specification
|
|
543
|
+
|
|
544
|
+
use serde::{Deserialize, Serialize};
|
|
545
|
+
|
|
546
|
+
${generateComponentSchemas()}
|
|
547
|
+
${generateNestedTypes()}
|
|
548
|
+
${(() => {
|
|
549
|
+
// Track which types have already had implementations generated
|
|
550
|
+
const implementedTypes = new Set();
|
|
551
|
+
const implementations = [];
|
|
552
|
+
|
|
553
|
+
messageSchemas.forEach(schema => {
|
|
554
|
+
const doc = schema.description ? `/// ${schema.description}` : `/// ${schema.name} message`;
|
|
555
|
+
|
|
556
|
+
// Check if the message payload references a component schema
|
|
557
|
+
let payloadRustName = null;
|
|
558
|
+
let isComponentMessage = false;
|
|
559
|
+
|
|
560
|
+
if (schema.rawPayload && schema.rawPayload.$ref) {
|
|
561
|
+
const refName = schema.rawPayload.$ref.split('/').pop();
|
|
562
|
+
payloadRustName = toRustTypeName(refName);
|
|
563
|
+
isComponentMessage = true;
|
|
564
|
+
} else if (schema.payload && schema.payload.$ref) {
|
|
565
|
+
const refName = schema.payload.$ref.split('/').pop();
|
|
566
|
+
payloadRustName = toRustTypeName(refName);
|
|
567
|
+
isComponentMessage = true;
|
|
568
|
+
} else if (schema.payload && schema.payload['x-parser-schema-id']) {
|
|
569
|
+
// Handle resolved $ref references
|
|
570
|
+
const schemaId = schema.payload['x-parser-schema-id'];
|
|
571
|
+
if (schemaRegistry.has(schemaId)) {
|
|
572
|
+
payloadRustName = toRustTypeName(schemaId);
|
|
573
|
+
isComponentMessage = true;
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
// For component messages, always generate the message wrapper type
|
|
578
|
+
// even if the payload schema already exists
|
|
579
|
+
if (isComponentMessage && payloadRustName && !implementedTypes.has(schema.rustName)) {
|
|
580
|
+
implementedTypes.add(schema.rustName);
|
|
581
|
+
implementations.push(`
|
|
582
|
+
${doc}
|
|
583
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
584
|
+
pub struct ${schema.rustName} {
|
|
585
|
+
#[serde(flatten)]
|
|
586
|
+
pub payload: ${payloadRustName},
|
|
587
|
+
}`);
|
|
588
|
+
} else if (!generatedTypes.has(schema.rustName) && !implementedTypes.has(schema.rustName)) {
|
|
589
|
+
// Generate both struct for inline message schemas
|
|
590
|
+
implementedTypes.add(schema.rustName);
|
|
591
|
+
implementations.push(`
|
|
592
|
+
${doc}
|
|
593
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
594
|
+
pub struct ${schema.rustName} {
|
|
595
|
+
${generateMessageStruct(schema.payload, schema.rustName)}
|
|
596
|
+
}`);
|
|
597
|
+
}
|
|
598
|
+
});
|
|
599
|
+
|
|
600
|
+
return implementations.join('');
|
|
601
|
+
})()}
|
|
602
|
+
|
|
603
|
+
${messageSchemas.length === 0 && componentSchemas.length === 0 ? `
|
|
604
|
+
/// Example message structure when no messages are defined in the spec
|
|
605
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
606
|
+
pub struct ExampleMessage {
|
|
607
|
+
pub id: String,
|
|
608
|
+
pub content: String,
|
|
609
|
+
pub timestamp: chrono::DateTime<chrono::Utc>,
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
impl ExampleMessage {
|
|
613
|
+
/// Create a new instance with required fields
|
|
614
|
+
pub fn new(id: String, content: String, timestamp: chrono::DateTime<chrono::Utc>) -> Self {
|
|
615
|
+
Self {
|
|
616
|
+
id,
|
|
617
|
+
content,
|
|
618
|
+
timestamp,
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
}` : ''}
|
|
622
|
+
`}
|
|
623
|
+
</File>
|
|
624
|
+
);
|
|
625
|
+
}
|