@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.
@@ -0,0 +1,681 @@
1
+ /* eslint-disable no-unused-vars */
2
+ import { File } from '@asyncapi/generator-react-sdk';
3
+ import {
4
+ toRustIdentifier,
5
+ toRustTypeName,
6
+ toRustFieldName,
7
+ getPayloadRustTypeName,
8
+ analyzeClientOperations,
9
+ getNatsSubject,
10
+ isDynamicChannel,
11
+ extractChannelVariables,
12
+ getChannelParameters,
13
+ resolveChannelAddress,
14
+ channelHasParameters
15
+ } from '../helpers/index.js';
16
+
17
+ export default function ClientRs({ asyncapi, params }) {
18
+ const info = asyncapi.info();
19
+ const title = info.title();
20
+
21
+ // Helper function to check if a parameter contains unresolved template variables
22
+ function isTemplateVariable(value) {
23
+ return typeof value === 'string' && value.includes('{{') && value.includes('}}');
24
+ }
25
+
26
+ // Helper function to convert title to PascalCase for struct names
27
+ function toPascalCase(str) {
28
+ return str.replace(/[^a-zA-Z0-9]/g, ' ')
29
+ .split(' ')
30
+ .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
31
+ .join('');
32
+ }
33
+
34
+ // Resolve clientName parameter, falling back to extracted values if parameter contains template variables
35
+ const clientName = (params.clientName && !isTemplateVariable(params.clientName))
36
+ ? params.clientName
37
+ : `${toPascalCase(title)}Client`;
38
+
39
+ // Extract all operations from the AsyncAPI spec
40
+ const allOperations = [];
41
+ const processedOperations = new Set(); // Track processed operations to avoid duplicates
42
+
43
+ // Skip processing operations from the operations collection since we'll get them from channels
44
+ // This avoids duplicate operations and ensures we have proper channel context
45
+
46
+ // Extract channels and their operations (AsyncAPI 3.x style)
47
+ const channelServices = [];
48
+ if (asyncapi.channels) {
49
+ const channels = asyncapi.channels();
50
+ if (channels) {
51
+ for (const channel of channels) {
52
+ try {
53
+ const channelName = channel.id();
54
+ const subject = getNatsSubject(channel);
55
+ const isDynamic = isDynamicChannel(subject);
56
+ const parameters = isDynamic ? getChannelParameters(channel) : [];
57
+
58
+ // Create channel service info
59
+ const channelService = {
60
+ channelName,
61
+ subject,
62
+ isDynamic,
63
+ parameters,
64
+ operations: []
65
+ };
66
+
67
+ if (channel.operations) {
68
+ const operations = channel.operations();
69
+ if (operations) {
70
+ for (const operation of operations) {
71
+ try {
72
+ const operationId = operation.id ? operation.id() : null;
73
+ if (!operationId) continue;
74
+
75
+ const action = operation.action ? operation.action() : 'send';
76
+ const messages = operation.messages ? operation.messages() : null;
77
+
78
+ if (!messages || !messages.all) continue;
79
+
80
+ const messageArray = messages.all();
81
+ if (!messageArray || messageArray.length === 0) continue;
82
+
83
+ const firstMessage = messageArray[0];
84
+ const methodName = toRustFieldName(operationId);
85
+
86
+ if (action === 'send') {
87
+ const reply = operation.reply ? operation.reply() : null;
88
+ if (reply) {
89
+ const replyMessages = reply.messages ? reply.messages() : null;
90
+ if (replyMessages && replyMessages.all) {
91
+ const replyMessageArray = replyMessages.all();
92
+ if (replyMessageArray && replyMessageArray.length > 0) {
93
+ const opInfo = {
94
+ type: 'request_reply',
95
+ operationName: operationId,
96
+ methodName,
97
+ requestType: getPayloadRustTypeName(firstMessage),
98
+ responseType: getPayloadRustTypeName(replyMessageArray[0]),
99
+ subject,
100
+ channelName
101
+ };
102
+ channelService.operations.push(opInfo);
103
+ allOperations.push(opInfo);
104
+ processedOperations.add(operationId);
105
+ continue;
106
+ }
107
+ }
108
+ }
109
+
110
+ const opInfo = {
111
+ type: 'publish',
112
+ operationName: operationId,
113
+ methodName,
114
+ payloadType: getPayloadRustTypeName(firstMessage),
115
+ subject,
116
+ channelName
117
+ };
118
+ channelService.operations.push(opInfo);
119
+ allOperations.push(opInfo);
120
+ processedOperations.add(operationId);
121
+
122
+ // Also add a subscription method for notification-type operations
123
+ // (send operations without replies are typically notifications/events)
124
+ const subscribeOpInfo = {
125
+ type: 'subscribe',
126
+ operationName: operationId,
127
+ methodName: `subscribe_to_${methodName}`,
128
+ payloadType: getPayloadRustTypeName(firstMessage),
129
+ subject,
130
+ channelName
131
+ };
132
+ channelService.operations.push(subscribeOpInfo);
133
+ allOperations.push(subscribeOpInfo);
134
+ } else if (action === 'receive') {
135
+ const opInfo = {
136
+ type: 'subscribe',
137
+ operationName: operationId,
138
+ methodName: toRustFieldName(operationId.replace(/^receive/, 'subscribeTo')),
139
+ payloadType: getPayloadRustTypeName(firstMessage),
140
+ subject,
141
+ channelName
142
+ };
143
+ channelService.operations.push(opInfo);
144
+ allOperations.push(opInfo);
145
+ processedOperations.add(operationId);
146
+ }
147
+ } catch (e) {
148
+ console.warn(`Error processing channel operation: ${e.message}`);
149
+ }
150
+ }
151
+ }
152
+ }
153
+
154
+ if (channelService.operations.length > 0) {
155
+ channelServices.push(channelService);
156
+ }
157
+ } catch (e) {
158
+ console.warn(`Error processing channel: ${e.message}`);
159
+ }
160
+ }
161
+ }
162
+ }
163
+
164
+ // Generate channel service structs for dynamic channels
165
+ function generateChannelServices() {
166
+ const dynamicChannels = channelServices.filter(cs => cs.isDynamic);
167
+ if (dynamicChannels.length === 0) {
168
+ return '';
169
+ }
170
+
171
+ return dynamicChannels.map(channelService => {
172
+ const serviceName = `${toPascalCase(channelService.channelName)}Service`;
173
+
174
+ // Extract variables from the channel address to get the actual parameter names
175
+ const variables = extractChannelVariables(channelService.subject);
176
+ const actualParams = variables.map(varName => ({
177
+ name: varName,
178
+ rustName: toRustFieldName(varName),
179
+ description: channelService.parameters.find(p => p.name === varName)?.description || `${varName} parameter`
180
+ }));
181
+
182
+ const paramSignature = actualParams.map(p => `${p.rustName}: &str`).join(', ');
183
+ const paramArgs = actualParams.map(p => `${p.rustName}: ${p.rustName}.to_string()`).join(', ');
184
+
185
+ const serviceOperations = channelService.operations.map(op => {
186
+ const resolvedSubject = 'resolved_subject';
187
+
188
+ switch (op.type) {
189
+ case 'request_reply':
190
+ return ` /// ${op.operationName} - Request/Reply operation
191
+ ///
192
+ /// Sends a request and waits for a response using NATS request/reply pattern.
193
+ /// This operation uses the NATS Services API for reliable request/response messaging.
194
+ ///
195
+ /// # Arguments
196
+ /// * \`payload\` - Request payload
197
+ ///
198
+ /// # Returns
199
+ /// * \`ClientResult<${op.responseType}>\` - The response from the server
200
+ ///
201
+ /// # Errors
202
+ /// * \`ClientError::Nats\` - NATS operation failed
203
+ /// * \`ClientError::Serialization\` - Failed to serialize/deserialize data
204
+ /// * \`ClientError::Timeout\` - Request timed out
205
+ pub async fn ${op.methodName}(&self, payload: ${op.requestType}) -> ClientResult<${op.responseType}> {
206
+ let envelope = if let Some(ref auth) = self.auth {
207
+ MessageEnvelope::new_with_auth("${op.operationName}", payload, auth)
208
+ .map_err(ClientError::Serialization)?
209
+ } else {
210
+ MessageEnvelope::new("${op.operationName}", payload)
211
+ .map_err(ClientError::Serialization)?
212
+ };
213
+
214
+ let subject = self.${resolvedSubject}.clone();
215
+ let response = self.client
216
+ .request(subject, Bytes::from(envelope.to_bytes()?))
217
+ .await
218
+ .map_err(|e| ClientError::Nats(Box::new(e)))?;
219
+
220
+ let response_envelope = MessageEnvelope::from_bytes(&response.payload)
221
+ .map_err(|e| ClientError::InvalidEnvelope(e.to_string()))?;
222
+
223
+ let result: ${op.responseType} = response_envelope.extract_payload()
224
+ .map_err(ClientError::Serialization)?;
225
+
226
+ Ok(result)
227
+ }`;
228
+
229
+ case 'publish':
230
+ return ` /// ${op.operationName} - Publish operation
231
+ ///
232
+ /// Publishes a message using NATS publish (fire-and-forget).
233
+ /// This is a one-way operation with no response expected.
234
+ ///
235
+ /// # Arguments
236
+ /// * \`payload\` - Message payload to publish
237
+ ///
238
+ /// # Errors
239
+ /// * \`ClientError::Nats\` - NATS operation failed
240
+ /// * \`ClientError::Serialization\` - Failed to serialize data
241
+ pub async fn ${op.methodName}(&self, payload: ${op.payloadType}) -> ClientResult<()> {
242
+ let envelope = if let Some(ref auth) = self.auth {
243
+ MessageEnvelope::new_with_auth("${op.operationName}", payload, auth)
244
+ .map_err(ClientError::Serialization)?
245
+ } else {
246
+ MessageEnvelope::new("${op.operationName}", payload)
247
+ .map_err(ClientError::Serialization)?
248
+ };
249
+
250
+ let subject = self.${resolvedSubject}.clone();
251
+ self.client
252
+ .publish(subject, Bytes::from(envelope.to_bytes()?))
253
+ .await
254
+ .map_err(|e| ClientError::Nats(Box::new(e)))?;
255
+
256
+ Ok(())
257
+ }`;
258
+
259
+ case 'subscribe':
260
+ return ` /// ${op.operationName} - Subscribe operation
261
+ ///
262
+ /// Creates a subscription to receive messages from the specified subject.
263
+ /// Returns a NATS subscriber that can be used to receive messages.
264
+ ///
265
+ /// # Returns
266
+ /// * \`ClientResult<async_nats::Subscriber>\` - NATS subscriber for handling messages
267
+ ///
268
+ /// # Example
269
+ /// \`\`\`no-run
270
+ /// let mut subscriber = service.${op.methodName}().await?;
271
+ /// while let Some(message) = subscriber.next().await {
272
+ /// let envelope = MessageEnvelope::from_bytes(&message.payload)?;
273
+ /// let payload: ${op.payloadType} = envelope.extract_payload()?;
274
+ /// // Handle the message...
275
+ /// }
276
+ /// \`\`\`
277
+ pub async fn ${op.methodName}(&self) -> ClientResult<async_nats::Subscriber> {
278
+ let subject = self.${resolvedSubject}.clone();
279
+ let subscriber = self.client
280
+ .subscribe(subject)
281
+ .await
282
+ .map_err(|e| ClientError::Nats(Box::new(e)))?;
283
+
284
+ Ok(subscriber)
285
+ }`;
286
+
287
+ default:
288
+ return '';
289
+ }
290
+ }).filter(method => method).join('\n\n');
291
+
292
+ return `
293
+ /// ${channelService.channelName} channel service with resolved parameters
294
+ ///
295
+ /// This service provides access to operations on the ${channelService.channelName} channel
296
+ /// with resolved channel parameters for dynamic routing.
297
+ #[derive(Debug, Clone)]
298
+ pub struct ${serviceName} {
299
+ client: async_nats::Client,
300
+ resolved_subject: String,
301
+ auth: Option<AuthCredentials>,
302
+ }
303
+
304
+ impl ${serviceName} {
305
+ /// Create a new ${channelService.channelName} service with resolved parameters
306
+ ///
307
+ /// # Arguments
308
+ ${actualParams.map(p => ` /// * \`${p.rustName}\` - ${p.description}`).join('\n')}
309
+ pub fn new(client: async_nats::Client, ${paramSignature}) -> Self {
310
+ let resolved_subject = "${channelService.subject}".to_string()${actualParams.map((p, index) => {
311
+ return `
312
+ .replace("{${p.name}}", ${p.rustName})`;
313
+ }).join('')};
314
+
315
+ Self {
316
+ client,
317
+ resolved_subject,
318
+ auth: None,
319
+ }
320
+ }
321
+
322
+ /// Get the resolved subject for this channel service
323
+ pub fn subject(&self) -> &str {
324
+ &self.resolved_subject
325
+ }
326
+
327
+ /// Create a new service with authentication credentials
328
+ pub fn with_auth(mut self, auth: AuthCredentials) -> Self {
329
+ self.auth = Some(auth);
330
+ self
331
+ }
332
+
333
+ /// Update authentication credentials
334
+ pub fn update_auth(&mut self, auth: AuthCredentials) {
335
+ self.auth = Some(auth);
336
+ }
337
+
338
+ /// Remove authentication credentials
339
+ pub fn clear_auth(&mut self) {
340
+ self.auth = None;
341
+ }
342
+
343
+ /// Check if authentication credentials are configured
344
+ pub fn has_auth(&self) -> bool {
345
+ self.auth.as_ref().map_or(false, |auth| auth.has_credentials())
346
+ }
347
+
348
+ ${serviceOperations}
349
+ }`;
350
+ }).join('\n');
351
+ }
352
+
353
+ // Generate client methods for static channels and channel service accessors for dynamic channels
354
+ function generateClientMethods() {
355
+ const staticOperations = allOperations.filter(op => {
356
+ const channelService = channelServices.find(cs => cs.channelName === op.channelName);
357
+ return !channelService || !channelService.isDynamic;
358
+ });
359
+
360
+ const dynamicChannelAccessors = channelServices.filter(cs => cs.isDynamic).map(channelService => {
361
+ const serviceName = `${toPascalCase(channelService.channelName)}Service`;
362
+
363
+ // Extract variables from the channel address to get the actual parameter names
364
+ const variables = extractChannelVariables(channelService.subject);
365
+ const actualParams = variables.map(varName => ({
366
+ name: varName,
367
+ rustName: toRustFieldName(varName),
368
+ description: channelService.parameters.find(p => p.name === varName)?.description || `${varName} parameter`
369
+ }));
370
+
371
+ const paramSignature = actualParams.map(p => `${p.rustName}: &str`).join(', ');
372
+
373
+ return ` /// Access ${channelService.channelName} channel operations with parameters
374
+ ///
375
+ /// Returns a service instance configured for the specific channel parameters.
376
+ ///
377
+ /// # Arguments
378
+ ${actualParams.map(p => ` /// * \`${p.rustName}\` - ${p.description}`).join('\n')}
379
+ ///
380
+ /// # Example
381
+ /// \`\`\`ignore
382
+ /// let service = client.${toRustFieldName(channelService.channelName)}(${actualParams.map(p => `"${p.name.replace('_', '-')}"`).join(', ')});
383
+ /// let result = service.${channelService.operations[0]?.methodName || 'operation'}(payload).await?;
384
+ /// \`\`\`
385
+ pub fn ${toRustFieldName(channelService.channelName)}(&self, ${paramSignature}) -> ${serviceName} {
386
+ let mut service = ${serviceName}::new(self.client.clone(), ${actualParams.map(p => p.rustName).join(', ')});
387
+ if let Some(ref auth) = self.auth {
388
+ service.auth = Some(auth.clone());
389
+ }
390
+ service
391
+ }`;
392
+ });
393
+
394
+ const staticMethods = staticOperations.map(pattern => {
395
+ const subject = pattern.subject || pattern.channelName || 'unknown.subject';
396
+
397
+ switch (pattern.type) {
398
+ case 'request_reply':
399
+ return ` /// ${pattern.operationName} - Request/Reply operation
400
+ ///
401
+ /// Sends a request and waits for a response using NATS request/reply pattern.
402
+ /// This operation uses the NATS Services API for reliable request/response messaging.
403
+ ///
404
+ /// # Arguments
405
+ /// * \`payload\` - Request payload
406
+ ///
407
+ /// # Returns
408
+ /// * \`ClientResult<${pattern.responseType}>\` - The response from the server
409
+ ///
410
+ /// # Errors
411
+ /// * \`ClientError::Nats\` - NATS operation failed
412
+ /// * \`ClientError::Serialization\` - Failed to serialize/deserialize data
413
+ /// * \`ClientError::Timeout\` - Request timed out
414
+ pub async fn ${pattern.methodName}(&self, payload: ${pattern.requestType}) -> ClientResult<${pattern.responseType}> {
415
+ let envelope = if let Some(ref auth) = self.auth {
416
+ MessageEnvelope::new_with_auth("${pattern.operationName}", payload, auth)
417
+ .map_err(ClientError::Serialization)?
418
+ } else {
419
+ MessageEnvelope::new("${pattern.operationName}", payload)
420
+ .map_err(ClientError::Serialization)?
421
+ };
422
+
423
+ let response = self.client
424
+ .request("${subject}", Bytes::from(envelope.to_bytes()?))
425
+ .await
426
+ .map_err(|e| ClientError::Nats(Box::new(e)))?;
427
+
428
+ let response_envelope = MessageEnvelope::from_bytes(&response.payload)
429
+ .map_err(|e| ClientError::InvalidEnvelope(e.to_string()))?;
430
+
431
+ let result: ${pattern.responseType} = response_envelope.extract_payload()
432
+ .map_err(ClientError::Serialization)?;
433
+
434
+ Ok(result)
435
+ }`;
436
+
437
+ case 'publish':
438
+ return ` /// ${pattern.operationName} - Publish operation
439
+ ///
440
+ /// Publishes a message using NATS publish (fire-and-forget).
441
+ /// This is a one-way operation with no response expected.
442
+ ///
443
+ /// # Arguments
444
+ /// * \`payload\` - Message payload to publish
445
+ ///
446
+ /// # Errors
447
+ /// * \`ClientError::Nats\` - NATS operation failed
448
+ /// * \`ClientError::Serialization\` - Failed to serialize data
449
+ pub async fn ${pattern.methodName}(&self, payload: ${pattern.payloadType}) -> ClientResult<()> {
450
+ let envelope = if let Some(ref auth) = self.auth {
451
+ MessageEnvelope::new_with_auth("${pattern.operationName}", payload, auth)
452
+ .map_err(ClientError::Serialization)?
453
+ } else {
454
+ MessageEnvelope::new("${pattern.operationName}", payload)
455
+ .map_err(ClientError::Serialization)?
456
+ };
457
+
458
+ self.client
459
+ .publish("${subject}", Bytes::from(envelope.to_bytes()?))
460
+ .await
461
+ .map_err(|e| ClientError::Nats(Box::new(e)))?;
462
+
463
+ Ok(())
464
+ }`;
465
+
466
+ case 'subscribe':
467
+ return ` /// ${pattern.operationName} - Subscribe operation
468
+ ///
469
+ /// Creates a subscription to receive messages from the specified subject.
470
+ /// Returns a NATS subscriber that can be used to receive messages.
471
+ ///
472
+ /// # Returns
473
+ /// * \`ClientResult<async_nats::Subscriber>\` - NATS subscriber for handling messages
474
+ ///
475
+ /// # Example
476
+ /// \`\`\`ignore
477
+ /// let mut subscriber = client.${pattern.methodName}().await?;
478
+ /// while let Some(message) = subscriber.next().await {
479
+ /// let envelope = MessageEnvelope::from_bytes(&message.payload)?;
480
+ /// let payload: ${pattern.payloadType} = envelope.extract_payload()?;
481
+ /// // Handle the message...
482
+ /// }
483
+ /// \`\`\`
484
+ pub async fn ${pattern.methodName}(&self) -> ClientResult<async_nats::Subscriber> {
485
+ let subscriber = self.client
486
+ .subscribe("${subject}")
487
+ .await
488
+ .map_err(|e| ClientError::Nats(Box::new(e)))?;
489
+
490
+ Ok(subscriber)
491
+ }`;
492
+
493
+ default:
494
+ return '';
495
+ }
496
+ }).filter(method => method);
497
+
498
+ const allMethods = [...dynamicChannelAccessors, ...staticMethods];
499
+
500
+ if (allMethods.length === 0) {
501
+ return ` // No operations found in AsyncAPI specification
502
+ // You can add custom methods here`;
503
+ }
504
+
505
+ return allMethods.join('\n\n');
506
+ }
507
+
508
+ return (
509
+ <File name="client.rs">
510
+ {`//! Generated NATS client for ${title}
511
+
512
+ use crate::auth::AuthCredentials;
513
+ use crate::envelope::MessageEnvelope;
514
+ use crate::errors::{ClientError, ClientResult};
515
+ use crate::models::*;
516
+ use bytes::Bytes;
517
+
518
+ /// ${title} NATS client
519
+ ///
520
+ /// This client provides type-safe access to ${title} operations via NATS messaging.
521
+ /// It supports request/reply, publish/subscribe, and other NATS patterns based on the AsyncAPI specification.
522
+ ///
523
+ /// ## Features
524
+ ///
525
+ /// - **Type Safety**: All operations use strongly-typed message structures
526
+ /// - **NATS Services API**: Request/reply operations use NATS Services for reliability
527
+ /// - **Message Envelopes**: All messages are wrapped in a standard envelope format
528
+ /// - **Error Handling**: Comprehensive error types for different failure modes
529
+ /// - **Async/Await**: Full async support with Tokio compatibility
530
+ /// - **Dynamic Channels**: Support for channels with variable parameters
531
+ ///
532
+ /// ## Usage
533
+ ///
534
+ /// \`\`\`ignore
535
+ /// use async_nats;
536
+ /// use your_crate::${clientName};
537
+ ///
538
+ /// #[tokio::main]
539
+ /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
540
+ /// // Connect to NATS server
541
+ /// let nats_client = async_nats::connect("nats://localhost:4222").await?;
542
+ ///
543
+ /// // Create the AsyncAPI client
544
+ /// let client = ${clientName}::with(nats_client);
545
+ ///
546
+ /// // For dynamic channels, use the channel service:
547
+ /// // let service = client.user_create("us-west-1");
548
+ /// // let result = service.create_user(payload).await?;
549
+ ///
550
+ /// Ok(())
551
+ /// }
552
+ /// \`\`\`
553
+ #[derive(Debug, Clone)]
554
+ pub struct ${clientName} {
555
+ client: async_nats::Client,
556
+ auth: Option<AuthCredentials>,
557
+ }
558
+
559
+ impl ${clientName} {
560
+ /// Create a new client with an existing NATS client
561
+ ///
562
+ /// # Arguments
563
+ /// * \`client\` - A connected async-nats::Client instance
564
+ ///
565
+ /// # Example
566
+ /// \`\`\`ignore
567
+ /// use async_nats;
568
+ /// use your_crate::${clientName};
569
+ ///
570
+ /// #[tokio::main]
571
+ /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
572
+ /// let nats_client = async_nats::connect("nats://localhost:4222").await?;
573
+ /// let client = ${clientName}::with(nats_client);
574
+ /// Ok(())
575
+ /// }
576
+ /// \`\`\`
577
+ pub fn with(client: async_nats::Client) -> Self {
578
+ Self { client, auth: None }
579
+ }
580
+
581
+ /// Create a new client by connecting to NATS server
582
+ ///
583
+ /// # Arguments
584
+ /// * \`url\` - NATS server URL (e.g., "nats://localhost:4222")
585
+ ///
586
+ /// # Example
587
+ /// \`\`\`ignore
588
+ /// use your_crate::${clientName};
589
+ ///
590
+ /// #[tokio::main]
591
+ /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
592
+ /// let client = ${clientName}::connect("nats://localhost:4222").await?;
593
+ /// Ok(())
594
+ /// }
595
+ /// \`\`\`
596
+ pub async fn connect(url: &str) -> ClientResult<Self> {
597
+ let client = async_nats::connect(url)
598
+ .await
599
+ .map_err(|e| ClientError::Nats(Box::new(e)))?;
600
+ Ok(Self::with(client))
601
+ }
602
+
603
+ /// Get a reference to the underlying NATS client
604
+ ///
605
+ /// This allows access to low-level NATS operations if needed.
606
+ pub fn nats_client(&self) -> &async_nats::Client {
607
+ &self.client
608
+ }
609
+
610
+ /// Create a new client with authentication credentials
611
+ ///
612
+ /// # Arguments
613
+ /// * \`client\` - A connected async-nats::Client instance
614
+ /// * \`auth\` - Authentication credentials
615
+ ///
616
+ /// # Example
617
+ /// \`\`\`ignore
618
+ /// use async_nats;
619
+ /// use your_crate::{${clientName}, AuthCredentials};
620
+ ///
621
+ /// #[tokio::main]
622
+ /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
623
+ /// let nats_client = async_nats::connect("nats://localhost:4222").await?;
624
+ /// let auth = AuthCredentials::jwt("your-jwt-token");
625
+ /// let client = ${clientName}::with_auth(nats_client, auth);
626
+ /// Ok(())
627
+ /// }
628
+ /// \`\`\`
629
+ pub fn with_auth(client: async_nats::Client, auth: AuthCredentials) -> ClientResult<Self> {
630
+ auth.validate()?;
631
+ Ok(Self {
632
+ client,
633
+ auth: Some(auth),
634
+ })
635
+ }
636
+
637
+ /// Update authentication credentials
638
+ ///
639
+ /// This method allows you to update the authentication credentials for an existing client.
640
+ /// All subsequent operations will use the new credentials.
641
+ ///
642
+ /// # Arguments
643
+ /// * \`auth\` - New authentication credentials
644
+ ///
645
+ /// # Example
646
+ /// \`\`\`ignore
647
+ /// use your_crate::AuthCredentials;
648
+ ///
649
+ /// let new_auth = AuthCredentials::jwt("new-jwt-token");
650
+ /// client.update_auth(new_auth)?;
651
+ /// \`\`\`
652
+ pub fn update_auth(&mut self, auth: AuthCredentials) -> ClientResult<()> {
653
+ auth.validate()?;
654
+ self.auth = Some(auth);
655
+ Ok(())
656
+ }
657
+
658
+ /// Remove authentication credentials
659
+ ///
660
+ /// After calling this method, subsequent operations will not include authentication headers.
661
+ pub fn clear_auth(&mut self) {
662
+ self.auth = None;
663
+ }
664
+
665
+ /// Get the current authentication credentials
666
+ pub fn auth(&self) -> Option<&AuthCredentials> {
667
+ self.auth.as_ref()
668
+ }
669
+
670
+ /// Check if authentication credentials are configured
671
+ pub fn has_auth(&self) -> bool {
672
+ self.auth.as_ref().map_or(false, |auth| auth.has_credentials())
673
+ }
674
+
675
+ ${generateClientMethods()}
676
+ }
677
+ ${generateChannelServices()}
678
+ `}
679
+ </File>
680
+ );
681
+ }