@ioka-technologies/asyncapi-rust-client-template 0.0.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +262 -0
- package/package.json +52 -0
- package/template/Cargo.toml.js +100 -0
- package/template/helpers/index.js +501 -0
- package/template/index.js +244 -0
- package/template/package.json +48 -0
- package/template/src/auth.rs.js +221 -0
- package/template/src/client.rs.js +681 -0
- package/template/src/envelope.rs.js +199 -0
- package/template/src/errors.rs.js +49 -0
- package/template/src/lib.rs.js +112 -0
- package/template/src/models.rs.js +625 -0
|
@@ -0,0 +1,501 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared helper functions for Rust AsyncAPI NATS client template generation
|
|
3
|
+
*
|
|
4
|
+
* This module consolidates common utility functions used across multiple template files
|
|
5
|
+
* to reduce code duplication and ensure consistency.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Converts a string to a valid Rust identifier
|
|
10
|
+
* Handles special characters, keywords, and ensures valid Rust naming conventions
|
|
11
|
+
*
|
|
12
|
+
* @param {string} str - Input string to convert
|
|
13
|
+
* @returns {string} Valid Rust identifier
|
|
14
|
+
*/
|
|
15
|
+
export function toRustIdentifier(str) {
|
|
16
|
+
if (!str) return 'unknown';
|
|
17
|
+
let identifier = str
|
|
18
|
+
.replace(/[^a-zA-Z0-9_]/g, '_')
|
|
19
|
+
.replace(/^[0-9]/, '_$&')
|
|
20
|
+
.replace(/_+/g, '_')
|
|
21
|
+
.replace(/^_+|_+$/g, '');
|
|
22
|
+
if (/^[0-9]/.test(identifier)) {
|
|
23
|
+
identifier = 'item_' + identifier;
|
|
24
|
+
}
|
|
25
|
+
if (!identifier) {
|
|
26
|
+
identifier = 'unknown';
|
|
27
|
+
}
|
|
28
|
+
const rustKeywords = [
|
|
29
|
+
'as', 'break', 'const', 'continue', 'crate', 'else', 'enum', 'extern',
|
|
30
|
+
'false', 'fn', 'for', 'if', 'impl', 'in', 'let', 'loop', 'match',
|
|
31
|
+
'mod', 'move', 'mut', 'pub', 'ref', 'return', 'self', 'Self',
|
|
32
|
+
'static', 'struct', 'super', 'trait', 'true', 'type', 'unsafe',
|
|
33
|
+
'use', 'where', 'while', 'async', 'await', 'dyn'
|
|
34
|
+
];
|
|
35
|
+
if (rustKeywords.includes(identifier)) {
|
|
36
|
+
identifier = identifier + '_';
|
|
37
|
+
}
|
|
38
|
+
return identifier;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Converts a string to PascalCase Rust type name
|
|
43
|
+
* Handles camelCase, snake_case, and kebab-case inputs
|
|
44
|
+
*
|
|
45
|
+
* @param {string} str - Input string to convert
|
|
46
|
+
* @returns {string} PascalCase Rust type name
|
|
47
|
+
*/
|
|
48
|
+
export function toRustTypeName(str) {
|
|
49
|
+
if (!str) return 'Unknown';
|
|
50
|
+
|
|
51
|
+
// Ensure str is a string
|
|
52
|
+
const strValue = String(str);
|
|
53
|
+
const identifier = toRustIdentifier(strValue);
|
|
54
|
+
|
|
55
|
+
// Handle camelCase and PascalCase inputs by splitting on capital letters too
|
|
56
|
+
const parts = identifier
|
|
57
|
+
.replace(/([a-z])([A-Z])/g, '$1_$2') // Insert underscore before capital letters
|
|
58
|
+
.split(/[_\s-]+/) // Split on underscores, spaces, and hyphens
|
|
59
|
+
.filter(part => part.length > 0);
|
|
60
|
+
|
|
61
|
+
return parts
|
|
62
|
+
.map(part => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase())
|
|
63
|
+
.join('');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Converts a string to snake_case Rust field name
|
|
68
|
+
*
|
|
69
|
+
* @param {string} str - Input string to convert
|
|
70
|
+
* @returns {string} snake_case Rust field name
|
|
71
|
+
*/
|
|
72
|
+
export function toRustFieldName(str) {
|
|
73
|
+
if (!str) return 'unknown';
|
|
74
|
+
const identifier = toRustIdentifier(str);
|
|
75
|
+
return identifier
|
|
76
|
+
.replace(/([A-Z])/g, '_$1')
|
|
77
|
+
.toLowerCase()
|
|
78
|
+
.replace(/^_/, '')
|
|
79
|
+
.replace(/_+/g, '_');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Converts a string to Rust enum variant name (PascalCase)
|
|
84
|
+
*
|
|
85
|
+
* @param {string} str - Input string to convert
|
|
86
|
+
* @returns {string} PascalCase enum variant name
|
|
87
|
+
*/
|
|
88
|
+
export function toRustEnumVariant(str) {
|
|
89
|
+
if (!str) return 'Unknown';
|
|
90
|
+
return str
|
|
91
|
+
.split(/[-_\s]+/)
|
|
92
|
+
.map(part => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase())
|
|
93
|
+
.join('');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Converts a string to Rust enum variant with serde rename for lowercase serialization
|
|
98
|
+
*
|
|
99
|
+
* @param {string} str - Input string to convert
|
|
100
|
+
* @returns {object} Object with rustName (PascalCase) and serializedName (lowercase)
|
|
101
|
+
*/
|
|
102
|
+
export function toRustEnumVariantWithSerde(str) {
|
|
103
|
+
if (!str) return { rustName: 'Unknown', serializedName: 'unknown' };
|
|
104
|
+
|
|
105
|
+
const rustName = str
|
|
106
|
+
.split(/[-_\s]+/)
|
|
107
|
+
.map(part => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase())
|
|
108
|
+
.join('');
|
|
109
|
+
|
|
110
|
+
const serializedName = str.toLowerCase();
|
|
111
|
+
|
|
112
|
+
return { rustName, serializedName };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Gets the message type name from a message object
|
|
117
|
+
*
|
|
118
|
+
* @param {object} message - AsyncAPI message object
|
|
119
|
+
* @returns {string|null} Message type name or null if not found
|
|
120
|
+
*/
|
|
121
|
+
export function getMessageTypeName(message) {
|
|
122
|
+
if (!message) return null;
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
// Try AsyncAPI 3.x format first - check _meta and _json properties
|
|
126
|
+
if (message._meta && message._meta.id) {
|
|
127
|
+
return message._meta.id;
|
|
128
|
+
}
|
|
129
|
+
if (message._json && message._json['x-parser-message-name']) {
|
|
130
|
+
return message._json['x-parser-message-name'];
|
|
131
|
+
}
|
|
132
|
+
if (message._json && message._json['x-parser-unique-object-id']) {
|
|
133
|
+
return message._json['x-parser-unique-object-id'];
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Try different ways to get the message name
|
|
137
|
+
if (message.name && typeof message.name === 'function') {
|
|
138
|
+
return message.name();
|
|
139
|
+
}
|
|
140
|
+
if (message.name && typeof message.name === 'string') {
|
|
141
|
+
return message.name;
|
|
142
|
+
}
|
|
143
|
+
if (message.title && typeof message.title === 'function') {
|
|
144
|
+
return message.title();
|
|
145
|
+
}
|
|
146
|
+
if (message.title && typeof message.title === 'string') {
|
|
147
|
+
return message.title;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Try to extract from $ref
|
|
151
|
+
if (message.$ref) {
|
|
152
|
+
return message.$ref.split('/').pop();
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return null;
|
|
156
|
+
} catch (e) {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Gets the proper Rust type name from a message
|
|
163
|
+
*
|
|
164
|
+
* @param {object} message - AsyncAPI message object
|
|
165
|
+
* @returns {string} Rust type name
|
|
166
|
+
*/
|
|
167
|
+
export function getMessageRustTypeName(message) {
|
|
168
|
+
const messageName = getMessageTypeName(message);
|
|
169
|
+
return messageName ? toRustTypeName(messageName) : 'UnknownMessage';
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Gets the payload component schema Rust type name from a message
|
|
174
|
+
* This extracts the actual payload type (component schema) rather than the message wrapper
|
|
175
|
+
*
|
|
176
|
+
* @param {object} message - AsyncAPI message object
|
|
177
|
+
* @returns {string} Rust type name for the payload component schema
|
|
178
|
+
*/
|
|
179
|
+
export function getPayloadRustTypeName(message) {
|
|
180
|
+
if (!message) return 'UnknownPayload';
|
|
181
|
+
|
|
182
|
+
try {
|
|
183
|
+
// First priority: For inline message schemas, get the message name itself
|
|
184
|
+
// This handles cases where the message is defined inline in channels
|
|
185
|
+
const messageName = getMessageTypeName(message);
|
|
186
|
+
if (messageName) {
|
|
187
|
+
// Check if this is a component message reference (has payload.$ref)
|
|
188
|
+
let payload = null;
|
|
189
|
+
if (message.payload && typeof message.payload === 'function') {
|
|
190
|
+
payload = message.payload();
|
|
191
|
+
} else if (message.payload) {
|
|
192
|
+
payload = message.payload;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Check the message's _json for payload information
|
|
196
|
+
const messageJson = message._json || message;
|
|
197
|
+
if (!payload && messageJson.payload) {
|
|
198
|
+
payload = messageJson.payload;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// If payload has a $ref, this is a component message - extract the schema name
|
|
202
|
+
if (payload && payload.$ref) {
|
|
203
|
+
const refParts = payload.$ref.split('/');
|
|
204
|
+
const schemaName = refParts[refParts.length - 1];
|
|
205
|
+
return toRustTypeName(schemaName);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// For inline message schemas, use the message name directly as the payload type
|
|
209
|
+
// This is the correct approach for messages defined inline in channels
|
|
210
|
+
return toRustTypeName(messageName);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Second priority: Try to get the payload schema reference from the message
|
|
214
|
+
let payload = null;
|
|
215
|
+
|
|
216
|
+
// Try different ways to access the payload
|
|
217
|
+
if (message.payload && typeof message.payload === 'function') {
|
|
218
|
+
payload = message.payload();
|
|
219
|
+
} else if (message.payload) {
|
|
220
|
+
payload = message.payload;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (payload) {
|
|
224
|
+
// Check for $ref in the payload (direct reference to component schema)
|
|
225
|
+
if (payload.$ref) {
|
|
226
|
+
const refParts = payload.$ref.split('/');
|
|
227
|
+
const schemaName = refParts[refParts.length - 1];
|
|
228
|
+
return toRustTypeName(schemaName);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Check for resolved $ref using x-parser-schema-id
|
|
232
|
+
if (payload['x-parser-schema-id']) {
|
|
233
|
+
return toRustTypeName(payload['x-parser-schema-id']);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Check for x-parser-schema-id in _json
|
|
237
|
+
if (payload._json && payload._json['x-parser-schema-id']) {
|
|
238
|
+
return toRustTypeName(payload._json['x-parser-schema-id']);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Check for title or name in the payload schema
|
|
242
|
+
if (payload.title) {
|
|
243
|
+
const title = typeof payload.title === 'function' ? payload.title() : payload.title;
|
|
244
|
+
if (title) return toRustTypeName(title);
|
|
245
|
+
}
|
|
246
|
+
if (payload.name) {
|
|
247
|
+
const name = typeof payload.name === 'function' ? payload.name() : payload.name;
|
|
248
|
+
if (name) return toRustTypeName(name);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Check the message's _json for payload information
|
|
253
|
+
const messageJson = message._json || message;
|
|
254
|
+
if (messageJson.payload) {
|
|
255
|
+
if (messageJson.payload.$ref) {
|
|
256
|
+
const refParts = messageJson.payload.$ref.split('/');
|
|
257
|
+
const schemaName = refParts[refParts.length - 1];
|
|
258
|
+
return toRustTypeName(schemaName);
|
|
259
|
+
}
|
|
260
|
+
if (messageJson.payload['x-parser-schema-id']) {
|
|
261
|
+
return toRustTypeName(messageJson.payload['x-parser-schema-id']);
|
|
262
|
+
}
|
|
263
|
+
if (messageJson.payload.title) {
|
|
264
|
+
return toRustTypeName(messageJson.payload.title);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Final fallback: try to extract from message title or name directly
|
|
269
|
+
if (message.title && typeof message.title === 'function') {
|
|
270
|
+
const title = message.title();
|
|
271
|
+
if (title && typeof title === 'string') {
|
|
272
|
+
return toRustTypeName(title);
|
|
273
|
+
}
|
|
274
|
+
} else if (message.title && typeof message.title === 'string') {
|
|
275
|
+
return toRustTypeName(message.title);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (message.name && typeof message.name === 'function') {
|
|
279
|
+
const name = message.name();
|
|
280
|
+
if (name && typeof name === 'string') {
|
|
281
|
+
return toRustTypeName(name);
|
|
282
|
+
}
|
|
283
|
+
} else if (message.name && typeof message.name === 'string') {
|
|
284
|
+
return toRustTypeName(message.name);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Check message._json for title/name
|
|
288
|
+
if (messageJson.title && typeof messageJson.title === 'string') {
|
|
289
|
+
return toRustTypeName(messageJson.title);
|
|
290
|
+
}
|
|
291
|
+
if (messageJson.name && typeof messageJson.name === 'string') {
|
|
292
|
+
return toRustTypeName(messageJson.name);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return 'UnknownPayload';
|
|
296
|
+
} catch (e) {
|
|
297
|
+
console.warn('Error extracting payload type name:', e.message);
|
|
298
|
+
return 'UnknownPayload';
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Analyzes operations to determine client method patterns
|
|
304
|
+
* For NATS clients, we need to distinguish between:
|
|
305
|
+
* - Request/Reply operations (using NATS request/reply)
|
|
306
|
+
* - Publish operations (fire-and-forget)
|
|
307
|
+
* - Subscribe operations (message handlers)
|
|
308
|
+
*
|
|
309
|
+
* @param {Array} operations - Array of AsyncAPI operations
|
|
310
|
+
* @returns {Array} Array of client method patterns
|
|
311
|
+
*/
|
|
312
|
+
export function analyzeClientOperations(operations) {
|
|
313
|
+
const patterns = [];
|
|
314
|
+
|
|
315
|
+
for (const operation of operations) {
|
|
316
|
+
const operationName = operation.id();
|
|
317
|
+
const action = operation.action();
|
|
318
|
+
const messages = operation.messages();
|
|
319
|
+
|
|
320
|
+
if (action === 'send') {
|
|
321
|
+
// Client sends messages - this becomes a client method
|
|
322
|
+
if (operation.reply && operation.reply()) {
|
|
323
|
+
// Request/Reply pattern
|
|
324
|
+
patterns.push({
|
|
325
|
+
type: 'request_reply',
|
|
326
|
+
operation,
|
|
327
|
+
operationName,
|
|
328
|
+
methodName: toRustFieldName(operationName),
|
|
329
|
+
requestMessage: messages[0],
|
|
330
|
+
responseMessage: operation.reply().messages()[0],
|
|
331
|
+
requestType: getPayloadRustTypeName(messages[0]),
|
|
332
|
+
responseType: getPayloadRustTypeName(operation.reply().messages()[0])
|
|
333
|
+
});
|
|
334
|
+
} else {
|
|
335
|
+
// Publish pattern (fire-and-forget)
|
|
336
|
+
patterns.push({
|
|
337
|
+
type: 'publish',
|
|
338
|
+
operation,
|
|
339
|
+
operationName,
|
|
340
|
+
methodName: toRustFieldName(operationName),
|
|
341
|
+
message: messages[0],
|
|
342
|
+
payloadType: getPayloadRustTypeName(messages[0])
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
} else if (action === 'receive') {
|
|
346
|
+
// Client receives messages - this becomes a subscription method
|
|
347
|
+
patterns.push({
|
|
348
|
+
type: 'subscribe',
|
|
349
|
+
operation,
|
|
350
|
+
operationName,
|
|
351
|
+
methodName: toRustFieldName(operationName.replace(/^receive/, 'subscribe_to_')),
|
|
352
|
+
message: messages[0],
|
|
353
|
+
payloadType: getPayloadRustTypeName(messages[0])
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
return patterns;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Gets the NATS subject from a channel address
|
|
363
|
+
*
|
|
364
|
+
* @param {object} channel - AsyncAPI channel object
|
|
365
|
+
* @returns {string} NATS subject
|
|
366
|
+
*/
|
|
367
|
+
export function getNatsSubject(channel) {
|
|
368
|
+
try {
|
|
369
|
+
if (channel.address && typeof channel.address === 'function') {
|
|
370
|
+
return channel.address();
|
|
371
|
+
} else if (channel.address) {
|
|
372
|
+
return channel.address;
|
|
373
|
+
} else if (channel.id && typeof channel.id === 'function') {
|
|
374
|
+
return channel.id();
|
|
375
|
+
} else if (channel.id) {
|
|
376
|
+
return channel.id;
|
|
377
|
+
}
|
|
378
|
+
return 'unknown.subject';
|
|
379
|
+
} catch (e) {
|
|
380
|
+
return 'unknown.subject';
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Checks if a channel address contains variables (dynamic channel)
|
|
386
|
+
*
|
|
387
|
+
* @param {string} address - Channel address
|
|
388
|
+
* @returns {boolean} True if the address contains variables
|
|
389
|
+
*/
|
|
390
|
+
export function isDynamicChannel(address) {
|
|
391
|
+
if (!address || typeof address !== 'string') return false;
|
|
392
|
+
return /\{[^}]+\}/.test(address);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Extracts variable names from a channel address
|
|
397
|
+
*
|
|
398
|
+
* @param {string} address - Channel address with variables
|
|
399
|
+
* @returns {Array<string>} Array of variable names
|
|
400
|
+
*/
|
|
401
|
+
export function extractChannelVariables(address) {
|
|
402
|
+
if (!address || typeof address !== 'string') return [];
|
|
403
|
+
const matches = address.match(/\{([^}]+)\}/g);
|
|
404
|
+
if (!matches) return [];
|
|
405
|
+
return matches.map(match => match.slice(1, -1)); // Remove { and }
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Gets channel parameters from a channel object
|
|
410
|
+
*
|
|
411
|
+
* @param {object} channel - AsyncAPI channel object
|
|
412
|
+
* @returns {Array<object>} Array of parameter objects with name and description
|
|
413
|
+
*/
|
|
414
|
+
export function getChannelParameters(channel) {
|
|
415
|
+
try {
|
|
416
|
+
const parameters = [];
|
|
417
|
+
|
|
418
|
+
// Try to get parameters from the channel
|
|
419
|
+
let channelParams = null;
|
|
420
|
+
if (channel.parameters && typeof channel.parameters === 'function') {
|
|
421
|
+
channelParams = channel.parameters();
|
|
422
|
+
} else if (channel.parameters) {
|
|
423
|
+
channelParams = channel.parameters;
|
|
424
|
+
} else if (channel._json && channel._json.parameters) {
|
|
425
|
+
channelParams = channel._json.parameters;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
if (channelParams) {
|
|
429
|
+
// Handle different parameter formats
|
|
430
|
+
if (typeof channelParams === 'object') {
|
|
431
|
+
for (const [paramName, paramDef] of Object.entries(channelParams)) {
|
|
432
|
+
// Skip internal AsyncAPI parser properties
|
|
433
|
+
if (paramName.startsWith('_') || paramName === 'collections' || paramName === 'meta') {
|
|
434
|
+
continue;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
let description = 'Channel parameter';
|
|
438
|
+
|
|
439
|
+
if (paramDef && typeof paramDef === 'object') {
|
|
440
|
+
if (typeof paramDef.description === 'string') {
|
|
441
|
+
description = paramDef.description;
|
|
442
|
+
} else if (typeof paramDef.description === 'function') {
|
|
443
|
+
try {
|
|
444
|
+
description = paramDef.description();
|
|
445
|
+
} catch (e) {
|
|
446
|
+
description = 'Channel parameter';
|
|
447
|
+
}
|
|
448
|
+
} else if (paramDef._json && paramDef._json.description) {
|
|
449
|
+
description = paramDef._json.description;
|
|
450
|
+
}
|
|
451
|
+
} else if (typeof paramDef === 'string') {
|
|
452
|
+
description = paramDef;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
parameters.push({
|
|
456
|
+
name: paramName,
|
|
457
|
+
description: description,
|
|
458
|
+
rustName: toRustFieldName(paramName),
|
|
459
|
+
rustType: 'String' // For now, assume all parameters are strings
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
return parameters;
|
|
466
|
+
} catch (e) {
|
|
467
|
+
console.warn('Error extracting channel parameters:', e.message);
|
|
468
|
+
return [];
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* Resolves a dynamic channel address with provided variable values
|
|
474
|
+
*
|
|
475
|
+
* @param {string} address - Channel address template with variables
|
|
476
|
+
* @param {object} variables - Object mapping variable names to values
|
|
477
|
+
* @returns {string} Resolved channel address
|
|
478
|
+
*/
|
|
479
|
+
export function resolveChannelAddress(address, variables) {
|
|
480
|
+
if (!address || typeof address !== 'string') return address;
|
|
481
|
+
if (!variables || typeof variables !== 'object') return address;
|
|
482
|
+
|
|
483
|
+
let resolved = address;
|
|
484
|
+
for (const [varName, varValue] of Object.entries(variables)) {
|
|
485
|
+
const placeholder = `{${varName}}`;
|
|
486
|
+
resolved = resolved.replace(new RegExp(placeholder.replace(/[{}]/g, '\\$&'), 'g'), varValue);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
return resolved;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/**
|
|
493
|
+
* Checks if a channel has dynamic parameters
|
|
494
|
+
*
|
|
495
|
+
* @param {object} channel - AsyncAPI channel object
|
|
496
|
+
* @returns {boolean} True if the channel has parameters
|
|
497
|
+
*/
|
|
498
|
+
export function channelHasParameters(channel) {
|
|
499
|
+
const address = getNatsSubject(channel);
|
|
500
|
+
return isDynamicChannel(address);
|
|
501
|
+
}
|