@ioka-technologies/asyncapi-rust-client-template 0.0.21 → 0.0.23

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ioka-technologies/asyncapi-rust-client-template",
3
- "version": "0.0.21",
3
+ "version": "0.0.23",
4
4
  "description": "AsyncAPI template for generating Rust NATS clients",
5
5
  "main": "template/index.js",
6
6
  "keywords": [
@@ -14,12 +14,21 @@
14
14
  "author": "AsyncAPI Generator",
15
15
  "license": "Apache-2.0",
16
16
  "dependencies": {
17
- "@asyncapi/generator-react-sdk": "^1.0.0"
17
+ "@asyncapi/generator-react-sdk": "^1.0.20"
18
18
  },
19
19
  "scripts": {
20
+ "build": "webpack --config ../webpack.config.js --env template=rust-client",
21
+ "build:dev": "webpack --config ../webpack.config.js --env template=rust-client --mode development",
22
+ "prepublishOnly": "npm run build",
20
23
  "test": "npm run test:nats",
21
- "test:nats": "asyncapi generate fromTemplate ../examples/nats/asyncapi.yaml ./ -o test-output-nats --force-write && echo 'Generated library files:' && ls -la test-output-nats/ && cd test-output-nats && cargo build --lib"
24
+ "test:nats": "asyncapi generate fromTemplate ../examples/nats/asyncapi.yaml ./ -o test-output-nats --force-write && echo 'Generated library files:' && ls -la test-output-nats/ && cd test-output-nats && cargo build --lib",
25
+ "clean": "rm -rf dist test-output-*"
22
26
  },
27
+ "files": [
28
+ "template/**/*",
29
+ "dist/common/**/*",
30
+ "README.md"
31
+ ],
23
32
  "generator": {
24
33
  "renderer": "react",
25
34
  "apiVersion": "v3",
@@ -1,49 +1,24 @@
1
1
  /* eslint-disable no-unused-vars */
2
2
  import { File } from '@asyncapi/generator-react-sdk';
3
+ import {
4
+ toKebabCase,
5
+ toSnakeCase,
6
+ isTemplateVariable,
7
+ extractAsyncApiInfo,
8
+ resolveTemplateParameters
9
+ } from './helpers/index.js';
3
10
 
4
11
  module.exports = function ({ asyncapi, params }) {
5
- // Helper function to convert title to kebab-case
6
- function toKebabCase(str) {
7
- return str.replace(/[^a-zA-Z0-9]/g, '-')
8
- .toLowerCase()
9
- .replace(/-+/g, '-')
10
- .replace(/^-|-$/g, '');
11
- }
12
+ const asyncApiInfo = extractAsyncApiInfo(asyncapi);
13
+ const { title, version, description } = asyncApiInfo;
12
14
 
13
- // Helper function to convert title to snake_case
14
- function toSnakeCase(str) {
15
- return str.replace(/[^a-zA-Z0-9]/g, '_')
16
- .toLowerCase()
17
- .replace(/_+/g, '_')
18
- .replace(/^_|_$/g, '');
19
- }
15
+ const resolvedParams = resolveTemplateParameters(params, asyncApiInfo);
16
+ const { packageName, packageVersion, author, license } = resolvedParams;
20
17
 
21
- const info = asyncapi.info();
22
- const title = info.title();
23
- const description = (info.description() || `Generated Rust NATS client for ${title}`)
18
+ const finalDescription = (description || `Generated Rust NATS client for ${title}`)
24
19
  .replace(/"/g, '\\"')
25
20
  .replace(/\n/g, ' ')
26
21
  .trim();
27
- const version = info.version();
28
-
29
- // Helper function to check if a parameter contains unresolved template variables
30
- function isTemplateVariable(value) {
31
- return typeof value === 'string' && value.includes('{{') && value.includes('}}');
32
- }
33
-
34
- // Resolve parameters with fallbacks
35
- const packageName = (params.packageName && !isTemplateVariable(params.packageName))
36
- ? params.packageName
37
- : toKebabCase(title) + '-client';
38
- const packageVersion = (params.packageVersion && !isTemplateVariable(params.packageVersion))
39
- ? params.packageVersion
40
- : version;
41
- const author = (params.author && !isTemplateVariable(params.author))
42
- ? params.author
43
- : 'AsyncAPI Generator';
44
- const license = (params.license && !isTemplateVariable(params.license))
45
- ? params.license
46
- : 'Apache-2.0';
47
22
 
48
23
  return (
49
24
  <File name="Cargo.toml">
@@ -53,7 +28,7 @@ version = "${packageVersion}"
53
28
  edition = "2021"
54
29
  authors = ["${author}"]
55
30
  license = "${license}"
56
- description = "${description}"
31
+ description = "${finalDescription}"
57
32
  repository = "https://github.com/your-org/${packageName}"
58
33
  documentation = "https://docs.rs/${packageName}"
59
34
  keywords = ["asyncapi", "nats", "client", "messaging"]
@@ -1,303 +1,58 @@
1
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
- }
2
+ * Template-specific helper functions for Rust client template
3
+ *
4
+ * Most common utilities have been moved to the shared @common package.
5
+ * This file now only contains template-specific functions that are unique
6
+ * to the rust-client template.
7
+ */
8
+
9
+ // Import common utilities for use in local functions
10
+ import {
11
+ toRustIdentifier,
12
+ toRustTypeName,
13
+ toRustFieldName,
14
+ toRustEnumVariant,
15
+ toRustEnumVariantWithSerde,
16
+ getMessageTypeName,
17
+ getMessageRustTypeName,
18
+ getPayloadRustTypeName,
19
+ getNatsSubject,
20
+ isDynamicChannel,
21
+ extractChannelVariables,
22
+ getChannelParameters,
23
+ resolveChannelAddress,
24
+ channelHasParameters,
25
+ toPascalCase,
26
+ toKebabCase,
27
+ toSnakeCase,
28
+ isTemplateVariable,
29
+ extractAsyncApiInfo,
30
+ resolveTemplateParameters
31
+ } from "../dist/common/index.js";
32
+
33
+ // Re-export common utilities for backward compatibility
34
+ export {
35
+ toRustIdentifier,
36
+ toRustTypeName,
37
+ toRustFieldName,
38
+ toRustEnumVariant,
39
+ toRustEnumVariantWithSerde,
40
+ getMessageTypeName,
41
+ getMessageRustTypeName,
42
+ getPayloadRustTypeName,
43
+ getNatsSubject,
44
+ isDynamicChannel,
45
+ extractChannelVariables,
46
+ getChannelParameters,
47
+ resolveChannelAddress,
48
+ channelHasParameters,
49
+ toPascalCase,
50
+ toKebabCase,
51
+ toSnakeCase,
52
+ isTemplateVariable,
53
+ extractAsyncApiInfo,
54
+ resolveTemplateParameters
55
+ };
301
56
 
302
57
  /**
303
58
  * Analyzes operations to determine client method patterns
@@ -306,6 +61,8 @@ export function getPayloadRustTypeName(message) {
306
61
  * - Publish operations (fire-and-forget)
307
62
  * - Subscribe operations (message handlers)
308
63
  *
64
+ * This function is specific to the rust-client template's operation analysis needs.
65
+ *
309
66
  * @param {Array} operations - Array of AsyncAPI operations
310
67
  * @returns {Array} Array of client method patterns
311
68
  */
@@ -357,145 +114,3 @@ export function analyzeClientOperations(operations) {
357
114
 
358
115
  return patterns;
359
116
  }
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
- }