@ioka-technologies/asyncapi-rust-client-template 0.0.34 → 0.0.35
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/dist/common/index.js +3776 -0
- package/package.json +7 -1
|
@@ -0,0 +1,3776 @@
|
|
|
1
|
+
import "@asyncapi/generator-react-sdk";
|
|
2
|
+
/******/ // The require scope
|
|
3
|
+
/******/ var __webpack_require__ = {};
|
|
4
|
+
/******/
|
|
5
|
+
/************************************************************************/
|
|
6
|
+
/******/ /* webpack/runtime/define property getters */
|
|
7
|
+
/******/ (() => {
|
|
8
|
+
/******/ // define getter functions for harmony exports
|
|
9
|
+
/******/ __webpack_require__.d = (exports, definition) => {
|
|
10
|
+
/******/ for(var key in definition) {
|
|
11
|
+
/******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
|
|
12
|
+
/******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
|
|
13
|
+
/******/ }
|
|
14
|
+
/******/ }
|
|
15
|
+
/******/ };
|
|
16
|
+
/******/ })();
|
|
17
|
+
/******/
|
|
18
|
+
/******/ /* webpack/runtime/hasOwnProperty shorthand */
|
|
19
|
+
/******/ (() => {
|
|
20
|
+
/******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
|
|
21
|
+
/******/ })();
|
|
22
|
+
/******/
|
|
23
|
+
/************************************************************************/
|
|
24
|
+
var __webpack_exports__ = {};
|
|
25
|
+
|
|
26
|
+
;// ../common/src/string-utils.js
|
|
27
|
+
/**
|
|
28
|
+
* String conversion utilities for AsyncAPI template generation
|
|
29
|
+
*
|
|
30
|
+
* This module provides consistent string conversion functions used across
|
|
31
|
+
* multiple AsyncAPI templates to ensure naming conventions are standardized.
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Converts a string to a valid Rust identifier
|
|
36
|
+
* Handles special characters, keywords, and ensures valid Rust naming conventions
|
|
37
|
+
*
|
|
38
|
+
* @param {string} str - Input string to convert
|
|
39
|
+
* @returns {string} Valid Rust identifier
|
|
40
|
+
*/
|
|
41
|
+
function toRustIdentifier(str) {
|
|
42
|
+
if (!str) return 'unknown';
|
|
43
|
+
let identifier = str.replace(/[^a-zA-Z0-9_]/g, '_').replace(/^[0-9]/, '_$&').replace(/_+/g, '_').replace(/^_+|_+$/g, '');
|
|
44
|
+
if (/^[0-9]/.test(identifier)) {
|
|
45
|
+
identifier = 'item_' + identifier;
|
|
46
|
+
}
|
|
47
|
+
if (!identifier) {
|
|
48
|
+
identifier = 'unknown';
|
|
49
|
+
}
|
|
50
|
+
const rustKeywords = ['as', 'break', 'const', 'continue', 'crate', 'else', 'enum', 'extern', 'false', 'fn', 'for', 'if', 'impl', 'in', 'let', 'loop', 'match', 'mod', 'move', 'mut', 'pub', 'ref', 'return', 'self', 'Self', 'static', 'struct', 'super', 'trait', 'true', 'type', 'unsafe', 'use', 'where', 'while', 'async', 'await', 'dyn'];
|
|
51
|
+
if (rustKeywords.includes(identifier)) {
|
|
52
|
+
identifier = identifier + '_';
|
|
53
|
+
}
|
|
54
|
+
return identifier;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Converts a string to PascalCase Rust type name
|
|
59
|
+
* Handles camelCase, snake_case, and kebab-case inputs
|
|
60
|
+
*
|
|
61
|
+
* @param {string} str - Input string to convert
|
|
62
|
+
* @returns {string} PascalCase Rust type name
|
|
63
|
+
*/
|
|
64
|
+
function toRustTypeName(str) {
|
|
65
|
+
if (!str) return 'Unknown';
|
|
66
|
+
|
|
67
|
+
// Ensure str is a string
|
|
68
|
+
const strValue = String(str);
|
|
69
|
+
const identifier = toRustIdentifier(strValue);
|
|
70
|
+
|
|
71
|
+
// Handle camelCase and PascalCase inputs by splitting on capital letters too
|
|
72
|
+
const parts = identifier.replace(/([a-z])([A-Z])/g, '$1_$2') // Insert underscore before capital letters
|
|
73
|
+
.split(/[_\s-]+/) // Split on underscores, spaces, and hyphens
|
|
74
|
+
.filter(part => part.length > 0);
|
|
75
|
+
return parts.map(part => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase()).join('');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Converts a string to snake_case Rust field name
|
|
80
|
+
*
|
|
81
|
+
* @param {string} str - Input string to convert
|
|
82
|
+
* @returns {string} snake_case Rust field name
|
|
83
|
+
*/
|
|
84
|
+
function toRustFieldName(str) {
|
|
85
|
+
if (!str) return 'unknown';
|
|
86
|
+
const identifier = toRustIdentifier(str);
|
|
87
|
+
return identifier.replace(/([A-Z])/g, '_$1').toLowerCase().replace(/^_/, '').replace(/_+/g, '_');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Converts a string to Rust enum variant name (PascalCase)
|
|
92
|
+
*
|
|
93
|
+
* @param {string} str - Input string to convert
|
|
94
|
+
* @returns {string} PascalCase enum variant name
|
|
95
|
+
*/
|
|
96
|
+
function toRustEnumVariant(str) {
|
|
97
|
+
if (!str) return 'Unknown';
|
|
98
|
+
return str.split(/[-_\s]+/).map(part => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase()).join('');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Converts a string to Rust enum variant with serde rename for lowercase serialization
|
|
103
|
+
*
|
|
104
|
+
* @param {string} str - Input string to convert
|
|
105
|
+
* @returns {object} Object with rustName (PascalCase) and serializedName (lowercase)
|
|
106
|
+
*/
|
|
107
|
+
function toRustEnumVariantWithSerde(str) {
|
|
108
|
+
if (!str) return {
|
|
109
|
+
rustName: 'Unknown',
|
|
110
|
+
serializedName: 'unknown'
|
|
111
|
+
};
|
|
112
|
+
const rustName = str.split(/[-_\s]+/).map(part => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase()).join('');
|
|
113
|
+
const serializedName = str.toLowerCase();
|
|
114
|
+
return {
|
|
115
|
+
rustName,
|
|
116
|
+
serializedName
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Converts a string to kebab-case
|
|
122
|
+
* Useful for package names and file names
|
|
123
|
+
*
|
|
124
|
+
* @param {string} str - Input string to convert
|
|
125
|
+
* @returns {string} kebab-case string
|
|
126
|
+
*/
|
|
127
|
+
function toKebabCase(str) {
|
|
128
|
+
if (!str) return '';
|
|
129
|
+
return str.toLowerCase().replace(/[^a-z0-9]/g, '-').replace(/-+/g, '-').replace(/^-|-$/g, '');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Converts a string to PascalCase
|
|
134
|
+
* Useful for class names and type names
|
|
135
|
+
*
|
|
136
|
+
* @param {string} str - Input string to convert
|
|
137
|
+
* @returns {string} PascalCase string
|
|
138
|
+
*/
|
|
139
|
+
function toPascalCase(str) {
|
|
140
|
+
if (!str) return '';
|
|
141
|
+
return str.replace(/[^a-zA-Z0-9]/g, ' ').split(' ').map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join('');
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Converts a string to snake_case
|
|
146
|
+
* Useful for Rust identifiers and file names
|
|
147
|
+
*
|
|
148
|
+
* @param {string} str - Input string to convert
|
|
149
|
+
* @returns {string} snake_case string
|
|
150
|
+
*/
|
|
151
|
+
function toSnakeCase(str) {
|
|
152
|
+
if (!str) return '';
|
|
153
|
+
return str.toLowerCase().replace(/[^a-z0-9]/g, '_').replace(/_+/g, '_').replace(/^_|_$/g, '');
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Converts a string to camelCase
|
|
158
|
+
* Useful for JavaScript/TypeScript identifiers
|
|
159
|
+
*
|
|
160
|
+
* @param {string} str - Input string to convert
|
|
161
|
+
* @returns {string} camelCase string
|
|
162
|
+
*/
|
|
163
|
+
function toCamelCase(str) {
|
|
164
|
+
if (!str) return '';
|
|
165
|
+
const pascalCase = toPascalCase(str);
|
|
166
|
+
return pascalCase.charAt(0).toLowerCase() + pascalCase.slice(1);
|
|
167
|
+
}
|
|
168
|
+
;// ../common/src/message-utils.js
|
|
169
|
+
/**
|
|
170
|
+
* Message processing utilities for AsyncAPI template generation
|
|
171
|
+
*
|
|
172
|
+
* This module provides functions for extracting and processing message information
|
|
173
|
+
* from AsyncAPI specifications, handling various AsyncAPI versions and formats.
|
|
174
|
+
*/
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Gets the message type name from a message object
|
|
180
|
+
*
|
|
181
|
+
* @param {object} message - AsyncAPI message object
|
|
182
|
+
* @returns {string|null} Message type name or null if not found
|
|
183
|
+
*/
|
|
184
|
+
function getMessageTypeName(message) {
|
|
185
|
+
if (!message) return null;
|
|
186
|
+
try {
|
|
187
|
+
// Try AsyncAPI 3.x format first - check _meta and _json properties
|
|
188
|
+
if (message._meta && message._meta.id) {
|
|
189
|
+
return message._meta.id;
|
|
190
|
+
}
|
|
191
|
+
if (message._json && message._json['x-parser-message-name']) {
|
|
192
|
+
return message._json['x-parser-message-name'];
|
|
193
|
+
}
|
|
194
|
+
if (message._json && message._json['x-parser-unique-object-id']) {
|
|
195
|
+
return message._json['x-parser-unique-object-id'];
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Try different ways to get the message name
|
|
199
|
+
if (message.name && typeof message.name === 'function') {
|
|
200
|
+
return message.name();
|
|
201
|
+
}
|
|
202
|
+
if (message.name && typeof message.name === 'string') {
|
|
203
|
+
return message.name;
|
|
204
|
+
}
|
|
205
|
+
if (message.title && typeof message.title === 'function') {
|
|
206
|
+
return message.title();
|
|
207
|
+
}
|
|
208
|
+
if (message.title && typeof message.title === 'string') {
|
|
209
|
+
return message.title;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Try to extract from $ref
|
|
213
|
+
if (message.$ref) {
|
|
214
|
+
return message.$ref.split('/').pop();
|
|
215
|
+
}
|
|
216
|
+
return null;
|
|
217
|
+
} catch (e) {
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Gets the proper Rust type name from a message
|
|
224
|
+
*
|
|
225
|
+
* @param {object} message - AsyncAPI message object
|
|
226
|
+
* @returns {string} Rust type name
|
|
227
|
+
*/
|
|
228
|
+
function getMessageRustTypeName(message) {
|
|
229
|
+
const messageName = getMessageTypeName(message);
|
|
230
|
+
return messageName ? toRustTypeName(messageName) : 'UnknownMessage';
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Gets the payload component schema Rust type name from a message
|
|
235
|
+
* This extracts the actual payload type (component schema) rather than the message wrapper
|
|
236
|
+
*
|
|
237
|
+
* @param {object} message - AsyncAPI message object
|
|
238
|
+
* @returns {string} Rust type name for the payload component schema
|
|
239
|
+
*/
|
|
240
|
+
function getPayloadRustTypeName(message) {
|
|
241
|
+
if (!message) return 'UnknownPayload';
|
|
242
|
+
try {
|
|
243
|
+
// First priority: For inline message schemas, get the message name itself
|
|
244
|
+
// This handles cases where the message is defined inline in channels
|
|
245
|
+
const messageName = getMessageTypeName(message);
|
|
246
|
+
if (messageName) {
|
|
247
|
+
// Check if this is a component message reference (has payload.$ref)
|
|
248
|
+
let payload = null;
|
|
249
|
+
if (message.payload && typeof message.payload === 'function') {
|
|
250
|
+
payload = message.payload();
|
|
251
|
+
} else if (message.payload) {
|
|
252
|
+
payload = message.payload;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Check the message's _json for payload information
|
|
256
|
+
const messageJson = message._json || message;
|
|
257
|
+
if (!payload && messageJson.payload) {
|
|
258
|
+
payload = messageJson.payload;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// If payload has a $ref, this is a component message - extract the schema name
|
|
262
|
+
if (payload && payload.$ref) {
|
|
263
|
+
const refParts = payload.$ref.split('/');
|
|
264
|
+
const schemaName = refParts[refParts.length - 1];
|
|
265
|
+
return toRustTypeName(schemaName);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// For inline message schemas, use the message name directly as the payload type
|
|
269
|
+
// This is the correct approach for messages defined inline in channels
|
|
270
|
+
return toRustTypeName(messageName);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Second priority: Try to get the payload schema reference from the message
|
|
274
|
+
let payload = null;
|
|
275
|
+
|
|
276
|
+
// Try different ways to access the payload
|
|
277
|
+
if (message.payload && typeof message.payload === 'function') {
|
|
278
|
+
payload = message.payload();
|
|
279
|
+
} else if (message.payload) {
|
|
280
|
+
payload = message.payload;
|
|
281
|
+
}
|
|
282
|
+
if (payload) {
|
|
283
|
+
// Check for $ref in the payload (direct reference to component schema)
|
|
284
|
+
if (payload.$ref) {
|
|
285
|
+
const refParts = payload.$ref.split('/');
|
|
286
|
+
const schemaName = refParts[refParts.length - 1];
|
|
287
|
+
return toRustTypeName(schemaName);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Check for resolved $ref using x-parser-schema-id
|
|
291
|
+
if (payload['x-parser-schema-id']) {
|
|
292
|
+
return toRustTypeName(payload['x-parser-schema-id']);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Check for x-parser-schema-id in _json
|
|
296
|
+
if (payload._json && payload._json['x-parser-schema-id']) {
|
|
297
|
+
return toRustTypeName(payload._json['x-parser-schema-id']);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Check for title or name in the payload schema
|
|
301
|
+
if (payload.title) {
|
|
302
|
+
const title = typeof payload.title === 'function' ? payload.title() : payload.title;
|
|
303
|
+
if (title) return toRustTypeName(title);
|
|
304
|
+
}
|
|
305
|
+
if (payload.name) {
|
|
306
|
+
const name = typeof payload.name === 'function' ? payload.name() : payload.name;
|
|
307
|
+
if (name) return toRustTypeName(name);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Check the message's _json for payload information
|
|
312
|
+
const messageJson = message._json || message;
|
|
313
|
+
if (messageJson.payload) {
|
|
314
|
+
if (messageJson.payload.$ref) {
|
|
315
|
+
const refParts = messageJson.payload.$ref.split('/');
|
|
316
|
+
const schemaName = refParts[refParts.length - 1];
|
|
317
|
+
return toRustTypeName(schemaName);
|
|
318
|
+
}
|
|
319
|
+
if (messageJson.payload['x-parser-schema-id']) {
|
|
320
|
+
return toRustTypeName(messageJson.payload['x-parser-schema-id']);
|
|
321
|
+
}
|
|
322
|
+
if (messageJson.payload.title) {
|
|
323
|
+
return toRustTypeName(messageJson.payload.title);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Final fallback: try to extract from message title or name directly
|
|
328
|
+
if (message.title && typeof message.title === 'function') {
|
|
329
|
+
const title = message.title();
|
|
330
|
+
if (title && typeof title === 'string') {
|
|
331
|
+
return toRustTypeName(title);
|
|
332
|
+
}
|
|
333
|
+
} else if (message.title && typeof message.title === 'string') {
|
|
334
|
+
return toRustTypeName(message.title);
|
|
335
|
+
}
|
|
336
|
+
if (message.name && typeof message.name === 'function') {
|
|
337
|
+
const name = message.name();
|
|
338
|
+
if (name && typeof name === 'string') {
|
|
339
|
+
return toRustTypeName(name);
|
|
340
|
+
}
|
|
341
|
+
} else if (message.name && typeof message.name === 'string') {
|
|
342
|
+
return toRustTypeName(message.name);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Check message._json for title/name
|
|
346
|
+
if (messageJson.title && typeof messageJson.title === 'string') {
|
|
347
|
+
return toRustTypeName(messageJson.title);
|
|
348
|
+
}
|
|
349
|
+
if (messageJson.name && typeof messageJson.name === 'string') {
|
|
350
|
+
return toRustTypeName(messageJson.name);
|
|
351
|
+
}
|
|
352
|
+
return 'UnknownPayload';
|
|
353
|
+
} catch (e) {
|
|
354
|
+
console.warn('Error extracting payload type name:', e.message);
|
|
355
|
+
return 'UnknownPayload';
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Gets the TypeScript type name from a message for TypeScript templates
|
|
361
|
+
*
|
|
362
|
+
* @param {object} message - AsyncAPI message object
|
|
363
|
+
* @returns {string} TypeScript type name
|
|
364
|
+
*/
|
|
365
|
+
function getMessageTypeScriptTypeName(message) {
|
|
366
|
+
const messageName = getMessageTypeName(message);
|
|
367
|
+
if (!messageName) return 'UnknownMessage';
|
|
368
|
+
|
|
369
|
+
// Convert to PascalCase for TypeScript interfaces
|
|
370
|
+
return messageName.split(/[-_\s]+/).map(part => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase()).join('');
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Gets the payload TypeScript type name from a message
|
|
375
|
+
*
|
|
376
|
+
* @param {object} message - AsyncAPI message object
|
|
377
|
+
* @returns {string} TypeScript type name for the payload
|
|
378
|
+
*/
|
|
379
|
+
function getPayloadTypeScriptTypeName(message) {
|
|
380
|
+
if (!message) return 'unknown';
|
|
381
|
+
try {
|
|
382
|
+
// Similar logic to getPayloadRustTypeName but for TypeScript naming
|
|
383
|
+
const messageName = getMessageTypeName(message);
|
|
384
|
+
if (messageName) {
|
|
385
|
+
let payload = null;
|
|
386
|
+
if (message.payload && typeof message.payload === 'function') {
|
|
387
|
+
payload = message.payload();
|
|
388
|
+
} else if (message.payload) {
|
|
389
|
+
payload = message.payload;
|
|
390
|
+
}
|
|
391
|
+
const messageJson = message._json || message;
|
|
392
|
+
if (!payload && messageJson.payload) {
|
|
393
|
+
payload = messageJson.payload;
|
|
394
|
+
}
|
|
395
|
+
if (payload && payload.$ref) {
|
|
396
|
+
const refParts = payload.$ref.split('/');
|
|
397
|
+
const schemaName = refParts[refParts.length - 1];
|
|
398
|
+
return schemaName.split(/[-_\s]+/).map(part => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase()).join('');
|
|
399
|
+
}
|
|
400
|
+
return messageName.split(/[-_\s]+/).map(part => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase()).join('');
|
|
401
|
+
}
|
|
402
|
+
return 'unknown';
|
|
403
|
+
} catch (e) {
|
|
404
|
+
console.warn('Error extracting TypeScript payload type name:', e.message);
|
|
405
|
+
return 'unknown';
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Checks if a message has a payload schema defined
|
|
411
|
+
*
|
|
412
|
+
* @param {object} message - AsyncAPI message object
|
|
413
|
+
* @returns {boolean} True if message has a payload schema
|
|
414
|
+
*/
|
|
415
|
+
function messageHasPayload(message) {
|
|
416
|
+
if (!message) return false;
|
|
417
|
+
try {
|
|
418
|
+
// Check for payload function
|
|
419
|
+
if (message.payload && typeof message.payload === 'function') {
|
|
420
|
+
const payload = message.payload();
|
|
421
|
+
return payload !== null && payload !== undefined;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// Check for payload property
|
|
425
|
+
if (message.payload) {
|
|
426
|
+
return true;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// Check _json for payload
|
|
430
|
+
const messageJson = message._json || message;
|
|
431
|
+
return !!messageJson.payload;
|
|
432
|
+
} catch (e) {
|
|
433
|
+
return false;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* Gets the content type of a message
|
|
439
|
+
*
|
|
440
|
+
* @param {object} message - AsyncAPI message object
|
|
441
|
+
* @returns {string} Content type (e.g., 'application/json')
|
|
442
|
+
*/
|
|
443
|
+
function getMessageContentType(message) {
|
|
444
|
+
if (!message) return 'application/json';
|
|
445
|
+
try {
|
|
446
|
+
// Check for contentType function
|
|
447
|
+
if (message.contentType && typeof message.contentType === 'function') {
|
|
448
|
+
return message.contentType() || 'application/json';
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// Check for contentType property
|
|
452
|
+
if (message.contentType) {
|
|
453
|
+
return message.contentType;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// Check _json for contentType
|
|
457
|
+
const messageJson = message._json || message;
|
|
458
|
+
if (messageJson.contentType) {
|
|
459
|
+
return messageJson.contentType;
|
|
460
|
+
}
|
|
461
|
+
return 'application/json';
|
|
462
|
+
} catch (e) {
|
|
463
|
+
return 'application/json';
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
;// ../common/src/channel-utils.js
|
|
467
|
+
/**
|
|
468
|
+
* Channel processing utilities for AsyncAPI template generation
|
|
469
|
+
*
|
|
470
|
+
* This module provides functions for handling channels, parameters, and
|
|
471
|
+
* dynamic channel address resolution across different AsyncAPI templates.
|
|
472
|
+
*/
|
|
473
|
+
|
|
474
|
+
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Gets the NATS subject from a channel address
|
|
478
|
+
*
|
|
479
|
+
* @param {object} channel - AsyncAPI channel object
|
|
480
|
+
* @returns {string} NATS subject
|
|
481
|
+
*/
|
|
482
|
+
function getNatsSubject(channel) {
|
|
483
|
+
try {
|
|
484
|
+
if (channel.address && typeof channel.address === 'function') {
|
|
485
|
+
return channel.address();
|
|
486
|
+
} else if (channel.address) {
|
|
487
|
+
return channel.address;
|
|
488
|
+
} else if (channel.id && typeof channel.id === 'function') {
|
|
489
|
+
return channel.id();
|
|
490
|
+
} else if (channel.id) {
|
|
491
|
+
return channel.id;
|
|
492
|
+
}
|
|
493
|
+
return 'unknown.subject';
|
|
494
|
+
} catch (e) {
|
|
495
|
+
return 'unknown.subject';
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* Gets the channel address from a channel object
|
|
501
|
+
* This is a more generic version of getNatsSubject that works for any protocol
|
|
502
|
+
*
|
|
503
|
+
* @param {object} channel - AsyncAPI channel object
|
|
504
|
+
* @returns {string} Channel address
|
|
505
|
+
*/
|
|
506
|
+
function getChannelAddress(channel) {
|
|
507
|
+
try {
|
|
508
|
+
if (channel.address && typeof channel.address === 'function') {
|
|
509
|
+
return channel.address();
|
|
510
|
+
} else if (channel.address) {
|
|
511
|
+
return channel.address;
|
|
512
|
+
} else if (channel.id && typeof channel.id === 'function') {
|
|
513
|
+
return channel.id();
|
|
514
|
+
} else if (channel.id) {
|
|
515
|
+
return channel.id;
|
|
516
|
+
}
|
|
517
|
+
return 'unknown.address';
|
|
518
|
+
} catch (e) {
|
|
519
|
+
return 'unknown.address';
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
/**
|
|
524
|
+
* Checks if a channel address contains variables (dynamic channel)
|
|
525
|
+
*
|
|
526
|
+
* @param {string} address - Channel address
|
|
527
|
+
* @returns {boolean} True if the address contains variables
|
|
528
|
+
*/
|
|
529
|
+
function isDynamicChannel(address) {
|
|
530
|
+
if (!address || typeof address !== 'string') return false;
|
|
531
|
+
return /\{[^}]+\}/.test(address);
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
/**
|
|
535
|
+
* Extracts variable names from a channel address
|
|
536
|
+
*
|
|
537
|
+
* @param {string} address - Channel address with variables
|
|
538
|
+
* @returns {Array<string>} Array of variable names
|
|
539
|
+
*/
|
|
540
|
+
function extractChannelVariables(address) {
|
|
541
|
+
if (!address || typeof address !== 'string') return [];
|
|
542
|
+
const matches = address.match(/\{([^}]+)\}/g);
|
|
543
|
+
if (!matches) return [];
|
|
544
|
+
return matches.map(match => match.slice(1, -1)); // Remove { and }
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/**
|
|
548
|
+
* Extracts channel parameters from a dynamic channel address
|
|
549
|
+
*
|
|
550
|
+
* @param {string} channelAddress - Channel address like "device.{device_id}" or "user.{user_id}.notifications"
|
|
551
|
+
* @returns {Array} Array of parameter objects with name and rustName
|
|
552
|
+
*/
|
|
553
|
+
function extractChannelParameters(channelAddress) {
|
|
554
|
+
if (!channelAddress) return [];
|
|
555
|
+
const parameterRegex = /\{([^}]+)\}/g;
|
|
556
|
+
const parameters = [];
|
|
557
|
+
let match;
|
|
558
|
+
while ((match = parameterRegex.exec(channelAddress)) !== null) {
|
|
559
|
+
const paramName = match[1];
|
|
560
|
+
parameters.push({
|
|
561
|
+
name: paramName,
|
|
562
|
+
rustName: toRustFieldName(paramName),
|
|
563
|
+
placeholder: match[0] // The full {param_name} string
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
return parameters;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
/**
|
|
570
|
+
* Gets channel parameters from a channel object
|
|
571
|
+
*
|
|
572
|
+
* @param {object} channel - AsyncAPI channel object
|
|
573
|
+
* @returns {Array<object>} Array of parameter objects with name and description
|
|
574
|
+
*/
|
|
575
|
+
function getChannelParameters(channel) {
|
|
576
|
+
try {
|
|
577
|
+
const parameters = [];
|
|
578
|
+
|
|
579
|
+
// Try to get parameters from the channel
|
|
580
|
+
let channelParams = null;
|
|
581
|
+
if (channel.parameters && typeof channel.parameters === 'function') {
|
|
582
|
+
channelParams = channel.parameters();
|
|
583
|
+
} else if (channel.parameters) {
|
|
584
|
+
channelParams = channel.parameters;
|
|
585
|
+
} else if (channel._json && channel._json.parameters) {
|
|
586
|
+
channelParams = channel._json.parameters;
|
|
587
|
+
}
|
|
588
|
+
if (channelParams) {
|
|
589
|
+
// Handle different parameter formats
|
|
590
|
+
if (typeof channelParams === 'object') {
|
|
591
|
+
for (const [paramName, paramDef] of Object.entries(channelParams)) {
|
|
592
|
+
// Skip internal AsyncAPI parser properties
|
|
593
|
+
if (paramName.startsWith('_') || paramName === 'collections' || paramName === 'meta') {
|
|
594
|
+
continue;
|
|
595
|
+
}
|
|
596
|
+
let description = 'Channel parameter';
|
|
597
|
+
if (paramDef && typeof paramDef === 'object') {
|
|
598
|
+
if (typeof paramDef.description === 'string') {
|
|
599
|
+
description = paramDef.description;
|
|
600
|
+
} else if (typeof paramDef.description === 'function') {
|
|
601
|
+
try {
|
|
602
|
+
description = paramDef.description();
|
|
603
|
+
} catch (e) {
|
|
604
|
+
description = 'Channel parameter';
|
|
605
|
+
}
|
|
606
|
+
} else if (paramDef._json && paramDef._json.description) {
|
|
607
|
+
description = paramDef._json.description;
|
|
608
|
+
}
|
|
609
|
+
} else if (typeof paramDef === 'string') {
|
|
610
|
+
description = paramDef;
|
|
611
|
+
}
|
|
612
|
+
parameters.push({
|
|
613
|
+
name: paramName,
|
|
614
|
+
description: description,
|
|
615
|
+
rustName: toRustFieldName(paramName),
|
|
616
|
+
rustType: 'String' // For now, assume all parameters are strings
|
|
617
|
+
});
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
return parameters;
|
|
622
|
+
} catch (e) {
|
|
623
|
+
console.warn('Error extracting channel parameters:', e.message);
|
|
624
|
+
return [];
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
/**
|
|
629
|
+
* Resolves a dynamic channel address with provided variable values
|
|
630
|
+
*
|
|
631
|
+
* @param {string} address - Channel address template with variables
|
|
632
|
+
* @param {object} variables - Object mapping variable names to values
|
|
633
|
+
* @returns {string} Resolved channel address
|
|
634
|
+
*/
|
|
635
|
+
function resolveChannelAddress(address, variables) {
|
|
636
|
+
if (!address || typeof address !== 'string') return address;
|
|
637
|
+
if (!variables || typeof variables !== 'object') return address;
|
|
638
|
+
let resolved = address;
|
|
639
|
+
for (const [varName, varValue] of Object.entries(variables)) {
|
|
640
|
+
const placeholder = `{${varName}}`;
|
|
641
|
+
resolved = resolved.replace(new RegExp(placeholder.replace(/[{}]/g, '\\$&'), 'g'), varValue);
|
|
642
|
+
}
|
|
643
|
+
return resolved;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
/**
|
|
647
|
+
* Checks if a channel has dynamic parameters
|
|
648
|
+
*
|
|
649
|
+
* @param {object} channel - AsyncAPI channel object
|
|
650
|
+
* @returns {boolean} True if the channel has parameters
|
|
651
|
+
*/
|
|
652
|
+
function channelHasParameters(channel) {
|
|
653
|
+
const address = getChannelAddress(channel);
|
|
654
|
+
return isDynamicChannel(address);
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
/**
|
|
658
|
+
* Generates Rust function parameters for dynamic channel parameters
|
|
659
|
+
*
|
|
660
|
+
* @param {Array} channelParameters - Array of channel parameter objects
|
|
661
|
+
* @returns {string} Rust function parameter string
|
|
662
|
+
*/
|
|
663
|
+
function generateChannelParameterArgs(channelParameters) {
|
|
664
|
+
if (!channelParameters || channelParameters.length === 0) {
|
|
665
|
+
return '';
|
|
666
|
+
}
|
|
667
|
+
return channelParameters.map(param => `${param.rustName}: String`).join(', ') + ', ';
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
/**
|
|
671
|
+
* Generates Rust format string and arguments for dynamic channel resolution
|
|
672
|
+
*
|
|
673
|
+
* @param {string} channelAddress - Original channel address with parameters
|
|
674
|
+
* @param {Array} channelParameters - Array of channel parameter objects
|
|
675
|
+
* @returns {object} Object with formatString and formatArgs
|
|
676
|
+
*/
|
|
677
|
+
function generateChannelFormatting(channelAddress, channelParameters) {
|
|
678
|
+
if (!channelParameters || channelParameters.length === 0) {
|
|
679
|
+
return {
|
|
680
|
+
formatString: `"${channelAddress}".to_string()`,
|
|
681
|
+
formatArgs: ''
|
|
682
|
+
};
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
// Replace parameter placeholders with format placeholders
|
|
686
|
+
let formatString = channelAddress;
|
|
687
|
+
const formatArgs = [];
|
|
688
|
+
for (const param of channelParameters) {
|
|
689
|
+
formatString = formatString.replace(param.placeholder, '{}');
|
|
690
|
+
formatArgs.push(param.rustName);
|
|
691
|
+
}
|
|
692
|
+
return {
|
|
693
|
+
formatString: `format!("${formatString}", ${formatArgs.join(', ')})`,
|
|
694
|
+
formatArgs: formatArgs.join(', ')
|
|
695
|
+
};
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
/**
|
|
699
|
+
* Generates TypeScript function parameters for dynamic channel parameters
|
|
700
|
+
*
|
|
701
|
+
* @param {Array} channelParameters - Array of channel parameter objects
|
|
702
|
+
* @returns {string} TypeScript function parameter string
|
|
703
|
+
*/
|
|
704
|
+
function generateTypeScriptChannelParameterArgs(channelParameters) {
|
|
705
|
+
if (!channelParameters || channelParameters.length === 0) {
|
|
706
|
+
return '';
|
|
707
|
+
}
|
|
708
|
+
return channelParameters.map(param => `${param.name}: string`).join(', ') + ', ';
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
/**
|
|
712
|
+
* Generates TypeScript template literal for dynamic channel resolution
|
|
713
|
+
*
|
|
714
|
+
* @param {string} channelAddress - Original channel address with parameters
|
|
715
|
+
* @param {Array} channelParameters - Array of channel parameter objects
|
|
716
|
+
* @returns {string} TypeScript template literal string
|
|
717
|
+
*/
|
|
718
|
+
function generateTypeScriptChannelFormatting(channelAddress, channelParameters) {
|
|
719
|
+
if (!channelParameters || channelParameters.length === 0) {
|
|
720
|
+
return `'${channelAddress}'`;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
// Replace parameter placeholders with template literal placeholders
|
|
724
|
+
let templateString = channelAddress;
|
|
725
|
+
for (const param of channelParameters) {
|
|
726
|
+
templateString = templateString.replace(param.placeholder, `\${${param.name}}`);
|
|
727
|
+
}
|
|
728
|
+
return `\`${templateString}\``;
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
/**
|
|
732
|
+
* Extracts server name from a $ref string
|
|
733
|
+
*
|
|
734
|
+
* @param {string} serverRef - Server reference like "#/servers/mqtt-server"
|
|
735
|
+
* @returns {string|null} Server name or null if invalid
|
|
736
|
+
*/
|
|
737
|
+
function extractServerNameFromRef(serverRef) {
|
|
738
|
+
if (!serverRef || typeof serverRef !== 'string') return null;
|
|
739
|
+
|
|
740
|
+
// Handle $ref format: "#/servers/server-name"
|
|
741
|
+
const refMatch = serverRef.match(/^#\/servers\/(.+)$/);
|
|
742
|
+
if (refMatch) {
|
|
743
|
+
return refMatch[1];
|
|
744
|
+
}
|
|
745
|
+
return null;
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
/**
|
|
749
|
+
* Analyzes channel server restrictions from AsyncAPI specification
|
|
750
|
+
*
|
|
751
|
+
* @param {object} asyncapi - AsyncAPI specification object
|
|
752
|
+
* @returns {Array} Array of channel server mapping objects
|
|
753
|
+
*/
|
|
754
|
+
function analyzeChannelServerMappings(asyncapi) {
|
|
755
|
+
const mappings = [];
|
|
756
|
+
try {
|
|
757
|
+
const channels = asyncapi.channels();
|
|
758
|
+
if (!channels) return mappings;
|
|
759
|
+
for (const channel of channels) {
|
|
760
|
+
const channelName = channel.id();
|
|
761
|
+
let allowedServers = null; // null means available on all servers
|
|
762
|
+
|
|
763
|
+
// Try to get servers from the channel object
|
|
764
|
+
let channelServers = null;
|
|
765
|
+
|
|
766
|
+
// Method 1: Try channel.servers() function
|
|
767
|
+
if (channel.servers && typeof channel.servers === 'function') {
|
|
768
|
+
try {
|
|
769
|
+
channelServers = channel.servers();
|
|
770
|
+
} catch (e) {
|
|
771
|
+
// Ignore errors and try other methods
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
// Method 2: Try channel._json.servers (raw JSON data)
|
|
776
|
+
if (!channelServers && channel._json && channel._json.servers) {
|
|
777
|
+
channelServers = channel._json.servers;
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
// Method 3: Try direct property access
|
|
781
|
+
if (!channelServers && channel.servers && Array.isArray(channel.servers)) {
|
|
782
|
+
channelServers = channel.servers;
|
|
783
|
+
}
|
|
784
|
+
if (channelServers && Array.isArray(channelServers) && channelServers.length > 0) {
|
|
785
|
+
// Extract server names from $ref strings
|
|
786
|
+
allowedServers = [];
|
|
787
|
+
for (const serverRef of channelServers) {
|
|
788
|
+
let serverName = null;
|
|
789
|
+
|
|
790
|
+
// Handle different ways the server reference might be provided
|
|
791
|
+
if (typeof serverRef === 'string') {
|
|
792
|
+
serverName = extractServerNameFromRef(serverRef);
|
|
793
|
+
} else if (serverRef && serverRef.$ref) {
|
|
794
|
+
serverName = extractServerNameFromRef(serverRef.$ref);
|
|
795
|
+
} else if (serverRef && typeof serverRef.id === 'function') {
|
|
796
|
+
serverName = serverRef.id();
|
|
797
|
+
} else if (serverRef && typeof serverRef.id === 'string') {
|
|
798
|
+
serverName = serverRef.id;
|
|
799
|
+
}
|
|
800
|
+
if (serverName) {
|
|
801
|
+
allowedServers.push(serverName);
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
// If no valid server names were extracted, treat as available on all servers
|
|
806
|
+
if (allowedServers.length === 0) {
|
|
807
|
+
allowedServers = null;
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
mappings.push({
|
|
811
|
+
channelName: channelName,
|
|
812
|
+
allowedServers: allowedServers,
|
|
813
|
+
rustChannelName: toRustIdentifier(channelName),
|
|
814
|
+
description: channel.description && channel.description() || ''
|
|
815
|
+
});
|
|
816
|
+
}
|
|
817
|
+
} catch (e) {
|
|
818
|
+
console.warn('Error analyzing channel server mappings:', e.message);
|
|
819
|
+
}
|
|
820
|
+
return mappings;
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
/**
|
|
824
|
+
* Checks if a channel is allowed on a specific server
|
|
825
|
+
*
|
|
826
|
+
* @param {string} channelName - Name of the channel
|
|
827
|
+
* @param {string} serverName - Name of the server
|
|
828
|
+
* @param {Array} channelMappings - Array of channel server mappings
|
|
829
|
+
* @returns {boolean} True if channel is allowed on the server
|
|
830
|
+
*/
|
|
831
|
+
function isChannelAllowedOnServer(channelName, serverName, channelMappings) {
|
|
832
|
+
const mapping = channelMappings.find(m => m.channelName === channelName);
|
|
833
|
+
if (!mapping) {
|
|
834
|
+
// If no mapping found, assume channel is allowed on all servers
|
|
835
|
+
return true;
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
// If allowedServers is null, channel is available on all servers
|
|
839
|
+
if (mapping.allowedServers === null) {
|
|
840
|
+
return true;
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
// Check if server is in the allowed list
|
|
844
|
+
return mapping.allowedServers.includes(serverName);
|
|
845
|
+
}
|
|
846
|
+
;// ../common/src/security-utils.js
|
|
847
|
+
/**
|
|
848
|
+
* Security analysis utilities for AsyncAPI template generation
|
|
849
|
+
*
|
|
850
|
+
* This module provides functions for analyzing security requirements and
|
|
851
|
+
* authentication schemes across different AsyncAPI templates.
|
|
852
|
+
*/
|
|
853
|
+
|
|
854
|
+
/**
|
|
855
|
+
* Analyzes operation security requirements from AsyncAPI specification
|
|
856
|
+
*
|
|
857
|
+
* @param {object} operation - AsyncAPI operation object
|
|
858
|
+
* @returns {object} Security analysis result
|
|
859
|
+
*/
|
|
860
|
+
function analyzeOperationSecurity(operation) {
|
|
861
|
+
try {
|
|
862
|
+
// Check AsyncAPI security field
|
|
863
|
+
const security = operation.security && operation.security();
|
|
864
|
+
if (security && Array.isArray(security) && security.length > 0) {
|
|
865
|
+
return {
|
|
866
|
+
hasSecurityRequirements: true,
|
|
867
|
+
securitySchemes: security,
|
|
868
|
+
requiresAuthentication: true
|
|
869
|
+
};
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
// Check if operation has security defined in AsyncAPI spec
|
|
873
|
+
const operationJson = operation._json || operation;
|
|
874
|
+
if (operationJson.security && Array.isArray(operationJson.security) && operationJson.security.length > 0) {
|
|
875
|
+
return {
|
|
876
|
+
hasSecurityRequirements: true,
|
|
877
|
+
securitySchemes: operationJson.security,
|
|
878
|
+
requiresAuthentication: true
|
|
879
|
+
};
|
|
880
|
+
}
|
|
881
|
+
return {
|
|
882
|
+
hasSecurityRequirements: false,
|
|
883
|
+
securitySchemes: [],
|
|
884
|
+
requiresAuthentication: false
|
|
885
|
+
};
|
|
886
|
+
} catch (e) {
|
|
887
|
+
return {
|
|
888
|
+
hasSecurityRequirements: false,
|
|
889
|
+
securitySchemes: [],
|
|
890
|
+
requiresAuthentication: false
|
|
891
|
+
};
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
/**
|
|
896
|
+
* Checks if an operation has security requirements
|
|
897
|
+
*
|
|
898
|
+
* @param {object} operation - AsyncAPI operation object
|
|
899
|
+
* @returns {boolean} True if operation has security requirements
|
|
900
|
+
*/
|
|
901
|
+
function operationHasSecurity(operation) {
|
|
902
|
+
const analysis = analyzeOperationSecurity(operation);
|
|
903
|
+
return analysis.hasSecurityRequirements;
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
/**
|
|
907
|
+
* Alias for operationHasSecurity for TypeScript templates
|
|
908
|
+
*
|
|
909
|
+
* @param {object} operation - AsyncAPI operation object
|
|
910
|
+
* @returns {boolean} True if operation requires authentication
|
|
911
|
+
*/
|
|
912
|
+
function operationRequiresAuth(operation) {
|
|
913
|
+
return operationHasSecurity(operation);
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
/**
|
|
917
|
+
* Checks if the AsyncAPI specification has security schemes defined
|
|
918
|
+
*
|
|
919
|
+
* @param {object} asyncapi - AsyncAPI specification object
|
|
920
|
+
* @param {boolean} enableAuth - Whether auth feature is enabled (optional, defaults to true)
|
|
921
|
+
* @returns {boolean} True if security schemes are present and auth is enabled
|
|
922
|
+
*/
|
|
923
|
+
function hasSecuritySchemes(asyncapi, enableAuth = true) {
|
|
924
|
+
if (!enableAuth) return false;
|
|
925
|
+
try {
|
|
926
|
+
const components = asyncapi.components();
|
|
927
|
+
if (!components) return false;
|
|
928
|
+
const securitySchemes = components.securitySchemes();
|
|
929
|
+
return securitySchemes && Object.keys(securitySchemes).length > 0;
|
|
930
|
+
} catch (e) {
|
|
931
|
+
return false;
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
/**
|
|
936
|
+
* Get security scheme type from AsyncAPI security scheme definition
|
|
937
|
+
*
|
|
938
|
+
* @param {object} securityScheme - AsyncAPI security scheme object
|
|
939
|
+
* @returns {string} Security scheme type ('jwt', 'basic', 'apikey', etc.)
|
|
940
|
+
*/
|
|
941
|
+
function getSecuritySchemeType(securityScheme) {
|
|
942
|
+
try {
|
|
943
|
+
if (securityScheme.type && typeof securityScheme.type === 'function') {
|
|
944
|
+
return securityScheme.type();
|
|
945
|
+
}
|
|
946
|
+
if (securityScheme.type) {
|
|
947
|
+
return securityScheme.type;
|
|
948
|
+
}
|
|
949
|
+
if (securityScheme._json && securityScheme._json.type) {
|
|
950
|
+
return securityScheme._json.type;
|
|
951
|
+
}
|
|
952
|
+
return 'unknown';
|
|
953
|
+
} catch (e) {
|
|
954
|
+
return 'unknown';
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
/**
|
|
959
|
+
* Gets the security scheme name from a security scheme object
|
|
960
|
+
*
|
|
961
|
+
* @param {object} securityScheme - AsyncAPI security scheme object
|
|
962
|
+
* @returns {string} Security scheme name
|
|
963
|
+
*/
|
|
964
|
+
function getSecuritySchemeName(securityScheme) {
|
|
965
|
+
try {
|
|
966
|
+
if (securityScheme.name && typeof securityScheme.name === 'function') {
|
|
967
|
+
return securityScheme.name();
|
|
968
|
+
}
|
|
969
|
+
if (securityScheme.name) {
|
|
970
|
+
return securityScheme.name;
|
|
971
|
+
}
|
|
972
|
+
if (securityScheme._json && securityScheme._json.name) {
|
|
973
|
+
return securityScheme._json.name;
|
|
974
|
+
}
|
|
975
|
+
return 'unknown';
|
|
976
|
+
} catch (e) {
|
|
977
|
+
return 'unknown';
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
/**
|
|
982
|
+
* Gets the security scheme location (header, query, cookie) for API key schemes
|
|
983
|
+
*
|
|
984
|
+
* @param {object} securityScheme - AsyncAPI security scheme object
|
|
985
|
+
* @returns {string} Security scheme location ('header', 'query', 'cookie')
|
|
986
|
+
*/
|
|
987
|
+
function getSecuritySchemeLocation(securityScheme) {
|
|
988
|
+
try {
|
|
989
|
+
if (securityScheme.in && typeof securityScheme.in === 'function') {
|
|
990
|
+
return securityScheme.in();
|
|
991
|
+
}
|
|
992
|
+
if (securityScheme.in) {
|
|
993
|
+
return securityScheme.in;
|
|
994
|
+
}
|
|
995
|
+
if (securityScheme._json && securityScheme._json.in) {
|
|
996
|
+
return securityScheme._json.in;
|
|
997
|
+
}
|
|
998
|
+
return 'header'; // Default to header
|
|
999
|
+
} catch (e) {
|
|
1000
|
+
return 'header';
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
/**
|
|
1005
|
+
* Extract security requirements for all operations in the AsyncAPI spec
|
|
1006
|
+
*
|
|
1007
|
+
* @param {object} asyncapi - AsyncAPI specification object
|
|
1008
|
+
* @returns {object} Map of operation names to their security requirements
|
|
1009
|
+
*/
|
|
1010
|
+
function extractOperationSecurityMap(asyncapi) {
|
|
1011
|
+
const securityMap = {};
|
|
1012
|
+
try {
|
|
1013
|
+
const operations = asyncapi.operations && asyncapi.operations();
|
|
1014
|
+
if (operations) {
|
|
1015
|
+
// Handle AsyncAPI parser collection - use .all() method to get array
|
|
1016
|
+
const operationArray = operations.all ? operations.all() : Object.values(operations);
|
|
1017
|
+
operationArray.forEach(operation => {
|
|
1018
|
+
// Get operation ID
|
|
1019
|
+
let operationId = null;
|
|
1020
|
+
if (operation._meta && operation._meta.id) {
|
|
1021
|
+
operationId = operation._meta.id;
|
|
1022
|
+
} else if (operation.id && typeof operation.id === 'function') {
|
|
1023
|
+
operationId = operation.id();
|
|
1024
|
+
} else if (operation.id) {
|
|
1025
|
+
operationId = operation.id;
|
|
1026
|
+
}
|
|
1027
|
+
if (operationId) {
|
|
1028
|
+
const securityAnalysis = analyzeOperationSecurity(operation);
|
|
1029
|
+
securityMap[operationId] = securityAnalysis;
|
|
1030
|
+
}
|
|
1031
|
+
});
|
|
1032
|
+
}
|
|
1033
|
+
} catch (e) {
|
|
1034
|
+
console.warn('Error extracting operation security map:', e.message);
|
|
1035
|
+
}
|
|
1036
|
+
return securityMap;
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
/**
|
|
1040
|
+
* Gets all security schemes from the AsyncAPI specification
|
|
1041
|
+
*
|
|
1042
|
+
* @param {object} asyncapi - AsyncAPI specification object
|
|
1043
|
+
* @returns {object} Map of security scheme names to their definitions
|
|
1044
|
+
*/
|
|
1045
|
+
function getAllSecuritySchemes(asyncapi) {
|
|
1046
|
+
try {
|
|
1047
|
+
const components = asyncapi.components();
|
|
1048
|
+
if (!components) return {};
|
|
1049
|
+
const securitySchemes = components.securitySchemes();
|
|
1050
|
+
if (!securitySchemes) return {};
|
|
1051
|
+
|
|
1052
|
+
// Convert to plain object if it's a collection
|
|
1053
|
+
if (typeof securitySchemes === 'object' && securitySchemes.all) {
|
|
1054
|
+
const schemes = {};
|
|
1055
|
+
const schemeArray = securitySchemes.all();
|
|
1056
|
+
schemeArray.forEach(scheme => {
|
|
1057
|
+
const name = scheme.id ? scheme.id() : 'unknown';
|
|
1058
|
+
schemes[name] = scheme;
|
|
1059
|
+
});
|
|
1060
|
+
return schemes;
|
|
1061
|
+
}
|
|
1062
|
+
return securitySchemes;
|
|
1063
|
+
} catch (e) {
|
|
1064
|
+
console.warn('Error extracting security schemes:', e.message);
|
|
1065
|
+
return {};
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
/**
|
|
1070
|
+
* Checks if a security scheme is of a specific type
|
|
1071
|
+
*
|
|
1072
|
+
* @param {object} securityScheme - AsyncAPI security scheme object
|
|
1073
|
+
* @param {string} expectedType - Expected security scheme type
|
|
1074
|
+
* @returns {boolean} True if the scheme matches the expected type
|
|
1075
|
+
*/
|
|
1076
|
+
function isSecuritySchemeType(securityScheme, expectedType) {
|
|
1077
|
+
const actualType = getSecuritySchemeType(securityScheme);
|
|
1078
|
+
return actualType.toLowerCase() === expectedType.toLowerCase();
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
/**
|
|
1082
|
+
* Gets the default port for a given protocol
|
|
1083
|
+
*
|
|
1084
|
+
* @param {string} protocol - Protocol name
|
|
1085
|
+
* @returns {number} Default port number
|
|
1086
|
+
*/
|
|
1087
|
+
function getDefaultPort(protocol) {
|
|
1088
|
+
switch (protocol === null || protocol === void 0 ? void 0 : protocol.toLowerCase()) {
|
|
1089
|
+
case 'http':
|
|
1090
|
+
return 80;
|
|
1091
|
+
case 'https':
|
|
1092
|
+
return 443;
|
|
1093
|
+
case 'ws':
|
|
1094
|
+
case 'websocket':
|
|
1095
|
+
return 80;
|
|
1096
|
+
case 'wss':
|
|
1097
|
+
case 'websockets':
|
|
1098
|
+
return 443;
|
|
1099
|
+
case 'mqtt':
|
|
1100
|
+
return 1883;
|
|
1101
|
+
case 'mqtts':
|
|
1102
|
+
return 8883;
|
|
1103
|
+
case 'amqp':
|
|
1104
|
+
return 5672;
|
|
1105
|
+
case 'amqps':
|
|
1106
|
+
return 5671;
|
|
1107
|
+
case 'kafka':
|
|
1108
|
+
return 9092;
|
|
1109
|
+
case 'nats':
|
|
1110
|
+
return 4222;
|
|
1111
|
+
default:
|
|
1112
|
+
return 8080;
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
/**
|
|
1117
|
+
* Validates that all server references in channels exist in the servers section
|
|
1118
|
+
*
|
|
1119
|
+
* @param {object} asyncapi - AsyncAPI specification object
|
|
1120
|
+
* @returns {object} Validation result with errors if any
|
|
1121
|
+
*/
|
|
1122
|
+
function validateChannelServerReferences(asyncapi) {
|
|
1123
|
+
const result = {
|
|
1124
|
+
valid: true,
|
|
1125
|
+
errors: []
|
|
1126
|
+
};
|
|
1127
|
+
try {
|
|
1128
|
+
// Try multiple ways to get servers
|
|
1129
|
+
let servers = null;
|
|
1130
|
+
let serverNames = [];
|
|
1131
|
+
|
|
1132
|
+
// Method 1: Try asyncapi.servers() function
|
|
1133
|
+
if (asyncapi.servers && typeof asyncapi.servers === 'function') {
|
|
1134
|
+
try {
|
|
1135
|
+
servers = asyncapi.servers();
|
|
1136
|
+
if (servers) {
|
|
1137
|
+
// Check if this is a collection object (has iterator methods)
|
|
1138
|
+
if (typeof servers[Symbol.iterator] === 'function') {
|
|
1139
|
+
// It's iterable - iterate through the servers
|
|
1140
|
+
serverNames = [];
|
|
1141
|
+
for (const server of servers) {
|
|
1142
|
+
const serverName = server.id && typeof server.id === 'function' ? server.id() : server.id;
|
|
1143
|
+
if (serverName) {
|
|
1144
|
+
serverNames.push(serverName);
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
} else {
|
|
1148
|
+
// It's a plain object - use Object.keys
|
|
1149
|
+
serverNames = Object.keys(servers);
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
} catch (e) {
|
|
1153
|
+
// Ignore and try other methods
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
// Method 2: Try asyncapi._json.servers (raw JSON data)
|
|
1158
|
+
if (serverNames.length === 0 && asyncapi._json && asyncapi._json.servers) {
|
|
1159
|
+
servers = asyncapi._json.servers;
|
|
1160
|
+
serverNames = Object.keys(servers);
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
// Method 3: Try direct property access
|
|
1164
|
+
if (serverNames.length === 0 && asyncapi.servers && typeof asyncapi.servers === 'object') {
|
|
1165
|
+
servers = asyncapi.servers;
|
|
1166
|
+
serverNames = Object.keys(servers);
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
// Method 4: Try json() method if available
|
|
1170
|
+
if (serverNames.length === 0 && asyncapi.json && typeof asyncapi.json === 'function') {
|
|
1171
|
+
try {
|
|
1172
|
+
const jsonDoc = asyncapi.json();
|
|
1173
|
+
if (jsonDoc && jsonDoc.servers) {
|
|
1174
|
+
servers = jsonDoc.servers;
|
|
1175
|
+
serverNames = Object.keys(servers);
|
|
1176
|
+
}
|
|
1177
|
+
} catch (e) {
|
|
1178
|
+
// Ignore and try other methods
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
// Note: We'll skip channel validation here to avoid circular dependency
|
|
1183
|
+
// This function can be enhanced later if needed
|
|
1184
|
+
console.warn('Channel server validation skipped to avoid circular dependency');
|
|
1185
|
+
} catch (e) {
|
|
1186
|
+
result.valid = false;
|
|
1187
|
+
result.errors.push({
|
|
1188
|
+
message: `Error validating channel server references: ${e.message}`
|
|
1189
|
+
});
|
|
1190
|
+
}
|
|
1191
|
+
return result;
|
|
1192
|
+
}
|
|
1193
|
+
;// ../common/src/template-utils.js
|
|
1194
|
+
/**
|
|
1195
|
+
* Template parameter utilities for AsyncAPI template generation
|
|
1196
|
+
*
|
|
1197
|
+
* This module provides functions for handling template parameters and
|
|
1198
|
+
* common template operations across different AsyncAPI templates.
|
|
1199
|
+
*/
|
|
1200
|
+
|
|
1201
|
+
|
|
1202
|
+
|
|
1203
|
+
/**
|
|
1204
|
+
* Checks if a parameter contains unresolved template variables
|
|
1205
|
+
*
|
|
1206
|
+
* @param {string} value - Parameter value to check
|
|
1207
|
+
* @returns {boolean} True if the value contains template variables
|
|
1208
|
+
*/
|
|
1209
|
+
function isTemplateVariable(value) {
|
|
1210
|
+
return typeof value === 'string' && value.includes('{{') && value.includes('}}');
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
/**
|
|
1214
|
+
* Extracts information from AsyncAPI specification with fallbacks
|
|
1215
|
+
*
|
|
1216
|
+
* @param {object} asyncapi - AsyncAPI specification object
|
|
1217
|
+
* @returns {object} Object with title, version, and description
|
|
1218
|
+
*/
|
|
1219
|
+
function extractAsyncApiInfo(asyncapi) {
|
|
1220
|
+
let title, version, description;
|
|
1221
|
+
try {
|
|
1222
|
+
const info = asyncapi.info();
|
|
1223
|
+
title = info.title();
|
|
1224
|
+
version = info.version();
|
|
1225
|
+
description = info.description();
|
|
1226
|
+
} catch (error) {
|
|
1227
|
+
title = 'UnknownAPI';
|
|
1228
|
+
version = '1.0.0';
|
|
1229
|
+
description = 'Generated AsyncAPI client';
|
|
1230
|
+
}
|
|
1231
|
+
return {
|
|
1232
|
+
title,
|
|
1233
|
+
version,
|
|
1234
|
+
description
|
|
1235
|
+
};
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
/**
|
|
1239
|
+
* Resolves template parameters with fallbacks based on AsyncAPI info
|
|
1240
|
+
*
|
|
1241
|
+
* @param {object} params - Template parameters
|
|
1242
|
+
* @param {object} asyncApiInfo - AsyncAPI info object from extractAsyncApiInfo
|
|
1243
|
+
* @returns {object} Resolved parameters
|
|
1244
|
+
*/
|
|
1245
|
+
function resolveTemplateParameters(params, asyncApiInfo) {
|
|
1246
|
+
const {
|
|
1247
|
+
title,
|
|
1248
|
+
version
|
|
1249
|
+
} = asyncApiInfo;
|
|
1250
|
+
|
|
1251
|
+
// Resolve parameters, falling back to extracted values if parameters contain template variables
|
|
1252
|
+
const clientName = params.clientName && !isTemplateVariable(params.clientName) ? params.clientName : `${toPascalCase(title)}Client`;
|
|
1253
|
+
const packageName = params.packageName && !isTemplateVariable(params.packageName) ? params.packageName : `${toKebabCase(title)}-client`;
|
|
1254
|
+
const packageVersion = params.packageVersion && !isTemplateVariable(params.packageVersion) ? params.packageVersion : version;
|
|
1255
|
+
const license = params.license && !isTemplateVariable(params.license) ? params.license : 'Apache-2.0';
|
|
1256
|
+
const author = params.author && !isTemplateVariable(params.author) ? params.author : 'AsyncAPI Generator';
|
|
1257
|
+
return {
|
|
1258
|
+
clientName,
|
|
1259
|
+
packageName,
|
|
1260
|
+
packageVersion,
|
|
1261
|
+
license,
|
|
1262
|
+
author
|
|
1263
|
+
};
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
/**
|
|
1267
|
+
* Generates package.json content for Node.js templates
|
|
1268
|
+
*
|
|
1269
|
+
* @param {object} resolvedParams - Resolved template parameters
|
|
1270
|
+
* @param {object} asyncApiInfo - AsyncAPI info object
|
|
1271
|
+
* @param {object} options - Additional options for package.json generation
|
|
1272
|
+
* @returns {object} Package.json object
|
|
1273
|
+
*/
|
|
1274
|
+
function generatePackageJson(resolvedParams, asyncApiInfo, options = {}) {
|
|
1275
|
+
const {
|
|
1276
|
+
title,
|
|
1277
|
+
description
|
|
1278
|
+
} = asyncApiInfo;
|
|
1279
|
+
const {
|
|
1280
|
+
packageName,
|
|
1281
|
+
packageVersion,
|
|
1282
|
+
license,
|
|
1283
|
+
author
|
|
1284
|
+
} = resolvedParams;
|
|
1285
|
+
const basePackage = {
|
|
1286
|
+
name: packageName,
|
|
1287
|
+
version: packageVersion,
|
|
1288
|
+
description: `${description || title} - AsyncAPI Client`,
|
|
1289
|
+
author: author,
|
|
1290
|
+
license: license,
|
|
1291
|
+
keywords: ['asyncapi', 'client', title.toLowerCase().replace(/[^a-z0-9]/g, '-'), ...(options.additionalKeywords || [])]
|
|
1292
|
+
};
|
|
1293
|
+
|
|
1294
|
+
// Merge with additional options
|
|
1295
|
+
return {
|
|
1296
|
+
...basePackage,
|
|
1297
|
+
...options.additionalFields
|
|
1298
|
+
};
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
/**
|
|
1302
|
+
* Generates README.md content for templates
|
|
1303
|
+
*
|
|
1304
|
+
* @param {object} resolvedParams - Resolved template parameters
|
|
1305
|
+
* @param {object} asyncApiInfo - AsyncAPI info object
|
|
1306
|
+
* @param {object} asyncapi - AsyncAPI specification object
|
|
1307
|
+
* @param {object} options - Additional options for README generation
|
|
1308
|
+
* @returns {string} README.md content
|
|
1309
|
+
*/
|
|
1310
|
+
function generateReadmeContent(resolvedParams, asyncApiInfo, asyncapi, options = {}) {
|
|
1311
|
+
const {
|
|
1312
|
+
title,
|
|
1313
|
+
description
|
|
1314
|
+
} = asyncApiInfo;
|
|
1315
|
+
const {
|
|
1316
|
+
packageName,
|
|
1317
|
+
packageVersion,
|
|
1318
|
+
license
|
|
1319
|
+
} = resolvedParams;
|
|
1320
|
+
const sections = {
|
|
1321
|
+
title: `# ${title}`,
|
|
1322
|
+
description: description || 'Generated AsyncAPI client',
|
|
1323
|
+
overview: options.overview || 'This client provides type-safe access to your AsyncAPI service.',
|
|
1324
|
+
installation: options.installation || `\`\`\`bash\nnpm install ${packageName}\n\`\`\``,
|
|
1325
|
+
usage: options.usage || '// Usage examples will be added here',
|
|
1326
|
+
metadata: `## Generated from AsyncAPI
|
|
1327
|
+
|
|
1328
|
+
- **AsyncAPI Version**: ${asyncapi.version()}
|
|
1329
|
+
- **Generated**: ${new Date().toISOString()}
|
|
1330
|
+
- **Title**: ${title}
|
|
1331
|
+
- **Version**: ${packageVersion}`,
|
|
1332
|
+
license: `## License
|
|
1333
|
+
|
|
1334
|
+
${license}`
|
|
1335
|
+
};
|
|
1336
|
+
|
|
1337
|
+
// Allow overriding sections
|
|
1338
|
+
const finalSections = {
|
|
1339
|
+
...sections,
|
|
1340
|
+
...options.sections
|
|
1341
|
+
};
|
|
1342
|
+
return Object.values(finalSections).join('\n\n');
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
/**
|
|
1346
|
+
* Validates template parameters
|
|
1347
|
+
*
|
|
1348
|
+
* @param {object} params - Template parameters to validate
|
|
1349
|
+
* @param {Array<string>} requiredParams - List of required parameter names
|
|
1350
|
+
* @returns {object} Validation result with errors if any
|
|
1351
|
+
*/
|
|
1352
|
+
function validateTemplateParameters(params, requiredParams = []) {
|
|
1353
|
+
const result = {
|
|
1354
|
+
valid: true,
|
|
1355
|
+
errors: [],
|
|
1356
|
+
warnings: []
|
|
1357
|
+
};
|
|
1358
|
+
|
|
1359
|
+
// Check required parameters
|
|
1360
|
+
for (const paramName of requiredParams) {
|
|
1361
|
+
if (!params[paramName] || isTemplateVariable(params[paramName])) {
|
|
1362
|
+
result.valid = false;
|
|
1363
|
+
result.errors.push(`Required parameter '${paramName}' is missing or contains unresolved template variables`);
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
// Check for common issues
|
|
1368
|
+
if (params.packageName && !/^[a-z0-9-]+$/.test(params.packageName)) {
|
|
1369
|
+
result.warnings.push('Package name should only contain lowercase letters, numbers, and hyphens');
|
|
1370
|
+
}
|
|
1371
|
+
if (params.packageVersion && !/^\d+\.\d+\.\d+/.test(params.packageVersion)) {
|
|
1372
|
+
result.warnings.push('Package version should follow semantic versioning (e.g., 1.0.0)');
|
|
1373
|
+
}
|
|
1374
|
+
return result;
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
/**
|
|
1378
|
+
* Generates TypeScript configuration for TypeScript templates
|
|
1379
|
+
*
|
|
1380
|
+
* @param {object} options - TypeScript configuration options
|
|
1381
|
+
* @returns {object} TypeScript configuration object
|
|
1382
|
+
*/
|
|
1383
|
+
function generateTypeScriptConfig(options = {}) {
|
|
1384
|
+
const defaultConfig = {
|
|
1385
|
+
compilerOptions: {
|
|
1386
|
+
target: 'ES2020',
|
|
1387
|
+
module: 'ES2020',
|
|
1388
|
+
lib: ['ES2020', 'DOM'],
|
|
1389
|
+
outDir: './dist',
|
|
1390
|
+
rootDir: './src',
|
|
1391
|
+
strict: true,
|
|
1392
|
+
esModuleInterop: true,
|
|
1393
|
+
skipLibCheck: true,
|
|
1394
|
+
forceConsistentCasingInFileNames: true,
|
|
1395
|
+
declaration: true,
|
|
1396
|
+
declarationMap: true,
|
|
1397
|
+
sourceMap: true,
|
|
1398
|
+
moduleResolution: 'bundler',
|
|
1399
|
+
allowSyntheticDefaultImports: true,
|
|
1400
|
+
experimentalDecorators: true,
|
|
1401
|
+
emitDecoratorMetadata: true,
|
|
1402
|
+
resolveJsonModule: true,
|
|
1403
|
+
typeRoots: ['node_modules/@types']
|
|
1404
|
+
},
|
|
1405
|
+
include: ['src/**/*'],
|
|
1406
|
+
exclude: ['node_modules', 'dist', '**/*.test.ts', '**/*.spec.ts']
|
|
1407
|
+
};
|
|
1408
|
+
|
|
1409
|
+
// Merge with custom options
|
|
1410
|
+
return {
|
|
1411
|
+
...defaultConfig,
|
|
1412
|
+
compilerOptions: {
|
|
1413
|
+
...defaultConfig.compilerOptions,
|
|
1414
|
+
...options.compilerOptions
|
|
1415
|
+
},
|
|
1416
|
+
include: options.include || defaultConfig.include,
|
|
1417
|
+
exclude: options.exclude || defaultConfig.exclude
|
|
1418
|
+
};
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
/**
|
|
1422
|
+
* Generates ESLint configuration for JavaScript/TypeScript templates
|
|
1423
|
+
*
|
|
1424
|
+
* @param {object} options - ESLint configuration options
|
|
1425
|
+
* @returns {object} ESLint configuration object
|
|
1426
|
+
*/
|
|
1427
|
+
function generateEslintConfig(options = {}) {
|
|
1428
|
+
const isTypeScript = options.typescript || false;
|
|
1429
|
+
const baseConfig = {
|
|
1430
|
+
env: {
|
|
1431
|
+
node: true,
|
|
1432
|
+
es2021: true
|
|
1433
|
+
},
|
|
1434
|
+
extends: ['eslint:recommended'],
|
|
1435
|
+
parserOptions: {
|
|
1436
|
+
ecmaVersion: 12,
|
|
1437
|
+
sourceType: 'module'
|
|
1438
|
+
},
|
|
1439
|
+
rules: {
|
|
1440
|
+
'indent': ['error', 4],
|
|
1441
|
+
'linebreak-style': ['error', 'unix'],
|
|
1442
|
+
'quotes': ['error', 'single'],
|
|
1443
|
+
'semi': ['error', 'always']
|
|
1444
|
+
}
|
|
1445
|
+
};
|
|
1446
|
+
if (isTypeScript) {
|
|
1447
|
+
baseConfig.extends.push('@typescript-eslint/recommended');
|
|
1448
|
+
baseConfig.parser = '@typescript-eslint/parser';
|
|
1449
|
+
baseConfig.plugins = ['@typescript-eslint'];
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
// Merge with custom options
|
|
1453
|
+
return {
|
|
1454
|
+
...baseConfig,
|
|
1455
|
+
...options.additionalConfig
|
|
1456
|
+
};
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
/**
|
|
1460
|
+
* Formats a date for use in generated files
|
|
1461
|
+
*
|
|
1462
|
+
* @param {Date} date - Date to format (defaults to current date)
|
|
1463
|
+
* @returns {string} Formatted date string
|
|
1464
|
+
*/
|
|
1465
|
+
function formatGenerationDate(date = new Date()) {
|
|
1466
|
+
return date.toISOString();
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
/**
|
|
1470
|
+
* Generates a comment header for generated files
|
|
1471
|
+
*
|
|
1472
|
+
* @param {object} options - Header options
|
|
1473
|
+
* @returns {string} Comment header
|
|
1474
|
+
*/
|
|
1475
|
+
function generateFileHeader(options = {}) {
|
|
1476
|
+
const {
|
|
1477
|
+
title = 'Generated AsyncAPI File',
|
|
1478
|
+
description = 'This file was automatically generated from an AsyncAPI specification.',
|
|
1479
|
+
generator = 'AsyncAPI Generator',
|
|
1480
|
+
date = new Date(),
|
|
1481
|
+
warning = 'Do not modify this file directly.'
|
|
1482
|
+
} = options;
|
|
1483
|
+
return `/**
|
|
1484
|
+
* ${title}
|
|
1485
|
+
*
|
|
1486
|
+
* ${description}
|
|
1487
|
+
*
|
|
1488
|
+
* Generated by: ${generator}
|
|
1489
|
+
* Generated on: ${formatGenerationDate(date)}
|
|
1490
|
+
*
|
|
1491
|
+
* WARNING: ${warning}
|
|
1492
|
+
*/`;
|
|
1493
|
+
}
|
|
1494
|
+
;// ../common/src/operation-utils.js
|
|
1495
|
+
/**
|
|
1496
|
+
* Operation processing utilities for AsyncAPI template generation
|
|
1497
|
+
*
|
|
1498
|
+
* This module provides functions for extracting and processing operation information
|
|
1499
|
+
* from AsyncAPI specifications, handling different operation types and patterns.
|
|
1500
|
+
*/
|
|
1501
|
+
|
|
1502
|
+
|
|
1503
|
+
|
|
1504
|
+
/**
|
|
1505
|
+
* Gets the operation name from an operation object
|
|
1506
|
+
*
|
|
1507
|
+
* @param {object} operation - AsyncAPI operation object
|
|
1508
|
+
* @returns {string|null} Operation name or null if not found
|
|
1509
|
+
*/
|
|
1510
|
+
function getOperationName(operation) {
|
|
1511
|
+
if (!operation) return null;
|
|
1512
|
+
try {
|
|
1513
|
+
// Try AsyncAPI 3.x format first - check _meta and _json properties
|
|
1514
|
+
if (operation._meta && operation._meta.id) {
|
|
1515
|
+
return operation._meta.id;
|
|
1516
|
+
}
|
|
1517
|
+
if (operation._json && operation._json['x-parser-operation-id']) {
|
|
1518
|
+
return operation._json['x-parser-operation-id'];
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
// Try different ways to get the operation name
|
|
1522
|
+
if (operation.id && typeof operation.id === 'function') {
|
|
1523
|
+
return operation.id();
|
|
1524
|
+
}
|
|
1525
|
+
if (operation.id && typeof operation.id === 'string') {
|
|
1526
|
+
return operation.id;
|
|
1527
|
+
}
|
|
1528
|
+
if (operation.operationId && typeof operation.operationId === 'function') {
|
|
1529
|
+
return operation.operationId();
|
|
1530
|
+
}
|
|
1531
|
+
if (operation.operationId && typeof operation.operationId === 'string') {
|
|
1532
|
+
return operation.operationId;
|
|
1533
|
+
}
|
|
1534
|
+
return null;
|
|
1535
|
+
} catch (e) {
|
|
1536
|
+
return null;
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
|
|
1540
|
+
/**
|
|
1541
|
+
* Gets the operation action (send/receive/publish/subscribe)
|
|
1542
|
+
*
|
|
1543
|
+
* @param {object} operation - AsyncAPI operation object
|
|
1544
|
+
* @returns {string} Operation action
|
|
1545
|
+
*/
|
|
1546
|
+
function getOperationAction(operation) {
|
|
1547
|
+
if (!operation) return 'unknown';
|
|
1548
|
+
try {
|
|
1549
|
+
if (operation.action && typeof operation.action === 'function') {
|
|
1550
|
+
return operation.action();
|
|
1551
|
+
}
|
|
1552
|
+
if (operation.action && typeof operation.action === 'string') {
|
|
1553
|
+
return operation.action;
|
|
1554
|
+
}
|
|
1555
|
+
if (operation._json && operation._json.action) {
|
|
1556
|
+
return operation._json.action;
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1559
|
+
// Fallback based on operation type
|
|
1560
|
+
if (operation.isSend && typeof operation.isSend === 'function' && operation.isSend()) {
|
|
1561
|
+
return 'send';
|
|
1562
|
+
}
|
|
1563
|
+
if (operation.isReceive && typeof operation.isReceive === 'function' && operation.isReceive()) {
|
|
1564
|
+
return 'receive';
|
|
1565
|
+
}
|
|
1566
|
+
return 'unknown';
|
|
1567
|
+
} catch (e) {
|
|
1568
|
+
return 'unknown';
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1572
|
+
/**
|
|
1573
|
+
* Gets the channel associated with an operation
|
|
1574
|
+
*
|
|
1575
|
+
* @param {object} operation - AsyncAPI operation object
|
|
1576
|
+
* @returns {object|null} Channel object or null if not found
|
|
1577
|
+
*/
|
|
1578
|
+
function getOperationChannel(operation) {
|
|
1579
|
+
if (!operation) return null;
|
|
1580
|
+
try {
|
|
1581
|
+
if (operation.channel && typeof operation.channel === 'function') {
|
|
1582
|
+
return operation.channel();
|
|
1583
|
+
}
|
|
1584
|
+
if (operation.channel && typeof operation.channel === 'object') {
|
|
1585
|
+
return operation.channel;
|
|
1586
|
+
}
|
|
1587
|
+
return null;
|
|
1588
|
+
} catch (e) {
|
|
1589
|
+
return null;
|
|
1590
|
+
}
|
|
1591
|
+
}
|
|
1592
|
+
|
|
1593
|
+
/**
|
|
1594
|
+
* Gets the messages associated with an operation
|
|
1595
|
+
*
|
|
1596
|
+
* @param {object} operation - AsyncAPI operation object
|
|
1597
|
+
* @returns {Array} Array of message objects
|
|
1598
|
+
*/
|
|
1599
|
+
function getOperationMessages(operation) {
|
|
1600
|
+
if (!operation) return [];
|
|
1601
|
+
try {
|
|
1602
|
+
const messages = [];
|
|
1603
|
+
|
|
1604
|
+
// Try to get messages from the operation
|
|
1605
|
+
if (operation.messages && typeof operation.messages === 'function') {
|
|
1606
|
+
const operationMessages = operation.messages();
|
|
1607
|
+
if (operationMessages && Array.isArray(operationMessages)) {
|
|
1608
|
+
messages.push(...operationMessages);
|
|
1609
|
+
} else if (operationMessages && typeof operationMessages === 'object') {
|
|
1610
|
+
// Handle collection object
|
|
1611
|
+
if (operationMessages.all && typeof operationMessages.all === 'function') {
|
|
1612
|
+
messages.push(...operationMessages.all());
|
|
1613
|
+
} else {
|
|
1614
|
+
messages.push(...Object.values(operationMessages));
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
} else if (operation.messages && Array.isArray(operation.messages)) {
|
|
1618
|
+
messages.push(...operation.messages);
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
// Try to get message from _json
|
|
1622
|
+
if (messages.length === 0 && operation._json && operation._json.message) {
|
|
1623
|
+
if (Array.isArray(operation._json.message)) {
|
|
1624
|
+
messages.push(...operation._json.message);
|
|
1625
|
+
} else {
|
|
1626
|
+
messages.push(operation._json.message);
|
|
1627
|
+
}
|
|
1628
|
+
}
|
|
1629
|
+
return messages;
|
|
1630
|
+
} catch (e) {
|
|
1631
|
+
return [];
|
|
1632
|
+
}
|
|
1633
|
+
}
|
|
1634
|
+
|
|
1635
|
+
/**
|
|
1636
|
+
* Checks if an operation is a send operation
|
|
1637
|
+
*
|
|
1638
|
+
* @param {object} operation - AsyncAPI operation object
|
|
1639
|
+
* @returns {boolean} True if operation is a send operation
|
|
1640
|
+
*/
|
|
1641
|
+
function isOperationSend(operation) {
|
|
1642
|
+
const action = getOperationAction(operation);
|
|
1643
|
+
return action === 'send' || action === 'publish';
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1646
|
+
/**
|
|
1647
|
+
* Checks if an operation is a receive operation
|
|
1648
|
+
*
|
|
1649
|
+
* @param {object} operation - AsyncAPI operation object
|
|
1650
|
+
* @returns {boolean} True if operation is a receive operation
|
|
1651
|
+
*/
|
|
1652
|
+
function isOperationReceive(operation) {
|
|
1653
|
+
const action = getOperationAction(operation);
|
|
1654
|
+
return action === 'receive' || action === 'subscribe';
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1657
|
+
/**
|
|
1658
|
+
* Gets the Rust function name for an operation
|
|
1659
|
+
*
|
|
1660
|
+
* @param {object} operation - AsyncAPI operation object
|
|
1661
|
+
* @returns {string} Rust function name
|
|
1662
|
+
*/
|
|
1663
|
+
function getOperationRustFunctionName(operation) {
|
|
1664
|
+
const operationName = getOperationName(operation);
|
|
1665
|
+
if (!operationName) return 'unknown_operation';
|
|
1666
|
+
return toRustIdentifier(operationName);
|
|
1667
|
+
}
|
|
1668
|
+
|
|
1669
|
+
/**
|
|
1670
|
+
* Gets the TypeScript method name for an operation
|
|
1671
|
+
*
|
|
1672
|
+
* @param {object} operation - AsyncAPI operation object
|
|
1673
|
+
* @returns {string} TypeScript method name
|
|
1674
|
+
*/
|
|
1675
|
+
function getOperationTypeScriptMethodName(operation) {
|
|
1676
|
+
const operationName = getOperationName(operation);
|
|
1677
|
+
if (!operationName) return 'unknownOperation';
|
|
1678
|
+
|
|
1679
|
+
// Convert to camelCase for TypeScript
|
|
1680
|
+
return operationName.split(/[-_\s]+/).map((part, index) => {
|
|
1681
|
+
if (index === 0) {
|
|
1682
|
+
return part.toLowerCase();
|
|
1683
|
+
}
|
|
1684
|
+
return part.charAt(0).toUpperCase() + part.slice(1).toLowerCase();
|
|
1685
|
+
}).join('');
|
|
1686
|
+
}
|
|
1687
|
+
|
|
1688
|
+
/**
|
|
1689
|
+
* Gets the operation description
|
|
1690
|
+
*
|
|
1691
|
+
* @param {object} operation - AsyncAPI operation object
|
|
1692
|
+
* @returns {string} Operation description
|
|
1693
|
+
*/
|
|
1694
|
+
function getOperationDescription(operation) {
|
|
1695
|
+
if (!operation) return '';
|
|
1696
|
+
try {
|
|
1697
|
+
if (operation.description && typeof operation.description === 'function') {
|
|
1698
|
+
return operation.description() || '';
|
|
1699
|
+
}
|
|
1700
|
+
if (operation.description && typeof operation.description === 'string') {
|
|
1701
|
+
return operation.description;
|
|
1702
|
+
}
|
|
1703
|
+
if (operation._json && operation._json.description) {
|
|
1704
|
+
return operation._json.description;
|
|
1705
|
+
}
|
|
1706
|
+
return '';
|
|
1707
|
+
} catch (e) {
|
|
1708
|
+
return '';
|
|
1709
|
+
}
|
|
1710
|
+
}
|
|
1711
|
+
|
|
1712
|
+
/**
|
|
1713
|
+
* Gets the operation summary
|
|
1714
|
+
*
|
|
1715
|
+
* @param {object} operation - AsyncAPI operation object
|
|
1716
|
+
* @returns {string} Operation summary
|
|
1717
|
+
*/
|
|
1718
|
+
function getOperationSummary(operation) {
|
|
1719
|
+
if (!operation) return '';
|
|
1720
|
+
try {
|
|
1721
|
+
if (operation.summary && typeof operation.summary === 'function') {
|
|
1722
|
+
return operation.summary() || '';
|
|
1723
|
+
}
|
|
1724
|
+
if (operation.summary && typeof operation.summary === 'string') {
|
|
1725
|
+
return operation.summary;
|
|
1726
|
+
}
|
|
1727
|
+
if (operation._json && operation._json.summary) {
|
|
1728
|
+
return operation._json.summary;
|
|
1729
|
+
}
|
|
1730
|
+
return '';
|
|
1731
|
+
} catch (e) {
|
|
1732
|
+
return '';
|
|
1733
|
+
}
|
|
1734
|
+
}
|
|
1735
|
+
|
|
1736
|
+
/**
|
|
1737
|
+
* Extracts all operations from an AsyncAPI specification
|
|
1738
|
+
*
|
|
1739
|
+
* @param {object} asyncapi - AsyncAPI specification object
|
|
1740
|
+
* @returns {Array} Array of operation objects with metadata
|
|
1741
|
+
*/
|
|
1742
|
+
function extractAllOperations(asyncapi) {
|
|
1743
|
+
const operations = [];
|
|
1744
|
+
try {
|
|
1745
|
+
const asyncApiOperations = asyncapi.operations && asyncapi.operations();
|
|
1746
|
+
if (asyncApiOperations) {
|
|
1747
|
+
// Handle AsyncAPI parser collection - use .all() method to get array
|
|
1748
|
+
const operationArray = asyncApiOperations.all ? asyncApiOperations.all() : Object.values(asyncApiOperations);
|
|
1749
|
+
operationArray.forEach(operation => {
|
|
1750
|
+
const operationName = getOperationName(operation);
|
|
1751
|
+
if (operationName) {
|
|
1752
|
+
const channel = getOperationChannel(operation);
|
|
1753
|
+
const messages = getOperationMessages(operation);
|
|
1754
|
+
operations.push({
|
|
1755
|
+
name: operationName,
|
|
1756
|
+
operation: operation,
|
|
1757
|
+
action: getOperationAction(operation),
|
|
1758
|
+
channel: channel,
|
|
1759
|
+
messages: messages,
|
|
1760
|
+
description: getOperationDescription(operation),
|
|
1761
|
+
summary: getOperationSummary(operation),
|
|
1762
|
+
rustFunctionName: getOperationRustFunctionName(operation),
|
|
1763
|
+
typeScriptMethodName: getOperationTypeScriptMethodName(operation),
|
|
1764
|
+
isSend: isOperationSend(operation),
|
|
1765
|
+
isReceive: isOperationReceive(operation)
|
|
1766
|
+
});
|
|
1767
|
+
}
|
|
1768
|
+
});
|
|
1769
|
+
}
|
|
1770
|
+
} catch (e) {
|
|
1771
|
+
console.warn('Error extracting operations:', e.message);
|
|
1772
|
+
}
|
|
1773
|
+
return operations;
|
|
1774
|
+
}
|
|
1775
|
+
|
|
1776
|
+
/**
|
|
1777
|
+
* Groups operations by their action type
|
|
1778
|
+
*
|
|
1779
|
+
* @param {Array} operations - Array of operation objects
|
|
1780
|
+
* @returns {object} Object with send and receive operation arrays
|
|
1781
|
+
*/
|
|
1782
|
+
function groupOperationsByAction(operations) {
|
|
1783
|
+
const grouped = {
|
|
1784
|
+
send: [],
|
|
1785
|
+
receive: [],
|
|
1786
|
+
publish: [],
|
|
1787
|
+
subscribe: [],
|
|
1788
|
+
unknown: []
|
|
1789
|
+
};
|
|
1790
|
+
operations.forEach(operation => {
|
|
1791
|
+
const action = operation.action || 'unknown';
|
|
1792
|
+
if (grouped[action]) {
|
|
1793
|
+
grouped[action].push(operation);
|
|
1794
|
+
} else {
|
|
1795
|
+
grouped.unknown.push(operation);
|
|
1796
|
+
}
|
|
1797
|
+
});
|
|
1798
|
+
return grouped;
|
|
1799
|
+
}
|
|
1800
|
+
|
|
1801
|
+
/**
|
|
1802
|
+
* Gets the operation trait information
|
|
1803
|
+
*
|
|
1804
|
+
* @param {object} operation - AsyncAPI operation object
|
|
1805
|
+
* @returns {Array} Array of trait objects
|
|
1806
|
+
*/
|
|
1807
|
+
function getOperationTraits(operation) {
|
|
1808
|
+
if (!operation) return [];
|
|
1809
|
+
try {
|
|
1810
|
+
const traits = [];
|
|
1811
|
+
if (operation.traits && typeof operation.traits === 'function') {
|
|
1812
|
+
const operationTraits = operation.traits();
|
|
1813
|
+
if (operationTraits && Array.isArray(operationTraits)) {
|
|
1814
|
+
traits.push(...operationTraits);
|
|
1815
|
+
}
|
|
1816
|
+
} else if (operation.traits && Array.isArray(operation.traits)) {
|
|
1817
|
+
traits.push(...operation.traits);
|
|
1818
|
+
}
|
|
1819
|
+
|
|
1820
|
+
// Try to get traits from _json
|
|
1821
|
+
if (traits.length === 0 && operation._json && operation._json.traits) {
|
|
1822
|
+
if (Array.isArray(operation._json.traits)) {
|
|
1823
|
+
traits.push(...operation._json.traits);
|
|
1824
|
+
}
|
|
1825
|
+
}
|
|
1826
|
+
return traits;
|
|
1827
|
+
} catch (e) {
|
|
1828
|
+
return [];
|
|
1829
|
+
}
|
|
1830
|
+
}
|
|
1831
|
+
|
|
1832
|
+
/**
|
|
1833
|
+
* Checks if an operation has any traits defined
|
|
1834
|
+
*
|
|
1835
|
+
* @param {object} operation - AsyncAPI operation object
|
|
1836
|
+
* @returns {boolean} True if operation has traits
|
|
1837
|
+
*/
|
|
1838
|
+
function operationHasTraits(operation) {
|
|
1839
|
+
const traits = getOperationTraits(operation);
|
|
1840
|
+
return traits.length > 0;
|
|
1841
|
+
}
|
|
1842
|
+
|
|
1843
|
+
/**
|
|
1844
|
+
* Gets the operation tags
|
|
1845
|
+
*
|
|
1846
|
+
* @param {object} operation - AsyncAPI operation object
|
|
1847
|
+
* @returns {Array} Array of tag objects
|
|
1848
|
+
*/
|
|
1849
|
+
function getOperationTags(operation) {
|
|
1850
|
+
if (!operation) return [];
|
|
1851
|
+
try {
|
|
1852
|
+
const tags = [];
|
|
1853
|
+
if (operation.tags && typeof operation.tags === 'function') {
|
|
1854
|
+
const operationTags = operation.tags();
|
|
1855
|
+
if (operationTags && Array.isArray(operationTags)) {
|
|
1856
|
+
tags.push(...operationTags);
|
|
1857
|
+
}
|
|
1858
|
+
} else if (operation.tags && Array.isArray(operation.tags)) {
|
|
1859
|
+
tags.push(...operation.tags);
|
|
1860
|
+
}
|
|
1861
|
+
|
|
1862
|
+
// Try to get tags from _json
|
|
1863
|
+
if (tags.length === 0 && operation._json && operation._json.tags) {
|
|
1864
|
+
if (Array.isArray(operation._json.tags)) {
|
|
1865
|
+
tags.push(...operation._json.tags);
|
|
1866
|
+
}
|
|
1867
|
+
}
|
|
1868
|
+
return tags;
|
|
1869
|
+
} catch (e) {
|
|
1870
|
+
return [];
|
|
1871
|
+
}
|
|
1872
|
+
}
|
|
1873
|
+
|
|
1874
|
+
/**
|
|
1875
|
+
* Generates operation handler name for server templates
|
|
1876
|
+
*
|
|
1877
|
+
* @param {object} operation - AsyncAPI operation object
|
|
1878
|
+
* @param {string} suffix - Optional suffix to add to handler name
|
|
1879
|
+
* @returns {string} Handler function name
|
|
1880
|
+
*/
|
|
1881
|
+
function generateOperationHandlerName(operation, suffix = 'Handler') {
|
|
1882
|
+
const operationName = getOperationName(operation);
|
|
1883
|
+
if (!operationName) return `unknown${suffix}`;
|
|
1884
|
+
const pascalCaseName = toPascalCase(operationName);
|
|
1885
|
+
return `${pascalCaseName}${suffix}`;
|
|
1886
|
+
}
|
|
1887
|
+
;// external "@asyncapi/generator-react-sdk"
|
|
1888
|
+
|
|
1889
|
+
;// ../common/src/models-rust.js
|
|
1890
|
+
/* eslint-disable no-unused-vars */
|
|
1891
|
+
|
|
1892
|
+
|
|
1893
|
+
/**
|
|
1894
|
+
* Generate Rust models from AsyncAPI specification
|
|
1895
|
+
* This helper extracts the common schema processing logic used by both rust-server and rust-client templates
|
|
1896
|
+
*
|
|
1897
|
+
* @param {Object} asyncapi - AsyncAPI document
|
|
1898
|
+
* @param {Object} options - Generation options
|
|
1899
|
+
* @param {Function} options.toRustTypeName - Function to convert names to Rust type names
|
|
1900
|
+
* @param {Function} options.toRustFieldName - Function to convert names to Rust field names
|
|
1901
|
+
* @param {Function} options.toRustEnumVariantWithSerde - Function to convert enum variants with serde attributes
|
|
1902
|
+
* @param {boolean} options.includeAsyncApiTrait - Whether to include AsyncApiMessage trait implementations
|
|
1903
|
+
* @param {boolean} options.includeEnvelope - Whether to include MessageEnvelope (from envelope-rust.js)
|
|
1904
|
+
* @returns {Object} Generated models data and functions
|
|
1905
|
+
*/
|
|
1906
|
+
function generateRustModels(asyncapi, options = {}) {
|
|
1907
|
+
const {
|
|
1908
|
+
toRustTypeName,
|
|
1909
|
+
toRustFieldName,
|
|
1910
|
+
toRustEnumVariantWithSerde,
|
|
1911
|
+
includeAsyncApiTrait = false,
|
|
1912
|
+
includeEnvelope = false
|
|
1913
|
+
} = options;
|
|
1914
|
+
|
|
1915
|
+
// Extract message schemas and build channel mapping
|
|
1916
|
+
const components = asyncapi.components();
|
|
1917
|
+
const messageSchemas = [];
|
|
1918
|
+
const componentSchemas = [];
|
|
1919
|
+
const messageToChannels = new Map();
|
|
1920
|
+
const generatedTypes = new Set();
|
|
1921
|
+
const nestedSchemas = new Map();
|
|
1922
|
+
const schemaRegistry = new Map();
|
|
1923
|
+
|
|
1924
|
+
// First, build channel to message mapping and extract inline message schemas
|
|
1925
|
+
if (asyncapi.channels) {
|
|
1926
|
+
const channels = asyncapi.channels();
|
|
1927
|
+
if (channels) {
|
|
1928
|
+
// Use proper iteration for AsyncAPI collection
|
|
1929
|
+
for (const channel of channels) {
|
|
1930
|
+
try {
|
|
1931
|
+
const channelName = channel.id();
|
|
1932
|
+
|
|
1933
|
+
// Handle AsyncAPI 3.x format - extract inline messages from channels
|
|
1934
|
+
if (channel.messages) {
|
|
1935
|
+
const messages = channel.messages();
|
|
1936
|
+
if (messages) {
|
|
1937
|
+
// Check if messages is an object with message names as keys
|
|
1938
|
+
if (typeof messages === 'object' && !Array.isArray(messages)) {
|
|
1939
|
+
// Iterate through message entries (messageName -> messageObject)
|
|
1940
|
+
Object.entries(messages).forEach(([messageName, message]) => {
|
|
1941
|
+
if (message && messageName) {
|
|
1942
|
+
let payload = null;
|
|
1943
|
+
let description = null;
|
|
1944
|
+
|
|
1945
|
+
// Get payload schema
|
|
1946
|
+
if (message.payload && typeof message.payload === 'function') {
|
|
1947
|
+
payload = message.payload();
|
|
1948
|
+
if (payload && payload.json && typeof payload.json === 'function') {
|
|
1949
|
+
payload = payload.json();
|
|
1950
|
+
}
|
|
1951
|
+
} else if (message.payload) {
|
|
1952
|
+
payload = message.payload;
|
|
1953
|
+
}
|
|
1954
|
+
|
|
1955
|
+
// Get description
|
|
1956
|
+
if (message.description && typeof message.description === 'function') {
|
|
1957
|
+
description = message.description();
|
|
1958
|
+
} else if (message.description) {
|
|
1959
|
+
description = message.description;
|
|
1960
|
+
}
|
|
1961
|
+
|
|
1962
|
+
// Add to channel mapping
|
|
1963
|
+
if (!messageToChannels.has(messageName)) {
|
|
1964
|
+
messageToChannels.set(messageName, []);
|
|
1965
|
+
}
|
|
1966
|
+
messageToChannels.get(messageName).push(channelName);
|
|
1967
|
+
|
|
1968
|
+
// Add to message schemas for inline messages
|
|
1969
|
+
messageSchemas.push({
|
|
1970
|
+
name: messageName,
|
|
1971
|
+
rustName: toRustTypeName(messageName),
|
|
1972
|
+
payload,
|
|
1973
|
+
rawPayload: payload,
|
|
1974
|
+
description,
|
|
1975
|
+
channels: [channelName]
|
|
1976
|
+
});
|
|
1977
|
+
}
|
|
1978
|
+
});
|
|
1979
|
+
} else {
|
|
1980
|
+
// Try iterating as a collection
|
|
1981
|
+
for (const message of messages) {
|
|
1982
|
+
if (message) {
|
|
1983
|
+
let messageName = null;
|
|
1984
|
+
let payload = null;
|
|
1985
|
+
let description = null;
|
|
1986
|
+
|
|
1987
|
+
// Get message name - try multiple approaches
|
|
1988
|
+
if (message._meta && message._meta.id) {
|
|
1989
|
+
messageName = message._meta.id;
|
|
1990
|
+
} else if (message._json && message._json['x-parser-message-name']) {
|
|
1991
|
+
messageName = message._json['x-parser-message-name'];
|
|
1992
|
+
} else if (message._json && message._json['x-parser-unique-object-id']) {
|
|
1993
|
+
messageName = message._json['x-parser-unique-object-id'];
|
|
1994
|
+
} else if (message.name && typeof message.name === 'function') {
|
|
1995
|
+
messageName = message.name();
|
|
1996
|
+
} else if (message.name) {
|
|
1997
|
+
messageName = message.name;
|
|
1998
|
+
} else if (message.$ref) {
|
|
1999
|
+
messageName = message.$ref.split('/').pop();
|
|
2000
|
+
}
|
|
2001
|
+
|
|
2002
|
+
// Get payload schema
|
|
2003
|
+
if (message.payload && typeof message.payload === 'function') {
|
|
2004
|
+
payload = message.payload();
|
|
2005
|
+
if (payload && payload.json && typeof payload.json === 'function') {
|
|
2006
|
+
payload = payload.json();
|
|
2007
|
+
}
|
|
2008
|
+
} else if (message.payload) {
|
|
2009
|
+
payload = message.payload;
|
|
2010
|
+
}
|
|
2011
|
+
|
|
2012
|
+
// Get description
|
|
2013
|
+
if (message.description && typeof message.description === 'function') {
|
|
2014
|
+
description = message.description();
|
|
2015
|
+
} else if (message.description) {
|
|
2016
|
+
description = message.description;
|
|
2017
|
+
}
|
|
2018
|
+
if (messageName) {
|
|
2019
|
+
// Add to channel mapping
|
|
2020
|
+
if (!messageToChannels.has(messageName)) {
|
|
2021
|
+
messageToChannels.set(messageName, []);
|
|
2022
|
+
}
|
|
2023
|
+
messageToChannels.get(messageName).push(channelName);
|
|
2024
|
+
|
|
2025
|
+
// Add to message schemas for inline messages
|
|
2026
|
+
messageSchemas.push({
|
|
2027
|
+
name: messageName,
|
|
2028
|
+
rustName: toRustTypeName(messageName),
|
|
2029
|
+
payload,
|
|
2030
|
+
rawPayload: payload,
|
|
2031
|
+
description,
|
|
2032
|
+
channels: [channelName]
|
|
2033
|
+
});
|
|
2034
|
+
}
|
|
2035
|
+
}
|
|
2036
|
+
}
|
|
2037
|
+
}
|
|
2038
|
+
}
|
|
2039
|
+
}
|
|
2040
|
+
|
|
2041
|
+
// Handle AsyncAPI 2.x format
|
|
2042
|
+
if (channel.subscribe && channel.subscribe()) {
|
|
2043
|
+
const message = channel.subscribe().message();
|
|
2044
|
+
if (message) {
|
|
2045
|
+
let messageName = null;
|
|
2046
|
+
if (message.$ref) {
|
|
2047
|
+
messageName = message.$ref.split('/').pop();
|
|
2048
|
+
} else if (message.name) {
|
|
2049
|
+
messageName = typeof message.name === 'function' ? message.name() : message.name;
|
|
2050
|
+
}
|
|
2051
|
+
if (messageName) {
|
|
2052
|
+
if (!messageToChannels.has(messageName)) {
|
|
2053
|
+
messageToChannels.set(messageName, []);
|
|
2054
|
+
}
|
|
2055
|
+
messageToChannels.get(messageName).push(channelName);
|
|
2056
|
+
}
|
|
2057
|
+
}
|
|
2058
|
+
}
|
|
2059
|
+
if (channel.publish && channel.publish()) {
|
|
2060
|
+
const message = channel.publish().message();
|
|
2061
|
+
if (message) {
|
|
2062
|
+
let messageName = null;
|
|
2063
|
+
if (message.$ref) {
|
|
2064
|
+
messageName = message.$ref.split('/').pop();
|
|
2065
|
+
} else if (message.name) {
|
|
2066
|
+
messageName = typeof message.name === 'function' ? message.name() : message.name;
|
|
2067
|
+
}
|
|
2068
|
+
if (messageName) {
|
|
2069
|
+
if (!messageToChannels.has(messageName)) {
|
|
2070
|
+
messageToChannels.set(messageName, []);
|
|
2071
|
+
}
|
|
2072
|
+
messageToChannels.get(messageName).push(channelName);
|
|
2073
|
+
}
|
|
2074
|
+
}
|
|
2075
|
+
}
|
|
2076
|
+
} catch (e) {
|
|
2077
|
+
// Ignore channel processing errors
|
|
2078
|
+
console.warn(`Error processing channel: ${e.message}`);
|
|
2079
|
+
}
|
|
2080
|
+
}
|
|
2081
|
+
}
|
|
2082
|
+
}
|
|
2083
|
+
|
|
2084
|
+
// Build schema registry from components.schemas
|
|
2085
|
+
// Try to access the raw AsyncAPI document
|
|
2086
|
+
let rawDoc = null;
|
|
2087
|
+
try {
|
|
2088
|
+
if (asyncapi.json && typeof asyncapi.json === 'function') {
|
|
2089
|
+
rawDoc = asyncapi.json();
|
|
2090
|
+
} else if (asyncapi._json) {
|
|
2091
|
+
rawDoc = asyncapi._json;
|
|
2092
|
+
}
|
|
2093
|
+
} catch (e) {
|
|
2094
|
+
// Ignore
|
|
2095
|
+
}
|
|
2096
|
+
|
|
2097
|
+
// Extract schemas from raw document if available
|
|
2098
|
+
if (rawDoc && rawDoc.components && rawDoc.components.schemas) {
|
|
2099
|
+
Object.entries(rawDoc.components.schemas).forEach(([name, schema]) => {
|
|
2100
|
+
if (name && typeof name === 'string' && schema && typeof schema === 'object') {
|
|
2101
|
+
schemaRegistry.set(name, schema);
|
|
2102
|
+
componentSchemas.push({
|
|
2103
|
+
name,
|
|
2104
|
+
rustName: toRustTypeName(name),
|
|
2105
|
+
schema: schema,
|
|
2106
|
+
description: schema.description
|
|
2107
|
+
});
|
|
2108
|
+
}
|
|
2109
|
+
});
|
|
2110
|
+
}
|
|
2111
|
+
|
|
2112
|
+
// Fallback: try the components.schemas() method
|
|
2113
|
+
if (componentSchemas.length === 0 && components && components.schemas) {
|
|
2114
|
+
try {
|
|
2115
|
+
const schemas = components.schemas();
|
|
2116
|
+
if (schemas) {
|
|
2117
|
+
// Try different ways to access schemas
|
|
2118
|
+
let schemaEntries = [];
|
|
2119
|
+
if (schemas instanceof Map) {
|
|
2120
|
+
schemaEntries = Array.from(schemas.entries());
|
|
2121
|
+
} else if (typeof schemas === 'object') {
|
|
2122
|
+
schemaEntries = Object.entries(schemas);
|
|
2123
|
+
} else if (schemas.all && typeof schemas.all === 'function') {
|
|
2124
|
+
// AsyncAPI parser might have an all() method
|
|
2125
|
+
const allSchemas = schemas.all();
|
|
2126
|
+
if (Array.isArray(allSchemas)) {
|
|
2127
|
+
schemaEntries = allSchemas.map(schema => {
|
|
2128
|
+
const name = schema.uid ? schema.uid() : schema.id ? schema.id() : null;
|
|
2129
|
+
return [name, schema];
|
|
2130
|
+
}).filter(([name]) => name);
|
|
2131
|
+
}
|
|
2132
|
+
}
|
|
2133
|
+
schemaEntries.forEach(([name, schema]) => {
|
|
2134
|
+
// Skip internal AsyncAPI parser objects and numeric keys
|
|
2135
|
+
if (!name || name === 'collections' || name === '_meta' || name.startsWith('_') || /^\d+$/.test(name)) {
|
|
2136
|
+
return;
|
|
2137
|
+
}
|
|
2138
|
+
let schemaData = null;
|
|
2139
|
+
let description = null;
|
|
2140
|
+
try {
|
|
2141
|
+
// Handle different schema object types
|
|
2142
|
+
if (schema && typeof schema.json === 'function') {
|
|
2143
|
+
schemaData = schema.json();
|
|
2144
|
+
} else if (schema && typeof schema === 'object') {
|
|
2145
|
+
schemaData = schema;
|
|
2146
|
+
}
|
|
2147
|
+
if (schema && typeof schema.description === 'function') {
|
|
2148
|
+
description = schema.description();
|
|
2149
|
+
} else if (schema && schema.description) {
|
|
2150
|
+
description = schema.description;
|
|
2151
|
+
}
|
|
2152
|
+
} catch (e) {
|
|
2153
|
+
// Ignore schema extraction errors
|
|
2154
|
+
console.warn(`Failed to extract schema for ${name}:`, e.message);
|
|
2155
|
+
}
|
|
2156
|
+
if (schemaData && typeof name === 'string' && name.length > 0) {
|
|
2157
|
+
schemaRegistry.set(name, schemaData);
|
|
2158
|
+
componentSchemas.push({
|
|
2159
|
+
name,
|
|
2160
|
+
rustName: toRustTypeName(name),
|
|
2161
|
+
schema: schemaData,
|
|
2162
|
+
description
|
|
2163
|
+
});
|
|
2164
|
+
}
|
|
2165
|
+
});
|
|
2166
|
+
}
|
|
2167
|
+
} catch (e) {
|
|
2168
|
+
console.warn('Failed to extract component schemas:', e.message);
|
|
2169
|
+
}
|
|
2170
|
+
}
|
|
2171
|
+
|
|
2172
|
+
// Extract messages from components
|
|
2173
|
+
if (components && components.messages) {
|
|
2174
|
+
const messages = components.messages();
|
|
2175
|
+
if (messages) {
|
|
2176
|
+
Object.entries(messages).forEach(([name, message]) => {
|
|
2177
|
+
let payload = null;
|
|
2178
|
+
let description = null;
|
|
2179
|
+
let title = null;
|
|
2180
|
+
let messageName = name;
|
|
2181
|
+
try {
|
|
2182
|
+
let rawPayload = null;
|
|
2183
|
+
if (message.payload && typeof message.payload === 'function') {
|
|
2184
|
+
const payloadSchema = message.payload();
|
|
2185
|
+
payload = payloadSchema && payloadSchema.json ? payloadSchema.json() : payloadSchema;
|
|
2186
|
+
// Try to get the raw payload reference from the message
|
|
2187
|
+
if (message._json && message._json.payload) {
|
|
2188
|
+
rawPayload = message._json.payload;
|
|
2189
|
+
}
|
|
2190
|
+
}
|
|
2191
|
+
description = message.description && typeof message.description === 'function' ? message.description() : null;
|
|
2192
|
+
title = message.title && typeof message.title === 'function' ? message.title() : null;
|
|
2193
|
+
|
|
2194
|
+
// Try to get the actual message name
|
|
2195
|
+
if (message.name && typeof message.name === 'function') {
|
|
2196
|
+
messageName = message.name();
|
|
2197
|
+
} else if (message.name) {
|
|
2198
|
+
messageName = message.name;
|
|
2199
|
+
}
|
|
2200
|
+
|
|
2201
|
+
// If we have raw document access, try to get the payload reference from there
|
|
2202
|
+
if (!rawPayload && rawDoc && rawDoc.components && rawDoc.components.messages && rawDoc.components.messages[name]) {
|
|
2203
|
+
rawPayload = rawDoc.components.messages[name].payload;
|
|
2204
|
+
}
|
|
2205
|
+
const channels = messageToChannels.get(messageName) || messageToChannels.get(name) || [];
|
|
2206
|
+
messageSchemas.push({
|
|
2207
|
+
name: messageName,
|
|
2208
|
+
rustName: toRustTypeName(messageName),
|
|
2209
|
+
payload,
|
|
2210
|
+
rawPayload,
|
|
2211
|
+
description: description || title,
|
|
2212
|
+
channels
|
|
2213
|
+
});
|
|
2214
|
+
} catch (e) {
|
|
2215
|
+
// Ignore payload extraction errors
|
|
2216
|
+
const channels = messageToChannels.get(messageName) || messageToChannels.get(name) || [];
|
|
2217
|
+
messageSchemas.push({
|
|
2218
|
+
name: messageName,
|
|
2219
|
+
rustName: toRustTypeName(messageName),
|
|
2220
|
+
payload,
|
|
2221
|
+
rawPayload: null,
|
|
2222
|
+
description: description || title,
|
|
2223
|
+
channels
|
|
2224
|
+
});
|
|
2225
|
+
}
|
|
2226
|
+
});
|
|
2227
|
+
}
|
|
2228
|
+
}
|
|
2229
|
+
|
|
2230
|
+
// Helper function to convert JSON schema to Rust type
|
|
2231
|
+
function jsonSchemaToRustType(schema, typeName = null) {
|
|
2232
|
+
if (!schema) return 'serde_json::Value';
|
|
2233
|
+
|
|
2234
|
+
// Handle $ref
|
|
2235
|
+
if (schema.$ref) {
|
|
2236
|
+
const refName = schema.$ref.split('/').pop();
|
|
2237
|
+
const rustTypeName = toRustTypeName(refName);
|
|
2238
|
+
|
|
2239
|
+
// Generate the referenced schema if we have access to components
|
|
2240
|
+
if (components && components.schemas) {
|
|
2241
|
+
const schemas = components.schemas();
|
|
2242
|
+
if (schemas && schemas[refName] && !generatedTypes.has(rustTypeName)) {
|
|
2243
|
+
generatedTypes.add(rustTypeName);
|
|
2244
|
+
const referencedSchema = schemas[refName];
|
|
2245
|
+
const schemaJson = referencedSchema.json ? referencedSchema.json() : referencedSchema;
|
|
2246
|
+
nestedSchemas.set(rustTypeName, {
|
|
2247
|
+
type: 'struct',
|
|
2248
|
+
schema: schemaJson,
|
|
2249
|
+
description: referencedSchema.description && typeof referencedSchema.description === 'function' ? referencedSchema.description() : null
|
|
2250
|
+
});
|
|
2251
|
+
}
|
|
2252
|
+
}
|
|
2253
|
+
return rustTypeName;
|
|
2254
|
+
}
|
|
2255
|
+
|
|
2256
|
+
// Handle resolved $ref - check for x-parser-schema-id which indicates original schema name
|
|
2257
|
+
if (schema['x-parser-schema-id'] && typeof schema['x-parser-schema-id'] === 'string') {
|
|
2258
|
+
const schemaId = schema['x-parser-schema-id'];
|
|
2259
|
+
// Check if this matches a known component schema
|
|
2260
|
+
if (schemaRegistry.has(schemaId)) {
|
|
2261
|
+
const rustTypeName = toRustTypeName(schemaId);
|
|
2262
|
+
return rustTypeName;
|
|
2263
|
+
}
|
|
2264
|
+
}
|
|
2265
|
+
if (!schema.type) {
|
|
2266
|
+
// If no type specified, check for properties (object) or items (array)
|
|
2267
|
+
if (schema.properties) {
|
|
2268
|
+
schema.type = 'object';
|
|
2269
|
+
} else if (schema.items) {
|
|
2270
|
+
schema.type = 'array';
|
|
2271
|
+
} else {
|
|
2272
|
+
return 'serde_json::Value';
|
|
2273
|
+
}
|
|
2274
|
+
}
|
|
2275
|
+
switch (schema.type) {
|
|
2276
|
+
case 'string':
|
|
2277
|
+
if (schema.enum && schema.enum.length > 0) {
|
|
2278
|
+
// Generate enum type
|
|
2279
|
+
if (typeName) {
|
|
2280
|
+
const enumName = `${typeName}Enum`;
|
|
2281
|
+
if (!generatedTypes.has(enumName)) {
|
|
2282
|
+
generatedTypes.add(enumName);
|
|
2283
|
+
nestedSchemas.set(enumName, {
|
|
2284
|
+
type: 'enum',
|
|
2285
|
+
variants: schema.enum,
|
|
2286
|
+
description: schema.description
|
|
2287
|
+
});
|
|
2288
|
+
}
|
|
2289
|
+
return enumName;
|
|
2290
|
+
}
|
|
2291
|
+
return 'String'; // Fallback if no type name provided
|
|
2292
|
+
}
|
|
2293
|
+
if (schema.format === 'date-time') return 'chrono::DateTime<chrono::Utc>';
|
|
2294
|
+
if (schema.format === 'uuid') return 'uuid::Uuid';
|
|
2295
|
+
if (schema.format === 'email') return 'String';
|
|
2296
|
+
if (schema.format === 'uri') return 'String';
|
|
2297
|
+
return 'String';
|
|
2298
|
+
case 'integer':
|
|
2299
|
+
switch (schema.format) {
|
|
2300
|
+
case 'int32':
|
|
2301
|
+
return 'i32';
|
|
2302
|
+
case 'int64':
|
|
2303
|
+
return 'i64';
|
|
2304
|
+
case 'uint32':
|
|
2305
|
+
return 'u32';
|
|
2306
|
+
case 'uint64':
|
|
2307
|
+
return 'u64';
|
|
2308
|
+
case 'uint16':
|
|
2309
|
+
return 'u16';
|
|
2310
|
+
case 'uint8':
|
|
2311
|
+
return 'u8';
|
|
2312
|
+
case 'int16':
|
|
2313
|
+
return 'i16';
|
|
2314
|
+
case 'int8':
|
|
2315
|
+
return 'i8';
|
|
2316
|
+
default:
|
|
2317
|
+
// Default to i32 for unspecified format (maintains backward compatibility)
|
|
2318
|
+
return 'i32';
|
|
2319
|
+
}
|
|
2320
|
+
case 'number':
|
|
2321
|
+
return 'f64';
|
|
2322
|
+
case 'boolean':
|
|
2323
|
+
return 'bool';
|
|
2324
|
+
case 'array':
|
|
2325
|
+
{
|
|
2326
|
+
const itemType = jsonSchemaToRustType(schema.items);
|
|
2327
|
+
return `Vec<${itemType}>`;
|
|
2328
|
+
}
|
|
2329
|
+
case 'object':
|
|
2330
|
+
if (schema.properties && Object.keys(schema.properties).length > 0) {
|
|
2331
|
+
// Generate nested struct
|
|
2332
|
+
if (typeName) {
|
|
2333
|
+
const structName = toRustTypeName(typeName);
|
|
2334
|
+
if (!generatedTypes.has(structName)) {
|
|
2335
|
+
generatedTypes.add(structName);
|
|
2336
|
+
nestedSchemas.set(structName, {
|
|
2337
|
+
type: 'struct',
|
|
2338
|
+
schema: schema,
|
|
2339
|
+
description: schema.description
|
|
2340
|
+
});
|
|
2341
|
+
}
|
|
2342
|
+
return structName;
|
|
2343
|
+
}
|
|
2344
|
+
}
|
|
2345
|
+
return 'serde_json::Value';
|
|
2346
|
+
default:
|
|
2347
|
+
return 'serde_json::Value';
|
|
2348
|
+
}
|
|
2349
|
+
}
|
|
2350
|
+
|
|
2351
|
+
// Generate message structs
|
|
2352
|
+
function generateMessageStruct(schema, messageName) {
|
|
2353
|
+
if (!schema || !schema.properties) {
|
|
2354
|
+
return ' pub data: serde_json::Value,';
|
|
2355
|
+
}
|
|
2356
|
+
const fields = Object.entries(schema.properties).map(([fieldName, fieldSchema]) => {
|
|
2357
|
+
const rustFieldName = toRustFieldName(fieldName);
|
|
2358
|
+
const fieldTypeName = `${messageName}${toRustTypeName(fieldName)}`;
|
|
2359
|
+
const rustType = jsonSchemaToRustType(fieldSchema, fieldTypeName);
|
|
2360
|
+
const requiredFields = schema.required;
|
|
2361
|
+
const optional = !requiredFields || !Array.isArray(requiredFields) || requiredFields.indexOf(fieldName) === -1;
|
|
2362
|
+
const finalType = optional ? `Option<${rustType}>` : rustType;
|
|
2363
|
+
let fieldDoc = '';
|
|
2364
|
+
if (fieldSchema.description) {
|
|
2365
|
+
fieldDoc = ` /// ${fieldSchema.description}\n`;
|
|
2366
|
+
}
|
|
2367
|
+
let serdeRename = '';
|
|
2368
|
+
if (rustFieldName !== fieldName) {
|
|
2369
|
+
serdeRename = ` #[serde(rename = "${fieldName}")]\n`;
|
|
2370
|
+
}
|
|
2371
|
+
let skipSerializing = '';
|
|
2372
|
+
if (optional) {
|
|
2373
|
+
skipSerializing = ' #[serde(skip_serializing_if = "Option::is_none")]\n';
|
|
2374
|
+
}
|
|
2375
|
+
return `${fieldDoc}${serdeRename}${skipSerializing} pub ${rustFieldName}: ${finalType},`;
|
|
2376
|
+
}).join('\n');
|
|
2377
|
+
return fields;
|
|
2378
|
+
}
|
|
2379
|
+
|
|
2380
|
+
// Process all component schemas first to ensure they are available for references
|
|
2381
|
+
componentSchemas.forEach(schema => {
|
|
2382
|
+
jsonSchemaToRustType(schema.schema, schema.rustName);
|
|
2383
|
+
generatedTypes.add(schema.rustName);
|
|
2384
|
+
});
|
|
2385
|
+
|
|
2386
|
+
// Process all message schemas to ensure all referenced types are generated
|
|
2387
|
+
messageSchemas.forEach(schema => {
|
|
2388
|
+
if (schema.payload) {
|
|
2389
|
+
jsonSchemaToRustType(schema.payload, schema.rustName);
|
|
2390
|
+
}
|
|
2391
|
+
});
|
|
2392
|
+
|
|
2393
|
+
// Return the processed data and generation functions
|
|
2394
|
+
return {
|
|
2395
|
+
messageSchemas,
|
|
2396
|
+
componentSchemas,
|
|
2397
|
+
messageToChannels,
|
|
2398
|
+
generatedTypes,
|
|
2399
|
+
nestedSchemas,
|
|
2400
|
+
schemaRegistry,
|
|
2401
|
+
// Generation functions
|
|
2402
|
+
generateMessageStruct,
|
|
2403
|
+
jsonSchemaToRustType,
|
|
2404
|
+
// Generate component schema definitions
|
|
2405
|
+
generateComponentSchemas() {
|
|
2406
|
+
let result = '';
|
|
2407
|
+
componentSchemas.forEach(schema => {
|
|
2408
|
+
const doc = schema.description ? `/// ${schema.description}\n` : `/// ${schema.name}\n`;
|
|
2409
|
+
|
|
2410
|
+
// Check if this is a standalone enum schema
|
|
2411
|
+
if (schema.schema.type === 'string' && schema.schema.enum && Array.isArray(schema.schema.enum)) {
|
|
2412
|
+
// Generate enum definition with serde rename attributes for lowercase serialization
|
|
2413
|
+
const variants = schema.schema.enum.map(variant => {
|
|
2414
|
+
const {
|
|
2415
|
+
rustName,
|
|
2416
|
+
serializedName
|
|
2417
|
+
} = toRustEnumVariantWithSerde(variant);
|
|
2418
|
+
return ` #[serde(rename = "${serializedName}")]\n ${rustName}`;
|
|
2419
|
+
}).join(',\n');
|
|
2420
|
+
result += `
|
|
2421
|
+
${doc}#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
|
2422
|
+
pub enum ${schema.rustName} {
|
|
2423
|
+
${variants},
|
|
2424
|
+
}
|
|
2425
|
+
`;
|
|
2426
|
+
} else if (schema.schema.type && !schema.schema.properties) {
|
|
2427
|
+
// Handle primitive types and arrays without properties
|
|
2428
|
+
// Don't use jsonSchemaToRustType here as it might return the type name itself
|
|
2429
|
+
let primitiveType = 'serde_json::Value';
|
|
2430
|
+
switch (schema.schema.type) {
|
|
2431
|
+
case 'string':
|
|
2432
|
+
if (schema.schema.format === 'date-time') primitiveType = 'chrono::DateTime<chrono::Utc>';else if (schema.schema.format === 'uuid') primitiveType = 'uuid::Uuid';else primitiveType = 'String';
|
|
2433
|
+
break;
|
|
2434
|
+
case 'integer':
|
|
2435
|
+
switch (schema.schema.format) {
|
|
2436
|
+
case 'int32':
|
|
2437
|
+
primitiveType = 'i32';
|
|
2438
|
+
break;
|
|
2439
|
+
case 'int64':
|
|
2440
|
+
primitiveType = 'i64';
|
|
2441
|
+
break;
|
|
2442
|
+
case 'uint32':
|
|
2443
|
+
primitiveType = 'u32';
|
|
2444
|
+
break;
|
|
2445
|
+
case 'uint64':
|
|
2446
|
+
primitiveType = 'u64';
|
|
2447
|
+
break;
|
|
2448
|
+
default:
|
|
2449
|
+
primitiveType = 'i32';
|
|
2450
|
+
break;
|
|
2451
|
+
}
|
|
2452
|
+
break;
|
|
2453
|
+
case 'number':
|
|
2454
|
+
primitiveType = 'f64';
|
|
2455
|
+
break;
|
|
2456
|
+
case 'boolean':
|
|
2457
|
+
primitiveType = 'bool';
|
|
2458
|
+
break;
|
|
2459
|
+
case 'array':
|
|
2460
|
+
if (schema.schema.items) {
|
|
2461
|
+
// Handle array items
|
|
2462
|
+
let itemType = 'serde_json::Value';
|
|
2463
|
+
if (schema.schema.items.type === 'integer') {
|
|
2464
|
+
switch (schema.schema.items.format) {
|
|
2465
|
+
case 'int32':
|
|
2466
|
+
itemType = 'i32';
|
|
2467
|
+
break;
|
|
2468
|
+
case 'int64':
|
|
2469
|
+
itemType = 'i64';
|
|
2470
|
+
break;
|
|
2471
|
+
case 'uint32':
|
|
2472
|
+
itemType = 'u32';
|
|
2473
|
+
break;
|
|
2474
|
+
case 'uint64':
|
|
2475
|
+
itemType = 'u64';
|
|
2476
|
+
break;
|
|
2477
|
+
case 'uint8':
|
|
2478
|
+
itemType = 'u8';
|
|
2479
|
+
break;
|
|
2480
|
+
case 'int8':
|
|
2481
|
+
itemType = 'i8';
|
|
2482
|
+
break;
|
|
2483
|
+
case 'uint16':
|
|
2484
|
+
itemType = 'u16';
|
|
2485
|
+
break;
|
|
2486
|
+
case 'int16':
|
|
2487
|
+
itemType = 'i16';
|
|
2488
|
+
break;
|
|
2489
|
+
default:
|
|
2490
|
+
itemType = 'i32';
|
|
2491
|
+
break;
|
|
2492
|
+
}
|
|
2493
|
+
} else if (schema.schema.items.type === 'string') {
|
|
2494
|
+
if (schema.schema.items.format === 'date-time') itemType = 'chrono::DateTime<chrono::Utc>';else if (schema.schema.items.format === 'uuid') itemType = 'uuid::Uuid';else itemType = 'String';
|
|
2495
|
+
} else if (schema.schema.items.type === 'number') {
|
|
2496
|
+
itemType = 'f64';
|
|
2497
|
+
} else if (schema.schema.items.type === 'boolean') {
|
|
2498
|
+
itemType = 'bool';
|
|
2499
|
+
}
|
|
2500
|
+
primitiveType = `Vec<${itemType}>`;
|
|
2501
|
+
} else {
|
|
2502
|
+
primitiveType = 'Vec<serde_json::Value>';
|
|
2503
|
+
}
|
|
2504
|
+
break;
|
|
2505
|
+
}
|
|
2506
|
+
|
|
2507
|
+
// Generate a newtype wrapper for primitive types and arrays
|
|
2508
|
+
result += `
|
|
2509
|
+
${doc}#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
2510
|
+
pub struct ${schema.rustName}(pub ${primitiveType});
|
|
2511
|
+
|
|
2512
|
+
impl From<${primitiveType}> for ${schema.rustName} {
|
|
2513
|
+
fn from(value: ${primitiveType}) -> Self {
|
|
2514
|
+
Self(value)
|
|
2515
|
+
}
|
|
2516
|
+
}
|
|
2517
|
+
|
|
2518
|
+
impl From<${schema.rustName}> for ${primitiveType} {
|
|
2519
|
+
fn from(value: ${schema.rustName}) -> Self {
|
|
2520
|
+
value.0
|
|
2521
|
+
}
|
|
2522
|
+
}
|
|
2523
|
+
|
|
2524
|
+
impl std::fmt::Display for ${schema.rustName} {
|
|
2525
|
+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
2526
|
+
${(() => {
|
|
2527
|
+
// Generate appropriate Display implementation based on the primitive type
|
|
2528
|
+
if (primitiveType.startsWith('Vec<u8>')) {
|
|
2529
|
+
return `self.0.iter().try_for_each(|byte| write!(f, "{:02x}", byte))`;
|
|
2530
|
+
} else if (primitiveType.startsWith('Vec<')) {
|
|
2531
|
+
return 'write!(f, "{:?}", self.0)';
|
|
2532
|
+
} else {
|
|
2533
|
+
return 'write!(f, "{}", self.0)';
|
|
2534
|
+
}
|
|
2535
|
+
})()}
|
|
2536
|
+
}
|
|
2537
|
+
}
|
|
2538
|
+
`;
|
|
2539
|
+
} else {
|
|
2540
|
+
// Generate struct definition
|
|
2541
|
+
const fields = generateMessageStruct(schema.schema, schema.rustName);
|
|
2542
|
+
result += `
|
|
2543
|
+
${doc}#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
2544
|
+
pub struct ${schema.rustName} {
|
|
2545
|
+
${fields}
|
|
2546
|
+
}
|
|
2547
|
+
`;
|
|
2548
|
+
}
|
|
2549
|
+
});
|
|
2550
|
+
return result;
|
|
2551
|
+
},
|
|
2552
|
+
// Generate nested type definitions
|
|
2553
|
+
generateNestedTypes() {
|
|
2554
|
+
let result = '';
|
|
2555
|
+
for (const [typeName, typeInfo] of nestedSchemas.entries()) {
|
|
2556
|
+
// Don't skip enums - they need to be generated even if the parent type exists
|
|
2557
|
+
const isEnum = typeInfo.type === 'enum';
|
|
2558
|
+
const isComponentSchema = componentSchemas.some(cs => cs.rustName === typeName);
|
|
2559
|
+
|
|
2560
|
+
// Skip if this type was already generated as a component schema (but not enums)
|
|
2561
|
+
if (!isEnum && isComponentSchema) {
|
|
2562
|
+
continue;
|
|
2563
|
+
}
|
|
2564
|
+
if (typeInfo.type === 'enum') {
|
|
2565
|
+
const variants = typeInfo.variants.map(variant => {
|
|
2566
|
+
const {
|
|
2567
|
+
rustName,
|
|
2568
|
+
serializedName
|
|
2569
|
+
} = toRustEnumVariantWithSerde(variant);
|
|
2570
|
+
return ` #[serde(rename = "${serializedName}")]\n ${rustName}`;
|
|
2571
|
+
}).join(',\n');
|
|
2572
|
+
const doc = typeInfo.description ? `/// ${typeInfo.description}\n` : '';
|
|
2573
|
+
result += `
|
|
2574
|
+
${doc}#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
|
2575
|
+
pub enum ${typeName} {
|
|
2576
|
+
${variants},
|
|
2577
|
+
}
|
|
2578
|
+
`;
|
|
2579
|
+
} else if (typeInfo.type === 'struct') {
|
|
2580
|
+
const fields = generateMessageStruct(typeInfo.schema, typeName);
|
|
2581
|
+
const doc = typeInfo.description ? `/// ${typeInfo.description}\n` : '';
|
|
2582
|
+
result += `
|
|
2583
|
+
${doc}#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
2584
|
+
pub struct ${typeName} {
|
|
2585
|
+
${fields}
|
|
2586
|
+
}
|
|
2587
|
+
`;
|
|
2588
|
+
}
|
|
2589
|
+
}
|
|
2590
|
+
return result;
|
|
2591
|
+
},
|
|
2592
|
+
// Generate AsyncApiMessage trait implementations (optional)
|
|
2593
|
+
generateAsyncApiTrait() {
|
|
2594
|
+
if (!includeAsyncApiTrait) {
|
|
2595
|
+
return '';
|
|
2596
|
+
}
|
|
2597
|
+
|
|
2598
|
+
// Track which types have already had AsyncApiMessage implementations generated
|
|
2599
|
+
const implementedTypes = new Set();
|
|
2600
|
+
const implementations = [];
|
|
2601
|
+
|
|
2602
|
+
// First add the trait definition
|
|
2603
|
+
implementations.push(`
|
|
2604
|
+
/// Base trait for all AsyncAPI messages providing runtime type information
|
|
2605
|
+
///
|
|
2606
|
+
/// This trait enables:
|
|
2607
|
+
/// - **Dynamic message routing**: Route messages based on their type at runtime
|
|
2608
|
+
/// - **Channel identification**: Determine which channel a message belongs to
|
|
2609
|
+
/// - **Logging and monitoring**: Track message types for observability
|
|
2610
|
+
/// - **Protocol abstraction**: Handle different message types uniformly
|
|
2611
|
+
pub trait AsyncApiMessage {
|
|
2612
|
+
/// Returns the message type identifier as defined in the AsyncAPI specification
|
|
2613
|
+
///
|
|
2614
|
+
/// This is used for:
|
|
2615
|
+
/// - Message routing and dispatch
|
|
2616
|
+
/// - Logging and monitoring
|
|
2617
|
+
/// - Protocol-level message identification
|
|
2618
|
+
fn message_type(&self) -> &'static str;
|
|
2619
|
+
|
|
2620
|
+
/// Returns the primary channel this message is associated with
|
|
2621
|
+
///
|
|
2622
|
+
/// Used for:
|
|
2623
|
+
/// - Default routing when channel is not explicitly specified
|
|
2624
|
+
/// - Message categorization and organization
|
|
2625
|
+
/// - Channel-based access control and filtering
|
|
2626
|
+
fn channel(&self) -> &'static str;
|
|
2627
|
+
}`);
|
|
2628
|
+
messageSchemas.forEach(schema => {
|
|
2629
|
+
const doc = schema.description ? `/// ${schema.description}` : `/// ${schema.name} message`;
|
|
2630
|
+
const primaryChannel = schema.channels.length > 0 ? schema.channels[0] : 'default';
|
|
2631
|
+
|
|
2632
|
+
// Check if the message payload references a component schema
|
|
2633
|
+
let payloadRustName = null;
|
|
2634
|
+
let isComponentMessage = false;
|
|
2635
|
+
if (schema.rawPayload && schema.rawPayload.$ref) {
|
|
2636
|
+
const refName = schema.rawPayload.$ref.split('/').pop();
|
|
2637
|
+
payloadRustName = toRustTypeName(refName);
|
|
2638
|
+
isComponentMessage = true;
|
|
2639
|
+
} else if (schema.payload && schema.payload.$ref) {
|
|
2640
|
+
const refName = schema.payload.$ref.split('/').pop();
|
|
2641
|
+
payloadRustName = toRustTypeName(refName);
|
|
2642
|
+
isComponentMessage = true;
|
|
2643
|
+
} else if (schema.payload && schema.payload['x-parser-schema-id']) {
|
|
2644
|
+
// Handle resolved $ref references
|
|
2645
|
+
const schemaId = schema.payload['x-parser-schema-id'];
|
|
2646
|
+
if (schemaRegistry.has(schemaId)) {
|
|
2647
|
+
payloadRustName = toRustTypeName(schemaId);
|
|
2648
|
+
isComponentMessage = true;
|
|
2649
|
+
}
|
|
2650
|
+
}
|
|
2651
|
+
|
|
2652
|
+
// For component messages, always generate the message wrapper type
|
|
2653
|
+
// even if the payload schema already exists
|
|
2654
|
+
if (isComponentMessage && payloadRustName && !implementedTypes.has(schema.rustName)) {
|
|
2655
|
+
implementedTypes.add(schema.rustName);
|
|
2656
|
+
implementations.push(`
|
|
2657
|
+
${doc}
|
|
2658
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
2659
|
+
pub struct ${schema.rustName} {
|
|
2660
|
+
#[serde(flatten)]
|
|
2661
|
+
pub payload: ${payloadRustName},
|
|
2662
|
+
}
|
|
2663
|
+
|
|
2664
|
+
impl AsyncApiMessage for ${schema.rustName} {
|
|
2665
|
+
fn message_type(&self) -> &'static str {
|
|
2666
|
+
"${schema.name}"
|
|
2667
|
+
}
|
|
2668
|
+
|
|
2669
|
+
fn channel(&self) -> &'static str {
|
|
2670
|
+
"${primaryChannel}"
|
|
2671
|
+
}
|
|
2672
|
+
}`);
|
|
2673
|
+
} else if (!generatedTypes.has(schema.rustName) && !implementedTypes.has(schema.rustName)) {
|
|
2674
|
+
// Generate both struct and implementation for inline message schemas
|
|
2675
|
+
implementedTypes.add(schema.rustName);
|
|
2676
|
+
implementations.push(`
|
|
2677
|
+
${doc}
|
|
2678
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
2679
|
+
pub struct ${schema.rustName} {
|
|
2680
|
+
${generateMessageStruct(schema.payload, schema.rustName)}
|
|
2681
|
+
}
|
|
2682
|
+
|
|
2683
|
+
impl AsyncApiMessage for ${schema.rustName} {
|
|
2684
|
+
fn message_type(&self) -> &'static str {
|
|
2685
|
+
"${schema.name}"
|
|
2686
|
+
}
|
|
2687
|
+
|
|
2688
|
+
fn channel(&self) -> &'static str {
|
|
2689
|
+
"${primaryChannel}"
|
|
2690
|
+
}
|
|
2691
|
+
}`);
|
|
2692
|
+
} else if (payloadRustName && generatedTypes.has(payloadRustName) && !implementedTypes.has(payloadRustName)) {
|
|
2693
|
+
// Generate AsyncApiMessage implementation for existing component schema
|
|
2694
|
+
implementedTypes.add(payloadRustName);
|
|
2695
|
+
implementations.push(`
|
|
2696
|
+
impl AsyncApiMessage for ${payloadRustName} {
|
|
2697
|
+
fn message_type(&self) -> &'static str {
|
|
2698
|
+
"${schema.name}"
|
|
2699
|
+
}
|
|
2700
|
+
|
|
2701
|
+
fn channel(&self) -> &'static str {
|
|
2702
|
+
"${primaryChannel}"
|
|
2703
|
+
}
|
|
2704
|
+
}`);
|
|
2705
|
+
}
|
|
2706
|
+
});
|
|
2707
|
+
return implementations.join('');
|
|
2708
|
+
}
|
|
2709
|
+
};
|
|
2710
|
+
}
|
|
2711
|
+
;// ../common/src/envelope-rust.js
|
|
2712
|
+
/* eslint-disable no-unused-vars */
|
|
2713
|
+
|
|
2714
|
+
|
|
2715
|
+
/**
|
|
2716
|
+
* Generate a unified MessageEnvelope for both rust-server and rust-client templates
|
|
2717
|
+
* This envelope includes all features needed by both templates:
|
|
2718
|
+
* - Basic message structure (id, operation, payload, timestamp)
|
|
2719
|
+
* - Request/response patterns (correlation_id, create_response)
|
|
2720
|
+
* - Channel routing (channel field)
|
|
2721
|
+
* - Error handling (error field, error methods)
|
|
2722
|
+
* - Authentication (auth header methods)
|
|
2723
|
+
* - Serialization (to_bytes, from_bytes)
|
|
2724
|
+
*/
|
|
2725
|
+
function generateMessageEnvelope() {
|
|
2726
|
+
return `use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
|
2727
|
+
use std::collections::HashMap;
|
|
2728
|
+
use uuid::Uuid;
|
|
2729
|
+
use chrono::{DateTime, Utc};
|
|
2730
|
+
|
|
2731
|
+
/// Correlation ID for tracing errors across operations
|
|
2732
|
+
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
|
2733
|
+
pub struct CorrelationId(pub Uuid);
|
|
2734
|
+
|
|
2735
|
+
impl CorrelationId {
|
|
2736
|
+
pub fn new() -> Self {
|
|
2737
|
+
Self(Uuid::new_v4())
|
|
2738
|
+
}
|
|
2739
|
+
}
|
|
2740
|
+
|
|
2741
|
+
impl std::fmt::Display for CorrelationId {
|
|
2742
|
+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
2743
|
+
write!(f, "{}", self.0)
|
|
2744
|
+
}
|
|
2745
|
+
}
|
|
2746
|
+
|
|
2747
|
+
impl Default for CorrelationId {
|
|
2748
|
+
fn default() -> Self {
|
|
2749
|
+
Self::new()
|
|
2750
|
+
}
|
|
2751
|
+
}
|
|
2752
|
+
|
|
2753
|
+
/// Error severity levels for categorization and alerting
|
|
2754
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
|
2755
|
+
pub enum ErrorSeverity {
|
|
2756
|
+
/// Low severity - informational, no action required
|
|
2757
|
+
Low,
|
|
2758
|
+
/// Medium severity - warning, monitoring required
|
|
2759
|
+
Medium,
|
|
2760
|
+
/// High severity - error, immediate attention needed
|
|
2761
|
+
High,
|
|
2762
|
+
/// Critical severity - system failure, urgent action required
|
|
2763
|
+
Critical,
|
|
2764
|
+
}
|
|
2765
|
+
|
|
2766
|
+
impl std::fmt::Display for ErrorSeverity {
|
|
2767
|
+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
2768
|
+
match self {
|
|
2769
|
+
ErrorSeverity::Low => write!(f, "LOW"),
|
|
2770
|
+
ErrorSeverity::Medium => write!(f, "MEDIUM"),
|
|
2771
|
+
ErrorSeverity::High => write!(f, "HIGH"),
|
|
2772
|
+
ErrorSeverity::Critical => write!(f, "CRITICAL"),
|
|
2773
|
+
}
|
|
2774
|
+
}
|
|
2775
|
+
}
|
|
2776
|
+
|
|
2777
|
+
/// Error category for classification and handling
|
|
2778
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
|
2779
|
+
pub enum ErrorCategory {
|
|
2780
|
+
/// Configuration-related errors
|
|
2781
|
+
Configuration,
|
|
2782
|
+
/// Network and protocol errors
|
|
2783
|
+
Network,
|
|
2784
|
+
/// Message validation errors
|
|
2785
|
+
Validation,
|
|
2786
|
+
/// Business logic errors
|
|
2787
|
+
BusinessLogic,
|
|
2788
|
+
/// System resource errors
|
|
2789
|
+
Resource,
|
|
2790
|
+
/// Security-related errors
|
|
2791
|
+
Security,
|
|
2792
|
+
/// Serialization/deserialization errors
|
|
2793
|
+
Serialization,
|
|
2794
|
+
/// Routing errors
|
|
2795
|
+
Routing,
|
|
2796
|
+
/// Authorization errors
|
|
2797
|
+
Authorization,
|
|
2798
|
+
/// Unknown or unclassified errors
|
|
2799
|
+
Unknown,
|
|
2800
|
+
}
|
|
2801
|
+
|
|
2802
|
+
impl std::fmt::Display for ErrorCategory {
|
|
2803
|
+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
2804
|
+
match self {
|
|
2805
|
+
ErrorCategory::Configuration => write!(f, "CONFIGURATION"),
|
|
2806
|
+
ErrorCategory::Network => write!(f, "NETWORK"),
|
|
2807
|
+
ErrorCategory::Validation => write!(f, "VALIDATION"),
|
|
2808
|
+
ErrorCategory::BusinessLogic => write!(f, "BUSINESS_LOGIC"),
|
|
2809
|
+
ErrorCategory::Resource => write!(f, "RESOURCE"),
|
|
2810
|
+
ErrorCategory::Security => write!(f, "SECURITY"),
|
|
2811
|
+
ErrorCategory::Serialization => write!(f, "SERIALIZATION"),
|
|
2812
|
+
ErrorCategory::Routing => write!(f, "ROUTING"),
|
|
2813
|
+
ErrorCategory::Authorization => write!(f, "AUTHORIZATION"),
|
|
2814
|
+
ErrorCategory::Unknown => write!(f, "UNKNOWN"),
|
|
2815
|
+
}
|
|
2816
|
+
}
|
|
2817
|
+
}
|
|
2818
|
+
|
|
2819
|
+
/// Error metadata for enhanced context and monitoring
|
|
2820
|
+
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
2821
|
+
pub struct ErrorMetadata {
|
|
2822
|
+
pub correlation_id: CorrelationId,
|
|
2823
|
+
pub severity: ErrorSeverity,
|
|
2824
|
+
pub category: ErrorCategory,
|
|
2825
|
+
pub timestamp: DateTime<Utc>,
|
|
2826
|
+
pub retryable: bool,
|
|
2827
|
+
pub kind: u32,
|
|
2828
|
+
#[serde(skip_serializing_if = "Option::is_none")]
|
|
2829
|
+
pub source_location: Option<String>,
|
|
2830
|
+
#[serde(skip_serializing_if = "HashMap::is_empty", default)]
|
|
2831
|
+
pub additional_context: HashMap<String, String>,
|
|
2832
|
+
}
|
|
2833
|
+
|
|
2834
|
+
impl ErrorMetadata {
|
|
2835
|
+
pub fn new(severity: ErrorSeverity, category: ErrorCategory, retryable: bool) -> Self {
|
|
2836
|
+
Self {
|
|
2837
|
+
correlation_id: CorrelationId::new(),
|
|
2838
|
+
severity,
|
|
2839
|
+
category,
|
|
2840
|
+
timestamp: Utc::now(),
|
|
2841
|
+
retryable,
|
|
2842
|
+
kind: 0,
|
|
2843
|
+
source_location: None,
|
|
2844
|
+
additional_context: HashMap::new(),
|
|
2845
|
+
}
|
|
2846
|
+
}
|
|
2847
|
+
|
|
2848
|
+
pub fn with_kind(mut self, kind: u32) -> Self {
|
|
2849
|
+
self.kind = kind;
|
|
2850
|
+
self
|
|
2851
|
+
}
|
|
2852
|
+
}
|
|
2853
|
+
|
|
2854
|
+
/// Serializable AsyncAPI error for wire transmission
|
|
2855
|
+
///
|
|
2856
|
+
/// This error type can be sent between server and client while preserving
|
|
2857
|
+
/// all the rich error information needed for proper error handling.
|
|
2858
|
+
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
2859
|
+
#[serde(tag = "error_type", content = "details")]
|
|
2860
|
+
pub enum AsyncApiError {
|
|
2861
|
+
#[serde(rename = "configuration")]
|
|
2862
|
+
Configuration {
|
|
2863
|
+
message: String,
|
|
2864
|
+
metadata: ErrorMetadata,
|
|
2865
|
+
},
|
|
2866
|
+
|
|
2867
|
+
#[serde(rename = "protocol")]
|
|
2868
|
+
Protocol {
|
|
2869
|
+
message: String,
|
|
2870
|
+
protocol: String,
|
|
2871
|
+
metadata: ErrorMetadata,
|
|
2872
|
+
},
|
|
2873
|
+
|
|
2874
|
+
#[serde(rename = "validation")]
|
|
2875
|
+
Validation {
|
|
2876
|
+
message: String,
|
|
2877
|
+
#[serde(skip_serializing_if = "Option::is_none")]
|
|
2878
|
+
field: Option<String>,
|
|
2879
|
+
metadata: ErrorMetadata,
|
|
2880
|
+
},
|
|
2881
|
+
|
|
2882
|
+
#[serde(rename = "handler")]
|
|
2883
|
+
Handler {
|
|
2884
|
+
message: String,
|
|
2885
|
+
handler_name: String,
|
|
2886
|
+
metadata: ErrorMetadata,
|
|
2887
|
+
},
|
|
2888
|
+
|
|
2889
|
+
#[serde(rename = "middleware")]
|
|
2890
|
+
Middleware {
|
|
2891
|
+
message: String,
|
|
2892
|
+
middleware_name: String,
|
|
2893
|
+
metadata: ErrorMetadata,
|
|
2894
|
+
},
|
|
2895
|
+
|
|
2896
|
+
#[serde(rename = "recovery")]
|
|
2897
|
+
Recovery {
|
|
2898
|
+
message: String,
|
|
2899
|
+
attempts: u32,
|
|
2900
|
+
metadata: ErrorMetadata,
|
|
2901
|
+
},
|
|
2902
|
+
|
|
2903
|
+
#[serde(rename = "resource")]
|
|
2904
|
+
Resource {
|
|
2905
|
+
message: String,
|
|
2906
|
+
resource_type: String,
|
|
2907
|
+
metadata: ErrorMetadata,
|
|
2908
|
+
},
|
|
2909
|
+
|
|
2910
|
+
#[serde(rename = "security")]
|
|
2911
|
+
Security {
|
|
2912
|
+
message: String,
|
|
2913
|
+
metadata: ErrorMetadata,
|
|
2914
|
+
},
|
|
2915
|
+
|
|
2916
|
+
#[serde(rename = "authentication")]
|
|
2917
|
+
Authentication {
|
|
2918
|
+
message: String,
|
|
2919
|
+
auth_method: String,
|
|
2920
|
+
metadata: ErrorMetadata,
|
|
2921
|
+
},
|
|
2922
|
+
|
|
2923
|
+
#[serde(rename = "authorization")]
|
|
2924
|
+
Authorization {
|
|
2925
|
+
message: String,
|
|
2926
|
+
required_permissions: Vec<String>,
|
|
2927
|
+
metadata: ErrorMetadata,
|
|
2928
|
+
},
|
|
2929
|
+
|
|
2930
|
+
#[serde(rename = "rate_limit")]
|
|
2931
|
+
RateLimit {
|
|
2932
|
+
message: String,
|
|
2933
|
+
#[serde(skip_serializing_if = "Option::is_none")]
|
|
2934
|
+
retry_after_secs: Option<u64>,
|
|
2935
|
+
},
|
|
2936
|
+
|
|
2937
|
+
#[serde(rename = "context")]
|
|
2938
|
+
Context {
|
|
2939
|
+
message: String,
|
|
2940
|
+
context_key: String,
|
|
2941
|
+
metadata: ErrorMetadata,
|
|
2942
|
+
},
|
|
2943
|
+
}
|
|
2944
|
+
|
|
2945
|
+
impl AsyncApiError {
|
|
2946
|
+
/// Get error message
|
|
2947
|
+
pub fn message(&self) -> &str {
|
|
2948
|
+
match self {
|
|
2949
|
+
AsyncApiError::Configuration { message, .. } => message,
|
|
2950
|
+
AsyncApiError::Protocol { message, .. } => message,
|
|
2951
|
+
AsyncApiError::Validation { message, .. } => message,
|
|
2952
|
+
AsyncApiError::Handler { message, .. } => message,
|
|
2953
|
+
AsyncApiError::Middleware { message, .. } => message,
|
|
2954
|
+
AsyncApiError::Recovery { message, .. } => message,
|
|
2955
|
+
AsyncApiError::Resource { message, .. } => message,
|
|
2956
|
+
AsyncApiError::Security { message, .. } => message,
|
|
2957
|
+
AsyncApiError::Authentication { message, .. } => message,
|
|
2958
|
+
AsyncApiError::Authorization { message, .. } => message,
|
|
2959
|
+
AsyncApiError::RateLimit { message, .. } => message,
|
|
2960
|
+
AsyncApiError::Context { message, .. } => message,
|
|
2961
|
+
}
|
|
2962
|
+
}
|
|
2963
|
+
|
|
2964
|
+
/// Get error metadata (if available)
|
|
2965
|
+
pub fn metadata(&self) -> Option<&ErrorMetadata> {
|
|
2966
|
+
match self {
|
|
2967
|
+
AsyncApiError::Configuration { metadata, .. } => Some(metadata),
|
|
2968
|
+
AsyncApiError::Protocol { metadata, .. } => Some(metadata),
|
|
2969
|
+
AsyncApiError::Validation { metadata, .. } => Some(metadata),
|
|
2970
|
+
AsyncApiError::Handler { metadata, .. } => Some(metadata),
|
|
2971
|
+
AsyncApiError::Middleware { metadata, .. } => Some(metadata),
|
|
2972
|
+
AsyncApiError::Recovery { metadata, .. } => Some(metadata),
|
|
2973
|
+
AsyncApiError::Resource { metadata, .. } => Some(metadata),
|
|
2974
|
+
AsyncApiError::Security { metadata, .. } => Some(metadata),
|
|
2975
|
+
AsyncApiError::Authentication { metadata, .. } => Some(metadata),
|
|
2976
|
+
AsyncApiError::Authorization { metadata, .. } => Some(metadata),
|
|
2977
|
+
AsyncApiError::Context { metadata, .. } => Some(metadata),
|
|
2978
|
+
AsyncApiError::RateLimit { .. } => None,
|
|
2979
|
+
}
|
|
2980
|
+
}
|
|
2981
|
+
|
|
2982
|
+
/// Check if error is retryable
|
|
2983
|
+
pub fn is_retryable(&self) -> bool {
|
|
2984
|
+
self.metadata().map_or(false, |m| m.retryable)
|
|
2985
|
+
}
|
|
2986
|
+
|
|
2987
|
+
/// Get error severity
|
|
2988
|
+
pub fn severity(&self) -> Option<ErrorSeverity> {
|
|
2989
|
+
self.metadata().map(|m| m.severity)
|
|
2990
|
+
}
|
|
2991
|
+
|
|
2992
|
+
/// Get error category
|
|
2993
|
+
pub fn category(&self) -> Option<ErrorCategory> {
|
|
2994
|
+
self.metadata().map(|m| m.category)
|
|
2995
|
+
}
|
|
2996
|
+
|
|
2997
|
+
/// Get correlation ID for tracing
|
|
2998
|
+
pub fn correlation_id(&self) -> Option<&CorrelationId> {
|
|
2999
|
+
self.metadata().map(|m| &m.correlation_id)
|
|
3000
|
+
}
|
|
3001
|
+
}
|
|
3002
|
+
|
|
3003
|
+
impl std::fmt::Display for AsyncApiError {
|
|
3004
|
+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
3005
|
+
write!(f, "{}", self.message())
|
|
3006
|
+
}
|
|
3007
|
+
}
|
|
3008
|
+
|
|
3009
|
+
impl std::error::Error for AsyncApiError {}
|
|
3010
|
+
|
|
3011
|
+
/// Unified message envelope for consistent AsyncAPI message format
|
|
3012
|
+
///
|
|
3013
|
+
/// This envelope provides a standardized structure for all messages sent through the system,
|
|
3014
|
+
/// enabling better correlation, error handling, authentication, and observability.
|
|
3015
|
+
///
|
|
3016
|
+
/// ## Features
|
|
3017
|
+
///
|
|
3018
|
+
/// - **Request/Response Patterns**: Correlation IDs for matching requests with responses
|
|
3019
|
+
/// - **Error Handling**: Built-in error information for failed operations
|
|
3020
|
+
/// - **Authentication**: Integrated auth header support
|
|
3021
|
+
/// - **Channel Routing**: Optional channel context for message routing
|
|
3022
|
+
/// - **Serialization**: Efficient byte conversion for transport layers
|
|
3023
|
+
/// - **Type Safety**: Strongly-typed payload extraction
|
|
3024
|
+
///
|
|
3025
|
+
/// ## Usage
|
|
3026
|
+
///
|
|
3027
|
+
/// \`\`\`no-run
|
|
3028
|
+
/// use crate::models::*;
|
|
3029
|
+
/// use uuid::Uuid;
|
|
3030
|
+
/// use std::collections::HashMap;
|
|
3031
|
+
///
|
|
3032
|
+
/// // Create a basic message envelope
|
|
3033
|
+
/// let envelope = MessageEnvelope::new("sendChatMessage", chat_message)?;
|
|
3034
|
+
///
|
|
3035
|
+
/// // Create with correlation ID for request/response
|
|
3036
|
+
/// let request = MessageEnvelope::new_with_correlation_id(
|
|
3037
|
+
/// "getUserProfile",
|
|
3038
|
+
/// user_request,
|
|
3039
|
+
/// Uuid::new_v4().to_string()
|
|
3040
|
+
/// )?;
|
|
3041
|
+
///
|
|
3042
|
+
/// // Create response with same correlation ID
|
|
3043
|
+
/// let response = request.create_response("getUserProfile_response", user_profile)?;
|
|
3044
|
+
///
|
|
3045
|
+
/// // Create error response
|
|
3046
|
+
/// let error = MessageEnvelope::error_response(
|
|
3047
|
+
/// "getUserProfile_response",
|
|
3048
|
+
/// "USER_NOT_FOUND",
|
|
3049
|
+
/// "User does not exist",
|
|
3050
|
+
/// request.correlation_id().map(|s| s.to_string())
|
|
3051
|
+
/// );
|
|
3052
|
+
///
|
|
3053
|
+
/// // Add authentication headers
|
|
3054
|
+
/// let mut headers = HashMap::new();
|
|
3055
|
+
/// headers.insert("Authorization".to_string(), "Bearer token123".to_string());
|
|
3056
|
+
/// let auth_envelope = envelope.with_headers(headers);
|
|
3057
|
+
///
|
|
3058
|
+
/// // Serialize for transport
|
|
3059
|
+
/// let bytes = envelope.to_bytes()?;
|
|
3060
|
+
/// let deserialized = MessageEnvelope::from_bytes(&bytes)?;
|
|
3061
|
+
/// \`\`\`
|
|
3062
|
+
|
|
3063
|
+
/// Standard message envelope for all AsyncAPI messages
|
|
3064
|
+
///
|
|
3065
|
+
/// This envelope provides a consistent structure for all messages sent through the system,
|
|
3066
|
+
/// enabling better correlation, error handling, and observability.
|
|
3067
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
3068
|
+
pub struct MessageEnvelope {
|
|
3069
|
+
/// Unique message identifier
|
|
3070
|
+
pub id: String,
|
|
3071
|
+
/// AsyncAPI operation ID
|
|
3072
|
+
pub operation: String,
|
|
3073
|
+
/// Message payload (any serializable type)
|
|
3074
|
+
pub payload: serde_json::Value,
|
|
3075
|
+
/// ISO 8601 timestamp when message was created
|
|
3076
|
+
pub timestamp: String,
|
|
3077
|
+
/// Correlation ID for request/response patterns
|
|
3078
|
+
pub correlation_id: Option<String>,
|
|
3079
|
+
/// Optional channel context for routing
|
|
3080
|
+
pub channel: Option<String>,
|
|
3081
|
+
/// Transport-level headers (auth, routing, etc.)
|
|
3082
|
+
pub headers: Option<HashMap<String, String>>,
|
|
3083
|
+
/// Rich error information if operation failed
|
|
3084
|
+
/// Contains detailed error metadata including severity, category, and correlation
|
|
3085
|
+
pub error: Option<AsyncApiError>,
|
|
3086
|
+
}
|
|
3087
|
+
|
|
3088
|
+
impl MessageEnvelope {
|
|
3089
|
+
/// Create a new message envelope with the given operation and payload
|
|
3090
|
+
pub fn new<T: Serialize>(operation: &str, payload: T) -> Result<Self, serde_json::Error> {
|
|
3091
|
+
Ok(Self {
|
|
3092
|
+
id: Uuid::new_v4().to_string(),
|
|
3093
|
+
operation: operation.to_string(),
|
|
3094
|
+
payload: serde_json::to_value(payload)?,
|
|
3095
|
+
timestamp: chrono::Utc::now().to_rfc3339(),
|
|
3096
|
+
correlation_id: None,
|
|
3097
|
+
channel: None,
|
|
3098
|
+
headers: None,
|
|
3099
|
+
error: None,
|
|
3100
|
+
})
|
|
3101
|
+
}
|
|
3102
|
+
|
|
3103
|
+
/// Create a new envelope with automatic correlation ID generation
|
|
3104
|
+
pub fn new_with_id<T: Serialize>(
|
|
3105
|
+
operation: &str,
|
|
3106
|
+
payload: T,
|
|
3107
|
+
) -> Result<Self, serde_json::Error> {
|
|
3108
|
+
Self::new(operation, payload)
|
|
3109
|
+
.map(|envelope| envelope.with_correlation_id(Uuid::new_v4().to_string()))
|
|
3110
|
+
}
|
|
3111
|
+
|
|
3112
|
+
/// Create a new message envelope with a specific correlation ID
|
|
3113
|
+
pub fn new_with_correlation_id<T: Serialize>(
|
|
3114
|
+
operation: &str,
|
|
3115
|
+
payload: T,
|
|
3116
|
+
correlation_id: String,
|
|
3117
|
+
) -> Result<Self, serde_json::Error> {
|
|
3118
|
+
let mut envelope = Self::new(operation, payload)?;
|
|
3119
|
+
envelope.correlation_id = Some(correlation_id);
|
|
3120
|
+
Ok(envelope)
|
|
3121
|
+
}
|
|
3122
|
+
|
|
3123
|
+
/// Create an error response envelope with rich AsyncApiError
|
|
3124
|
+
pub fn error_response(
|
|
3125
|
+
operation: &str,
|
|
3126
|
+
error: AsyncApiError,
|
|
3127
|
+
correlation_id: Option<String>,
|
|
3128
|
+
) -> Self {
|
|
3129
|
+
Self {
|
|
3130
|
+
id: Uuid::new_v4().to_string(),
|
|
3131
|
+
operation: operation.to_string(),
|
|
3132
|
+
payload: serde_json::Value::Null,
|
|
3133
|
+
timestamp: chrono::Utc::now().to_rfc3339(),
|
|
3134
|
+
correlation_id,
|
|
3135
|
+
channel: None,
|
|
3136
|
+
headers: None,
|
|
3137
|
+
error: Some(error),
|
|
3138
|
+
}
|
|
3139
|
+
}
|
|
3140
|
+
|
|
3141
|
+
/// Create a simple error response envelope (for backward compatibility)
|
|
3142
|
+
pub fn simple_error_response(
|
|
3143
|
+
operation: &str,
|
|
3144
|
+
error_message: &str,
|
|
3145
|
+
correlation_id: Option<String>,
|
|
3146
|
+
) -> Self {
|
|
3147
|
+
let error = AsyncApiError::Handler {
|
|
3148
|
+
message: error_message.to_string(),
|
|
3149
|
+
handler_name: operation.to_string(),
|
|
3150
|
+
metadata: ErrorMetadata::new(
|
|
3151
|
+
ErrorSeverity::High,
|
|
3152
|
+
ErrorCategory::BusinessLogic,
|
|
3153
|
+
false,
|
|
3154
|
+
),
|
|
3155
|
+
};
|
|
3156
|
+
Self::error_response(operation, error, correlation_id)
|
|
3157
|
+
}
|
|
3158
|
+
|
|
3159
|
+
/// Set the correlation ID for this envelope
|
|
3160
|
+
pub fn with_correlation_id(mut self, id: String) -> Self {
|
|
3161
|
+
self.correlation_id = Some(id);
|
|
3162
|
+
self
|
|
3163
|
+
}
|
|
3164
|
+
|
|
3165
|
+
/// Set the channel for this envelope
|
|
3166
|
+
pub fn with_channel(mut self, channel: String) -> Self {
|
|
3167
|
+
self.channel = Some(channel);
|
|
3168
|
+
self
|
|
3169
|
+
}
|
|
3170
|
+
|
|
3171
|
+
/// Set headers for this envelope
|
|
3172
|
+
pub fn with_headers(mut self, headers: HashMap<String, String>) -> Self {
|
|
3173
|
+
self.headers = Some(headers);
|
|
3174
|
+
self
|
|
3175
|
+
}
|
|
3176
|
+
|
|
3177
|
+
/// Add a single header to this envelope
|
|
3178
|
+
pub fn with_header(mut self, key: String, value: String) -> Self {
|
|
3179
|
+
if self.headers.is_none() {
|
|
3180
|
+
self.headers = Some(HashMap::new());
|
|
3181
|
+
}
|
|
3182
|
+
if let Some(ref mut headers) = self.headers {
|
|
3183
|
+
headers.insert(key, value);
|
|
3184
|
+
}
|
|
3185
|
+
self
|
|
3186
|
+
}
|
|
3187
|
+
|
|
3188
|
+
/// Add authentication headers to the envelope
|
|
3189
|
+
/// This method accepts any headers map, allowing templates to integrate their own auth systems
|
|
3190
|
+
pub fn with_auth_headers(mut self, auth_headers: HashMap<String, String>) -> Self {
|
|
3191
|
+
if !auth_headers.is_empty() {
|
|
3192
|
+
if let Some(ref mut headers) = self.headers {
|
|
3193
|
+
headers.extend(auth_headers);
|
|
3194
|
+
} else {
|
|
3195
|
+
self.headers = Some(auth_headers);
|
|
3196
|
+
}
|
|
3197
|
+
}
|
|
3198
|
+
self
|
|
3199
|
+
}
|
|
3200
|
+
|
|
3201
|
+
/// Set an error on this envelope
|
|
3202
|
+
pub fn with_error(mut self, error: AsyncApiError) -> Self {
|
|
3203
|
+
self.error = Some(error);
|
|
3204
|
+
self
|
|
3205
|
+
}
|
|
3206
|
+
|
|
3207
|
+
/// Set a simple error on this envelope (for backward compatibility)
|
|
3208
|
+
pub fn with_simple_error(mut self, message: &str) -> Self {
|
|
3209
|
+
let error = AsyncApiError::Handler {
|
|
3210
|
+
message: message.to_string(),
|
|
3211
|
+
handler_name: self.operation.clone(),
|
|
3212
|
+
metadata: ErrorMetadata::new(
|
|
3213
|
+
ErrorSeverity::High,
|
|
3214
|
+
ErrorCategory::BusinessLogic,
|
|
3215
|
+
false,
|
|
3216
|
+
),
|
|
3217
|
+
};
|
|
3218
|
+
self.error = Some(error);
|
|
3219
|
+
self
|
|
3220
|
+
}
|
|
3221
|
+
|
|
3222
|
+
/// Extract the payload as a strongly-typed message
|
|
3223
|
+
pub fn extract_payload<T: DeserializeOwned>(&self) -> Result<T, serde_json::Error> {
|
|
3224
|
+
serde_json::from_value(self.payload.clone())
|
|
3225
|
+
}
|
|
3226
|
+
|
|
3227
|
+
/// Check if this envelope contains an error
|
|
3228
|
+
pub fn is_error(&self) -> bool {
|
|
3229
|
+
self.error.is_some()
|
|
3230
|
+
}
|
|
3231
|
+
|
|
3232
|
+
/// Get the correlation ID if present
|
|
3233
|
+
pub fn correlation_id(&self) -> Option<&str> {
|
|
3234
|
+
self.correlation_id.as_deref()
|
|
3235
|
+
}
|
|
3236
|
+
|
|
3237
|
+
/// Create a response envelope with the same correlation ID
|
|
3238
|
+
pub fn create_response<T: Serialize>(
|
|
3239
|
+
&self,
|
|
3240
|
+
response_operation: &str,
|
|
3241
|
+
payload: T,
|
|
3242
|
+
) -> Result<Self, serde_json::Error> {
|
|
3243
|
+
let mut response = Self::new(response_operation, payload)?;
|
|
3244
|
+
response.correlation_id = self.correlation_id.clone();
|
|
3245
|
+
response.channel = self.channel.clone();
|
|
3246
|
+
Ok(response)
|
|
3247
|
+
}
|
|
3248
|
+
|
|
3249
|
+
/// Convert the envelope to bytes for transport
|
|
3250
|
+
pub fn to_bytes(&self) -> Result<Vec<u8>, serde_json::Error> {
|
|
3251
|
+
serde_json::to_vec(self)
|
|
3252
|
+
}
|
|
3253
|
+
|
|
3254
|
+
/// Parse envelope from bytes received from transport
|
|
3255
|
+
pub fn from_bytes(bytes: &[u8]) -> Result<Self, serde_json::Error> {
|
|
3256
|
+
serde_json::from_slice(bytes)
|
|
3257
|
+
}
|
|
3258
|
+
}
|
|
3259
|
+
|
|
3260
|
+
`;
|
|
3261
|
+
}
|
|
3262
|
+
;// ../common/src/models-ts.js
|
|
3263
|
+
/* eslint-disable no-unused-vars */
|
|
3264
|
+
|
|
3265
|
+
|
|
3266
|
+
/**
|
|
3267
|
+
* Generate TypeScript models from AsyncAPI specification
|
|
3268
|
+
* This helper extracts the common schema processing logic used by TypeScript templates
|
|
3269
|
+
*
|
|
3270
|
+
* @param {Object} asyncapi - AsyncAPI document
|
|
3271
|
+
* @param {Object} options - Generation options
|
|
3272
|
+
* @param {Function} options.toTypeScriptTypeName - Function to convert names to TypeScript type names
|
|
3273
|
+
* @param {Function} options.toTypeScriptIdentifier - Function to convert names to TypeScript identifiers
|
|
3274
|
+
* @param {boolean} options.includeMessageTypes - Whether to include message type constants
|
|
3275
|
+
* @returns {Object} Generated models data and functions
|
|
3276
|
+
*/
|
|
3277
|
+
function generateTypeScriptModels(asyncapi, options = {}) {
|
|
3278
|
+
const {
|
|
3279
|
+
toTypeScriptTypeName,
|
|
3280
|
+
toTypeScriptIdentifier,
|
|
3281
|
+
includeMessageTypes = true
|
|
3282
|
+
} = options;
|
|
3283
|
+
|
|
3284
|
+
// Helper functions for TypeScript identifier generation
|
|
3285
|
+
function defaultToTypeScriptIdentifier(str) {
|
|
3286
|
+
if (!str) return 'unknown';
|
|
3287
|
+
let identifier = str.replace(/[^a-zA-Z0-9_]/g, '_').replace(/^[0-9]/, '_$&').replace(/_+/g, '_').replace(/^_+|_+$/g, '');
|
|
3288
|
+
if (/^[0-9]/.test(identifier)) {
|
|
3289
|
+
identifier = 'item_' + identifier;
|
|
3290
|
+
}
|
|
3291
|
+
if (!identifier) {
|
|
3292
|
+
identifier = 'unknown';
|
|
3293
|
+
}
|
|
3294
|
+
return identifier;
|
|
3295
|
+
}
|
|
3296
|
+
function defaultToTypeScriptTypeName(str) {
|
|
3297
|
+
if (!str) return 'Unknown';
|
|
3298
|
+
// Handle camelCase and PascalCase properly
|
|
3299
|
+
const identifier = str.replace(/[^a-zA-Z0-9]/g, '_').replace(/^[0-9]/, '_$&').replace(/_+/g, '_').replace(/^_+|_+$/g, '');
|
|
3300
|
+
|
|
3301
|
+
// Split on underscores and camelCase boundaries
|
|
3302
|
+
const parts = identifier.split(/[_\s]+|(?=[A-Z])/);
|
|
3303
|
+
return parts.filter(part => part.length > 0).map(part => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase()).join('');
|
|
3304
|
+
}
|
|
3305
|
+
|
|
3306
|
+
// Use provided functions or defaults
|
|
3307
|
+
const toTSTypeName = toTypeScriptTypeName || defaultToTypeScriptTypeName;
|
|
3308
|
+
const toTSIdentifier = toTypeScriptIdentifier || defaultToTypeScriptIdentifier;
|
|
3309
|
+
|
|
3310
|
+
// Extract message schemas and build channel mapping
|
|
3311
|
+
const components = asyncapi.components();
|
|
3312
|
+
const messageSchemas = [];
|
|
3313
|
+
const componentSchemas = [];
|
|
3314
|
+
const messageToChannels = new Map();
|
|
3315
|
+
const generatedTypes = new Set();
|
|
3316
|
+
const schemaRegistry = new Map();
|
|
3317
|
+
|
|
3318
|
+
// Build schema registry from components.schemas
|
|
3319
|
+
// Try to access the raw AsyncAPI document
|
|
3320
|
+
let rawDoc = null;
|
|
3321
|
+
try {
|
|
3322
|
+
if (asyncapi.json && typeof asyncapi.json === 'function') {
|
|
3323
|
+
rawDoc = asyncapi.json();
|
|
3324
|
+
} else if (asyncapi._json) {
|
|
3325
|
+
rawDoc = asyncapi._json;
|
|
3326
|
+
}
|
|
3327
|
+
} catch (e) {
|
|
3328
|
+
// Ignore
|
|
3329
|
+
}
|
|
3330
|
+
|
|
3331
|
+
// Extract schemas from raw document if available
|
|
3332
|
+
if (rawDoc && rawDoc.components && rawDoc.components.schemas) {
|
|
3333
|
+
Object.entries(rawDoc.components.schemas).forEach(([name, schema]) => {
|
|
3334
|
+
if (name && typeof name === 'string' && schema && typeof schema === 'object') {
|
|
3335
|
+
schemaRegistry.set(name, schema);
|
|
3336
|
+
componentSchemas.push({
|
|
3337
|
+
name,
|
|
3338
|
+
typeName: toTSTypeName(name),
|
|
3339
|
+
schema: schema,
|
|
3340
|
+
description: schema.description
|
|
3341
|
+
});
|
|
3342
|
+
}
|
|
3343
|
+
});
|
|
3344
|
+
}
|
|
3345
|
+
|
|
3346
|
+
// Fallback: try the components.schemas() method
|
|
3347
|
+
if (componentSchemas.length === 0 && components && components.schemas) {
|
|
3348
|
+
try {
|
|
3349
|
+
const schemas = components.schemas();
|
|
3350
|
+
if (schemas) {
|
|
3351
|
+
// Try different ways to access schemas
|
|
3352
|
+
let schemaEntries = [];
|
|
3353
|
+
if (schemas instanceof Map) {
|
|
3354
|
+
schemaEntries = Array.from(schemas.entries());
|
|
3355
|
+
} else if (typeof schemas === 'object') {
|
|
3356
|
+
schemaEntries = Object.entries(schemas);
|
|
3357
|
+
} else if (schemas.all && typeof schemas.all === 'function') {
|
|
3358
|
+
// AsyncAPI parser might have an all() method
|
|
3359
|
+
const allSchemas = schemas.all();
|
|
3360
|
+
if (Array.isArray(allSchemas)) {
|
|
3361
|
+
schemaEntries = allSchemas.map(schema => {
|
|
3362
|
+
const name = schema.uid ? schema.uid() : schema.id ? schema.id() : null;
|
|
3363
|
+
return [name, schema];
|
|
3364
|
+
}).filter(([name]) => name);
|
|
3365
|
+
}
|
|
3366
|
+
}
|
|
3367
|
+
schemaEntries.forEach(([name, schema]) => {
|
|
3368
|
+
// Skip internal AsyncAPI parser objects and numeric keys
|
|
3369
|
+
if (!name || name === 'collections' || name === '_meta' || name.startsWith('_') || /^\d+$/.test(name)) {
|
|
3370
|
+
return;
|
|
3371
|
+
}
|
|
3372
|
+
let schemaData = null;
|
|
3373
|
+
let description = null;
|
|
3374
|
+
try {
|
|
3375
|
+
// Handle different schema object types
|
|
3376
|
+
if (schema && typeof schema.json === 'function') {
|
|
3377
|
+
schemaData = schema.json();
|
|
3378
|
+
} else if (schema && typeof schema === 'object') {
|
|
3379
|
+
schemaData = schema;
|
|
3380
|
+
}
|
|
3381
|
+
if (schema && typeof schema.description === 'function') {
|
|
3382
|
+
description = schema.description();
|
|
3383
|
+
} else if (schema && schema.description) {
|
|
3384
|
+
description = schema.description;
|
|
3385
|
+
}
|
|
3386
|
+
} catch (e) {
|
|
3387
|
+
// Ignore schema extraction errors
|
|
3388
|
+
console.warn(`Failed to extract schema for ${name}:`, e.message);
|
|
3389
|
+
}
|
|
3390
|
+
if (schemaData && typeof name === 'string' && name.length > 0) {
|
|
3391
|
+
schemaRegistry.set(name, schemaData);
|
|
3392
|
+
componentSchemas.push({
|
|
3393
|
+
name,
|
|
3394
|
+
typeName: toTSTypeName(name),
|
|
3395
|
+
schema: schemaData,
|
|
3396
|
+
description
|
|
3397
|
+
});
|
|
3398
|
+
}
|
|
3399
|
+
});
|
|
3400
|
+
}
|
|
3401
|
+
} catch (e) {
|
|
3402
|
+
console.warn('Failed to extract component schemas:', e.message);
|
|
3403
|
+
}
|
|
3404
|
+
}
|
|
3405
|
+
|
|
3406
|
+
// First, build channel to message mapping
|
|
3407
|
+
if (asyncapi.channels) {
|
|
3408
|
+
const channels = asyncapi.channels();
|
|
3409
|
+
if (channels) {
|
|
3410
|
+
Object.entries(channels).forEach(([channelName, channel]) => {
|
|
3411
|
+
try {
|
|
3412
|
+
// Handle AsyncAPI 3.x format
|
|
3413
|
+
if (channel.messages) {
|
|
3414
|
+
const messages = channel.messages();
|
|
3415
|
+
if (messages) {
|
|
3416
|
+
Object.entries(messages).forEach(([msgKey, message]) => {
|
|
3417
|
+
if (message) {
|
|
3418
|
+
let messageName = null;
|
|
3419
|
+
if (message.$ref) {
|
|
3420
|
+
messageName = message.$ref.split('/').pop();
|
|
3421
|
+
} else if (message.name) {
|
|
3422
|
+
messageName = typeof message.name === 'function' ? message.name() : message.name;
|
|
3423
|
+
}
|
|
3424
|
+
if (messageName) {
|
|
3425
|
+
if (!messageToChannels.has(messageName)) {
|
|
3426
|
+
messageToChannels.set(messageName, []);
|
|
3427
|
+
}
|
|
3428
|
+
messageToChannels.get(messageName).push(channelName);
|
|
3429
|
+
}
|
|
3430
|
+
}
|
|
3431
|
+
});
|
|
3432
|
+
}
|
|
3433
|
+
}
|
|
3434
|
+
} catch (e) {
|
|
3435
|
+
// Ignore channel processing errors
|
|
3436
|
+
}
|
|
3437
|
+
});
|
|
3438
|
+
}
|
|
3439
|
+
}
|
|
3440
|
+
|
|
3441
|
+
// Extract messages from components
|
|
3442
|
+
if (components && components.messages) {
|
|
3443
|
+
const messages = components.messages();
|
|
3444
|
+
if (messages) {
|
|
3445
|
+
Object.entries(messages).forEach(([name, message]) => {
|
|
3446
|
+
// Skip internal AsyncAPI parser objects
|
|
3447
|
+
if (name === 'collections' || name === '_meta' || name.startsWith('_')) {
|
|
3448
|
+
return;
|
|
3449
|
+
}
|
|
3450
|
+
let payload = null;
|
|
3451
|
+
let description = null;
|
|
3452
|
+
let title = null;
|
|
3453
|
+
let messageName = name;
|
|
3454
|
+
try {
|
|
3455
|
+
if (message.payload && typeof message.payload === 'function') {
|
|
3456
|
+
const payloadSchema = message.payload();
|
|
3457
|
+
payload = payloadSchema && payloadSchema.json ? payloadSchema.json() : payloadSchema;
|
|
3458
|
+
}
|
|
3459
|
+
description = message.description && typeof message.description === 'function' ? message.description() : message.description;
|
|
3460
|
+
title = message.title && typeof message.title === 'function' ? message.title() : message.title;
|
|
3461
|
+
|
|
3462
|
+
// Try to get the actual message name
|
|
3463
|
+
if (message.name && typeof message.name === 'function') {
|
|
3464
|
+
messageName = message.name();
|
|
3465
|
+
} else if (message.name) {
|
|
3466
|
+
messageName = message.name;
|
|
3467
|
+
}
|
|
3468
|
+
} catch (e) {
|
|
3469
|
+
// Ignore payload extraction errors
|
|
3470
|
+
}
|
|
3471
|
+
const channels = messageToChannels.get(messageName) || messageToChannels.get(name) || [];
|
|
3472
|
+
messageSchemas.push({
|
|
3473
|
+
name: messageName,
|
|
3474
|
+
typeName: toTSTypeName(messageName),
|
|
3475
|
+
payload,
|
|
3476
|
+
description: description || title,
|
|
3477
|
+
channels
|
|
3478
|
+
});
|
|
3479
|
+
});
|
|
3480
|
+
}
|
|
3481
|
+
}
|
|
3482
|
+
|
|
3483
|
+
// Helper function to convert JSON schema to TypeScript type
|
|
3484
|
+
function jsonSchemaToTypeScriptType(schema, fieldName = '', isComponentSchemaDefinition = false) {
|
|
3485
|
+
if (!schema) return 'any';
|
|
3486
|
+
|
|
3487
|
+
// Handle $ref - resolve from schema registry
|
|
3488
|
+
if (schema.$ref) {
|
|
3489
|
+
const refName = schema.$ref.split('/').pop();
|
|
3490
|
+
// Always return the type name for $ref, since we generate all component schemas
|
|
3491
|
+
const typeName = toTSTypeName(refName);
|
|
3492
|
+
return typeName;
|
|
3493
|
+
}
|
|
3494
|
+
|
|
3495
|
+
// Handle resolved $ref - check for x-parser-schema-id which indicates original schema name
|
|
3496
|
+
// But skip this if we're defining the component schema itself (to avoid circular references)
|
|
3497
|
+
if (!isComponentSchemaDefinition && schema['x-parser-schema-id'] && typeof schema['x-parser-schema-id'] === 'string') {
|
|
3498
|
+
const schemaId = schema['x-parser-schema-id'];
|
|
3499
|
+
// Check if this matches a known component schema
|
|
3500
|
+
if (schemaRegistry.has(schemaId)) {
|
|
3501
|
+
const typeName = toTSTypeName(schemaId);
|
|
3502
|
+
return typeName;
|
|
3503
|
+
}
|
|
3504
|
+
}
|
|
3505
|
+
if (!schema.type) {
|
|
3506
|
+
// If no type specified, check for properties (object) or items (array)
|
|
3507
|
+
if (schema.properties) {
|
|
3508
|
+
schema.type = 'object';
|
|
3509
|
+
} else if (schema.items) {
|
|
3510
|
+
schema.type = 'array';
|
|
3511
|
+
} else {
|
|
3512
|
+
return 'any';
|
|
3513
|
+
}
|
|
3514
|
+
}
|
|
3515
|
+
switch (schema.type) {
|
|
3516
|
+
case 'string':
|
|
3517
|
+
if (schema.enum && schema.enum.length > 0) {
|
|
3518
|
+
return schema.enum.map(val => `'${val}'`).join(' | ');
|
|
3519
|
+
}
|
|
3520
|
+
return 'string';
|
|
3521
|
+
case 'integer':
|
|
3522
|
+
case 'number':
|
|
3523
|
+
return 'number';
|
|
3524
|
+
case 'boolean':
|
|
3525
|
+
return 'boolean';
|
|
3526
|
+
case 'array':
|
|
3527
|
+
{
|
|
3528
|
+
if (schema.items) {
|
|
3529
|
+
const itemType = jsonSchemaToTypeScriptType(schema.items, fieldName);
|
|
3530
|
+
return `${itemType}[]`;
|
|
3531
|
+
}
|
|
3532
|
+
return 'any[]';
|
|
3533
|
+
}
|
|
3534
|
+
case 'object':
|
|
3535
|
+
// For objects with properties, we should generate inline types or check if it's a known schema
|
|
3536
|
+
if (schema.properties) {
|
|
3537
|
+
// This is a complex object - for now return Record<string, any>
|
|
3538
|
+
// In a more sophisticated implementation, we could generate inline types
|
|
3539
|
+
return 'Record<string, any>';
|
|
3540
|
+
}
|
|
3541
|
+
return 'Record<string, any>';
|
|
3542
|
+
default:
|
|
3543
|
+
return 'any';
|
|
3544
|
+
}
|
|
3545
|
+
}
|
|
3546
|
+
|
|
3547
|
+
// Generate message interfaces
|
|
3548
|
+
function generateMessageInterface(schema, messageName) {
|
|
3549
|
+
if (!schema || !schema.properties) {
|
|
3550
|
+
return ' [key: string]: any;';
|
|
3551
|
+
}
|
|
3552
|
+
const fields = Object.entries(schema.properties).map(([fieldName, fieldSchema]) => {
|
|
3553
|
+
const tsType = jsonSchemaToTypeScriptType(fieldSchema, fieldName);
|
|
3554
|
+
const optional = !schema.required || !schema.required.includes(fieldName);
|
|
3555
|
+
const optionalMarker = optional ? '?' : '';
|
|
3556
|
+
let fieldDoc = '';
|
|
3557
|
+
if (fieldSchema.description) {
|
|
3558
|
+
fieldDoc = ` /** ${fieldSchema.description} */\n`;
|
|
3559
|
+
}
|
|
3560
|
+
return `${fieldDoc} ${fieldName}${optionalMarker}: ${tsType};`;
|
|
3561
|
+
}).join('\n');
|
|
3562
|
+
return fields;
|
|
3563
|
+
}
|
|
3564
|
+
|
|
3565
|
+
// Return the processed data and generation functions
|
|
3566
|
+
return {
|
|
3567
|
+
messageSchemas,
|
|
3568
|
+
componentSchemas,
|
|
3569
|
+
messageToChannels,
|
|
3570
|
+
generatedTypes,
|
|
3571
|
+
schemaRegistry,
|
|
3572
|
+
// Generation functions
|
|
3573
|
+
generateMessageInterface,
|
|
3574
|
+
jsonSchemaToTypeScriptType,
|
|
3575
|
+
// Generate interfaces for component schemas
|
|
3576
|
+
generateComponentSchemas() {
|
|
3577
|
+
let content = '';
|
|
3578
|
+
componentSchemas.forEach(schema => {
|
|
3579
|
+
const doc = schema.description ? `/** ${schema.description} */\n` : `/** ${schema.name} */\n`;
|
|
3580
|
+
|
|
3581
|
+
// Check if this is a standalone enum schema
|
|
3582
|
+
if (schema.schema.type === 'string' && schema.schema.enum && Array.isArray(schema.schema.enum)) {
|
|
3583
|
+
// Generate union type for enum
|
|
3584
|
+
const enumValues = schema.schema.enum.map(val => `'${val}'`).join(' | ');
|
|
3585
|
+
content += `${doc}export type ${schema.typeName} = ${enumValues};\n\n`;
|
|
3586
|
+
} else if (schema.schema.type && !schema.schema.properties) {
|
|
3587
|
+
// For primitive types (integer, number, string, boolean, array) without properties,
|
|
3588
|
+
// generate a type alias instead of an interface
|
|
3589
|
+
const tsType = jsonSchemaToTypeScriptType(schema.schema, schema.name, true);
|
|
3590
|
+
content += `${doc}export type ${schema.typeName} = ${tsType};\n\n`;
|
|
3591
|
+
} else {
|
|
3592
|
+
// Generate interface for object schema
|
|
3593
|
+
content += `${doc}export interface ${schema.typeName} {\n`;
|
|
3594
|
+
content += generateMessageInterface(schema.schema, schema.typeName);
|
|
3595
|
+
content += '\n}\n\n';
|
|
3596
|
+
}
|
|
3597
|
+
|
|
3598
|
+
// Track generated types to avoid duplicates
|
|
3599
|
+
generatedTypes.add(schema.typeName);
|
|
3600
|
+
});
|
|
3601
|
+
return content;
|
|
3602
|
+
},
|
|
3603
|
+
// Generate interfaces for each message
|
|
3604
|
+
generateMessageSchemas() {
|
|
3605
|
+
let content = '';
|
|
3606
|
+
messageSchemas.forEach(schema => {
|
|
3607
|
+
const interfaceName = `${schema.typeName}Payload`;
|
|
3608
|
+
|
|
3609
|
+
// Check if this is a duplicate of a component schema
|
|
3610
|
+
// For message payloads that match component schema names, skip the payload version
|
|
3611
|
+
if (generatedTypes.has(schema.typeName) || generatedTypes.has(interfaceName)) {
|
|
3612
|
+
// Skip generating the payload version if we already have the component schema
|
|
3613
|
+
return;
|
|
3614
|
+
}
|
|
3615
|
+
const doc = schema.description ? `/** ${schema.description} */\n` : `/** ${schema.name} message payload */\n`;
|
|
3616
|
+
content += `${doc}export interface ${interfaceName} {\n`;
|
|
3617
|
+
content += generateMessageInterface(schema.payload, schema.typeName);
|
|
3618
|
+
content += '\n}\n\n';
|
|
3619
|
+
generatedTypes.add(interfaceName);
|
|
3620
|
+
});
|
|
3621
|
+
return content;
|
|
3622
|
+
},
|
|
3623
|
+
// Generate message type constants and unions
|
|
3624
|
+
generateMessageTypes() {
|
|
3625
|
+
if (!includeMessageTypes || messageSchemas.length === 0) {
|
|
3626
|
+
return '';
|
|
3627
|
+
}
|
|
3628
|
+
let content = '';
|
|
3629
|
+
|
|
3630
|
+
// Generate a union type for all message payloads
|
|
3631
|
+
const payloadTypes = messageSchemas.map(schema => `${schema.typeName}Payload`).join(' | ');
|
|
3632
|
+
content += '/** Union type for all message payloads */\n';
|
|
3633
|
+
content += `export type MessagePayload = ${payloadTypes};\n\n`;
|
|
3634
|
+
|
|
3635
|
+
// Generate message type constants
|
|
3636
|
+
content += '/** Message type constants */\n';
|
|
3637
|
+
content += 'export const MessageTypes = {\n';
|
|
3638
|
+
messageSchemas.forEach(schema => {
|
|
3639
|
+
content += ` ${schema.typeName.toUpperCase()}: '${schema.name}',\n`;
|
|
3640
|
+
});
|
|
3641
|
+
content += '} as const;\n\n';
|
|
3642
|
+
content += '/** Message type union */\n';
|
|
3643
|
+
content += 'export type MessageType = typeof MessageTypes[keyof typeof MessageTypes];\n\n';
|
|
3644
|
+
return content;
|
|
3645
|
+
}
|
|
3646
|
+
};
|
|
3647
|
+
}
|
|
3648
|
+
;// ../common/src/index.js
|
|
3649
|
+
/**
|
|
3650
|
+
* AsyncAPI Common Template Utilities
|
|
3651
|
+
*
|
|
3652
|
+
* This package provides shared utilities for AsyncAPI template generation,
|
|
3653
|
+
* reducing code duplication across multiple templates.
|
|
3654
|
+
*/
|
|
3655
|
+
|
|
3656
|
+
// String utilities
|
|
3657
|
+
|
|
3658
|
+
|
|
3659
|
+
// Message utilities
|
|
3660
|
+
|
|
3661
|
+
|
|
3662
|
+
// Channel utilities
|
|
3663
|
+
|
|
3664
|
+
|
|
3665
|
+
// Security utilities
|
|
3666
|
+
|
|
3667
|
+
|
|
3668
|
+
// Template utilities
|
|
3669
|
+
|
|
3670
|
+
|
|
3671
|
+
// Operation utilities
|
|
3672
|
+
|
|
3673
|
+
|
|
3674
|
+
// Model generation utilities
|
|
3675
|
+
|
|
3676
|
+
|
|
3677
|
+
|
|
3678
|
+
|
|
3679
|
+
// Re-export all utilities
|
|
3680
|
+
|
|
3681
|
+
|
|
3682
|
+
// Version information
|
|
3683
|
+
const VERSION = '1.0.0';
|
|
3684
|
+
|
|
3685
|
+
// Convenience function to get all utilities in one object
|
|
3686
|
+
function getAllUtilities() {
|
|
3687
|
+
return {
|
|
3688
|
+
string: {
|
|
3689
|
+
toRustIdentifier: toRustIdentifier,
|
|
3690
|
+
toRustTypeName: toRustTypeName,
|
|
3691
|
+
toRustFieldName: toRustFieldName,
|
|
3692
|
+
toRustEnumVariant: toRustEnumVariant,
|
|
3693
|
+
toRustEnumVariantWithSerde: toRustEnumVariantWithSerde,
|
|
3694
|
+
toKebabCase: toKebabCase,
|
|
3695
|
+
toPascalCase: toPascalCase,
|
|
3696
|
+
toSnakeCase: toSnakeCase,
|
|
3697
|
+
toCamelCase: toCamelCase
|
|
3698
|
+
},
|
|
3699
|
+
message: {
|
|
3700
|
+
getMessageTypeName: getMessageTypeName,
|
|
3701
|
+
getMessageRustTypeName: getMessageRustTypeName,
|
|
3702
|
+
getPayloadRustTypeName: getPayloadRustTypeName,
|
|
3703
|
+
getMessageTypeScriptTypeName: getMessageTypeScriptTypeName,
|
|
3704
|
+
getPayloadTypeScriptTypeName: getPayloadTypeScriptTypeName,
|
|
3705
|
+
messageHasPayload: messageHasPayload,
|
|
3706
|
+
getMessageContentType: getMessageContentType
|
|
3707
|
+
},
|
|
3708
|
+
channel: {
|
|
3709
|
+
getNatsSubject: getNatsSubject,
|
|
3710
|
+
getChannelAddress: getChannelAddress,
|
|
3711
|
+
isDynamicChannel: isDynamicChannel,
|
|
3712
|
+
extractChannelVariables: extractChannelVariables,
|
|
3713
|
+
extractChannelParameters: extractChannelParameters,
|
|
3714
|
+
getChannelParameters: getChannelParameters,
|
|
3715
|
+
resolveChannelAddress: resolveChannelAddress,
|
|
3716
|
+
channelHasParameters: channelHasParameters,
|
|
3717
|
+
generateChannelParameterArgs: generateChannelParameterArgs,
|
|
3718
|
+
generateChannelFormatting: generateChannelFormatting,
|
|
3719
|
+
generateTypeScriptChannelParameterArgs: generateTypeScriptChannelParameterArgs,
|
|
3720
|
+
generateTypeScriptChannelFormatting: generateTypeScriptChannelFormatting,
|
|
3721
|
+
extractServerNameFromRef: extractServerNameFromRef,
|
|
3722
|
+
analyzeChannelServerMappings: analyzeChannelServerMappings,
|
|
3723
|
+
isChannelAllowedOnServer: isChannelAllowedOnServer
|
|
3724
|
+
},
|
|
3725
|
+
security: {
|
|
3726
|
+
analyzeOperationSecurity: analyzeOperationSecurity,
|
|
3727
|
+
operationHasSecurity: operationHasSecurity,
|
|
3728
|
+
operationRequiresAuth: operationRequiresAuth,
|
|
3729
|
+
hasSecuritySchemes: hasSecuritySchemes,
|
|
3730
|
+
getSecuritySchemeType: getSecuritySchemeType,
|
|
3731
|
+
getSecuritySchemeName: getSecuritySchemeName,
|
|
3732
|
+
getSecuritySchemeLocation: getSecuritySchemeLocation,
|
|
3733
|
+
extractOperationSecurityMap: extractOperationSecurityMap,
|
|
3734
|
+
getAllSecuritySchemes: getAllSecuritySchemes,
|
|
3735
|
+
isSecuritySchemeType: isSecuritySchemeType,
|
|
3736
|
+
getDefaultPort: getDefaultPort,
|
|
3737
|
+
validateChannelServerReferences: validateChannelServerReferences
|
|
3738
|
+
},
|
|
3739
|
+
template: {
|
|
3740
|
+
isTemplateVariable: isTemplateVariable,
|
|
3741
|
+
extractAsyncApiInfo: extractAsyncApiInfo,
|
|
3742
|
+
resolveTemplateParameters: resolveTemplateParameters,
|
|
3743
|
+
generatePackageJson: generatePackageJson,
|
|
3744
|
+
generateReadmeContent: generateReadmeContent,
|
|
3745
|
+
validateTemplateParameters: validateTemplateParameters,
|
|
3746
|
+
generateTypeScriptConfig: generateTypeScriptConfig,
|
|
3747
|
+
generateEslintConfig: generateEslintConfig,
|
|
3748
|
+
formatGenerationDate: formatGenerationDate,
|
|
3749
|
+
generateFileHeader: generateFileHeader
|
|
3750
|
+
},
|
|
3751
|
+
operation: {
|
|
3752
|
+
getOperationName: getOperationName,
|
|
3753
|
+
getOperationAction: getOperationAction,
|
|
3754
|
+
getOperationChannel: getOperationChannel,
|
|
3755
|
+
getOperationMessages: getOperationMessages,
|
|
3756
|
+
isOperationSend: isOperationSend,
|
|
3757
|
+
isOperationReceive: isOperationReceive,
|
|
3758
|
+
getOperationRustFunctionName: getOperationRustFunctionName,
|
|
3759
|
+
getOperationTypeScriptMethodName: getOperationTypeScriptMethodName,
|
|
3760
|
+
getOperationDescription: getOperationDescription,
|
|
3761
|
+
getOperationSummary: getOperationSummary,
|
|
3762
|
+
extractAllOperations: extractAllOperations,
|
|
3763
|
+
groupOperationsByAction: groupOperationsByAction,
|
|
3764
|
+
getOperationTraits: getOperationTraits,
|
|
3765
|
+
operationHasTraits: operationHasTraits,
|
|
3766
|
+
getOperationTags: getOperationTags,
|
|
3767
|
+
generateOperationHandlerName: generateOperationHandlerName
|
|
3768
|
+
},
|
|
3769
|
+
models: {
|
|
3770
|
+
generateRustModels: generateRustModels,
|
|
3771
|
+
generateMessageEnvelope: generateMessageEnvelope,
|
|
3772
|
+
generateTypeScriptModels: generateTypeScriptModels
|
|
3773
|
+
}
|
|
3774
|
+
};
|
|
3775
|
+
}
|
|
3776
|
+
export { VERSION, analyzeChannelServerMappings, analyzeOperationSecurity, channelHasParameters, extractAllOperations, extractAsyncApiInfo, extractChannelParameters, extractChannelVariables, extractOperationSecurityMap, extractServerNameFromRef, formatGenerationDate, generateChannelFormatting, generateChannelParameterArgs, generateEslintConfig, generateFileHeader, generateMessageEnvelope, generateOperationHandlerName, generatePackageJson, generateReadmeContent, generateRustModels, generateTypeScriptChannelFormatting, generateTypeScriptChannelParameterArgs, generateTypeScriptConfig, generateTypeScriptModels, getAllSecuritySchemes, getAllUtilities, getChannelAddress, getChannelParameters, getDefaultPort, getMessageContentType, getMessageRustTypeName, getMessageTypeName, getMessageTypeScriptTypeName, getNatsSubject, getOperationAction, getOperationChannel, getOperationDescription, getOperationMessages, getOperationName, getOperationRustFunctionName, getOperationSummary, getOperationTags, getOperationTraits, getOperationTypeScriptMethodName, getPayloadRustTypeName, getPayloadTypeScriptTypeName, getSecuritySchemeLocation, getSecuritySchemeName, getSecuritySchemeType, groupOperationsByAction, hasSecuritySchemes, isChannelAllowedOnServer, isDynamicChannel, isOperationReceive, isOperationSend, isSecuritySchemeType, isTemplateVariable, messageHasPayload, operationHasSecurity, operationHasTraits, operationRequiresAuth, resolveChannelAddress, resolveTemplateParameters, toCamelCase, toKebabCase, toPascalCase, toRustEnumVariant, toRustEnumVariantWithSerde, toRustFieldName, toRustIdentifier, toRustTypeName, toSnakeCase, validateChannelServerReferences, validateTemplateParameters };
|