@ioka-technologies/asyncapi-rust-client-template 0.0.30 → 0.0.32

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.
@@ -2305,6 +2305,14 @@ function generateRustModels(asyncapi, options = {}) {
2305
2305
  return 'u32';
2306
2306
  case 'uint64':
2307
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';
2308
2316
  default:
2309
2317
  // Default to i32 for unspecified format (maintains backward compatibility)
2310
2318
  return 'i32';
@@ -2718,6 +2726,287 @@ function generateMessageEnvelope() {
2718
2726
  return `use serde::{de::DeserializeOwned, Deserialize, Serialize};
2719
2727
  use std::collections::HashMap;
2720
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 {}
2721
3010
 
2722
3011
  /// Unified message envelope for consistent AsyncAPI message format
2723
3012
  ///
@@ -2791,17 +3080,9 @@ pub struct MessageEnvelope {
2791
3080
  pub channel: Option<String>,
2792
3081
  /// Transport-level headers (auth, routing, etc.)
2793
3082
  pub headers: Option<HashMap<String, String>>,
2794
- /// Error information if applicable
2795
- pub error: Option<MessageError>,
2796
- }
2797
-
2798
- /// Error information for failed operations
2799
- #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2800
- pub struct MessageError {
2801
- /// Error code (e.g., "VALIDATION_ERROR", "TIMEOUT", "UNAUTHORIZED")
2802
- pub code: String,
2803
- /// Human-readable error message
2804
- pub message: String,
3083
+ /// Rich error information if operation failed
3084
+ /// Contains detailed error metadata including severity, category, and correlation
3085
+ pub error: Option<AsyncApiError>,
2805
3086
  }
2806
3087
 
2807
3088
  impl MessageEnvelope {
@@ -2839,11 +3120,10 @@ impl MessageEnvelope {
2839
3120
  Ok(envelope)
2840
3121
  }
2841
3122
 
2842
- /// Create an error response envelope
3123
+ /// Create an error response envelope with rich AsyncApiError
2843
3124
  pub fn error_response(
2844
3125
  operation: &str,
2845
- error_code: &str,
2846
- error_message: &str,
3126
+ error: AsyncApiError,
2847
3127
  correlation_id: Option<String>,
2848
3128
  ) -> Self {
2849
3129
  Self {
@@ -2854,13 +3134,28 @@ impl MessageEnvelope {
2854
3134
  correlation_id,
2855
3135
  channel: None,
2856
3136
  headers: None,
2857
- error: Some(MessageError {
2858
- code: error_code.to_string(),
2859
- message: error_message.to_string(),
2860
- }),
3137
+ error: Some(error),
2861
3138
  }
2862
3139
  }
2863
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
+
2864
3159
  /// Set the correlation ID for this envelope
2865
3160
  pub fn with_correlation_id(mut self, id: String) -> Self {
2866
3161
  self.correlation_id = Some(id);
@@ -2904,11 +3199,23 @@ impl MessageEnvelope {
2904
3199
  }
2905
3200
 
2906
3201
  /// Set an error on this envelope
2907
- pub fn with_error(mut self, code: &str, message: &str) -> Self {
2908
- self.error = Some(MessageError {
2909
- code: code.to_string(),
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 {
2910
3210
  message: message.to_string(),
2911
- });
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);
2912
3219
  self
2913
3220
  }
2914
3221
 
@@ -2950,134 +3257,6 @@ impl MessageEnvelope {
2950
3257
  }
2951
3258
  }
2952
3259
 
2953
- #[cfg(test)]
2954
- mod tests {
2955
- use super::*;
2956
- use serde::{Deserialize, Serialize};
2957
-
2958
- #[derive(Debug, Serialize, Deserialize, PartialEq)]
2959
- struct TestPayload {
2960
- message: String,
2961
- count: u32,
2962
- }
2963
-
2964
- #[test]
2965
- fn test_envelope_creation() {
2966
- let payload = TestPayload {
2967
- message: "test".to_string(),
2968
- count: 42,
2969
- };
2970
-
2971
- let envelope = MessageEnvelope::new("test_operation", &payload).unwrap();
2972
-
2973
- assert_eq!(envelope.operation, "test_operation");
2974
- assert!(!envelope.id.is_empty());
2975
- assert!(!envelope.timestamp.is_empty());
2976
- assert_eq!(envelope.correlation_id, None);
2977
- assert_eq!(envelope.error, None);
2978
-
2979
- let extracted: TestPayload = envelope.extract_payload().unwrap();
2980
- assert_eq!(extracted, payload);
2981
- }
2982
-
2983
- #[test]
2984
- fn test_envelope_with_correlation_id() {
2985
- let payload = TestPayload {
2986
- message: "test".to_string(),
2987
- count: 42,
2988
- };
2989
-
2990
- let correlation_id = "test-correlation-id".to_string();
2991
- let envelope = MessageEnvelope::new_with_correlation_id(
2992
- "test_operation",
2993
- &payload,
2994
- correlation_id.clone(),
2995
- ).unwrap();
2996
-
2997
- assert_eq!(envelope.correlation_id, Some(correlation_id));
2998
- }
2999
-
3000
- #[test]
3001
- fn test_error_response() {
3002
- let error_envelope = MessageEnvelope::error_response(
3003
- "test_operation_response",
3004
- "TEST_ERROR",
3005
- "Test error message",
3006
- Some("correlation-123".to_string()),
3007
- );
3008
-
3009
- assert!(error_envelope.is_error());
3010
- assert_eq!(error_envelope.correlation_id, Some("correlation-123".to_string()));
3011
- if let Some(error) = &error_envelope.error {
3012
- assert_eq!(error.code, "TEST_ERROR");
3013
- assert_eq!(error.message, "Test error message");
3014
- }
3015
- }
3016
-
3017
- #[test]
3018
- fn test_envelope_serialization() {
3019
- let payload = TestPayload {
3020
- message: "test".to_string(),
3021
- count: 42,
3022
- };
3023
-
3024
- let envelope = MessageEnvelope::new("test_operation", &payload).unwrap();
3025
- let bytes = envelope.to_bytes().unwrap();
3026
- let deserialized = MessageEnvelope::from_bytes(&bytes).unwrap();
3027
-
3028
- assert_eq!(envelope.id, deserialized.id);
3029
- assert_eq!(envelope.operation, deserialized.operation);
3030
- assert_eq!(envelope.timestamp, deserialized.timestamp);
3031
- }
3032
-
3033
- #[test]
3034
- fn test_response_creation() {
3035
- let request_payload = TestPayload {
3036
- message: "request".to_string(),
3037
- count: 1,
3038
- };
3039
-
3040
- let response_payload = TestPayload {
3041
- message: "response".to_string(),
3042
- count: 2,
3043
- };
3044
-
3045
- let request = MessageEnvelope::new_with_correlation_id(
3046
- "test_request",
3047
- &request_payload,
3048
- "test-correlation".to_string(),
3049
- ).unwrap();
3050
-
3051
- let response = request.create_response("test_response", &response_payload).unwrap();
3052
-
3053
- assert_eq!(response.operation, "test_response");
3054
- assert_eq!(response.correlation_id, request.correlation_id);
3055
-
3056
- let extracted: TestPayload = response.extract_payload().unwrap();
3057
- assert_eq!(extracted, response_payload);
3058
- }
3059
-
3060
- #[test]
3061
- fn test_headers_and_auth() {
3062
- let payload = TestPayload {
3063
- message: "test".to_string(),
3064
- count: 42,
3065
- };
3066
-
3067
- let mut auth_headers = HashMap::new();
3068
- auth_headers.insert("Authorization".to_string(), "Bearer token123".to_string());
3069
-
3070
- let envelope = MessageEnvelope::new("test_operation", &payload)
3071
- .unwrap()
3072
- .with_auth_headers(auth_headers)
3073
- .with_header("Custom-Header".to_string(), "custom-value".to_string());
3074
-
3075
- assert!(envelope.headers.is_some());
3076
- let headers = envelope.headers.unwrap();
3077
- assert_eq!(headers.get("Authorization"), Some(&"Bearer token123".to_string()));
3078
- assert_eq!(headers.get("Custom-Header"), Some(&"custom-value".to_string()));
3079
- }
3080
- }
3081
3260
  `;
3082
3261
  }
3083
3262
  ;// ../common/src/models-ts.js
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ioka-technologies/asyncapi-rust-client-template",
3
- "version": "0.0.30",
3
+ "version": "0.0.32",
4
4
  "description": "AsyncAPI template for generating Rust NATS clients",
5
5
  "main": "template/index.js",
6
6
  "keywords": [
@@ -190,6 +190,7 @@ export default function ClientRs({ asyncapi, params }) {
190
190
  /// * \`ClientError::Nats\` - NATS operation failed
191
191
  /// * \`ClientError::Serialization\` - Failed to serialize/deserialize data
192
192
  /// * \`ClientError::Timeout\` - Request timed out
193
+ /// * \`ClientError::AsyncApi\` - Server returned an error
193
194
  pub async fn ${op.methodName}(&self, payload: ${op.requestType}) -> ClientResult<${op.responseType}> {
194
195
  let envelope = if let Some(ref auth) = self.auth {
195
196
  MessageEnvelope::new_with_auth("${op.operationName}", payload, auth)
@@ -208,6 +209,11 @@ export default function ClientRs({ asyncapi, params }) {
208
209
  let response_envelope = MessageEnvelope::from_bytes(&response.payload)
209
210
  .map_err(|e| ClientError::InvalidEnvelope(e.to_string()))?;
210
211
 
212
+ // Check if the response contains an error
213
+ if let Some(error) = response_envelope.error {
214
+ return Err(ClientError::AsyncApi(Box::new(error)));
215
+ }
216
+
211
217
  let result: ${op.responseType} = response_envelope.extract_payload()
212
218
  .map_err(ClientError::Serialization)?;
213
219
 
@@ -399,6 +405,7 @@ ${serviceOperations}
399
405
  /// * \`ClientError::Nats\` - NATS operation failed
400
406
  /// * \`ClientError::Serialization\` - Failed to serialize/deserialize data
401
407
  /// * \`ClientError::Timeout\` - Request timed out
408
+ /// * \`ClientError::AsyncApi\` - Server returned an error
402
409
  pub async fn ${pattern.methodName}(&self, payload: ${pattern.requestType}) -> ClientResult<${pattern.responseType}> {
403
410
  let envelope = if let Some(ref auth) = self.auth {
404
411
  MessageEnvelope::new_with_auth("${pattern.operationName}", payload, auth)
@@ -416,6 +423,11 @@ ${serviceOperations}
416
423
  let response_envelope = MessageEnvelope::from_bytes(&response.payload)
417
424
  .map_err(|e| ClientError::InvalidEnvelope(e.to_string()))?;
418
425
 
426
+ // Check if the response contains an error
427
+ if let Some(error) = response_envelope.error {
428
+ return Err(ClientError::AsyncApi(Box::new(error)));
429
+ }
430
+
419
431
  let result: ${pattern.responseType} = response_envelope.extract_payload()
420
432
  .map_err(ClientError::Serialization)?;
421
433
 
@@ -1,196 +1,28 @@
1
1
  /* eslint-disable no-unused-vars */
2
2
  import { File } from '@asyncapi/generator-react-sdk';
3
+ import { generateMessageEnvelope } from "../../dist/common/index.js";
3
4
 
4
5
  export default function ({ asyncapi, params }) {
6
+ // Generate the unified message envelope with error support
7
+ const envelopeCode = generateMessageEnvelope();
8
+
5
9
  return (
6
10
  <File name="envelope.rs">
7
11
  {`//! Message envelope for consistent NATS message format
8
12
 
9
13
  use crate::auth::{AuthCredentials, generate_auth_headers};
10
- use serde::{Deserialize, Serialize};
11
- use std::collections::HashMap;
12
- use uuid::Uuid;
13
-
14
- /// MessageEnvelope for consistent message format across NATS operations
15
- /// This matches the format expected by the server implementation
16
- #[derive(Debug, Clone, Serialize, Deserialize)]
17
- pub struct MessageEnvelope {
18
- /// Unique message identifier
19
- pub id: String,
20
- /// Operation name from AsyncAPI spec
21
- pub operation: String,
22
- /// Message payload as JSON value
23
- pub payload: serde_json::Value,
24
- /// ISO 8601 timestamp when message was created
25
- pub timestamp: String,
26
- /// Optional correlation ID for request/reply patterns
27
- pub correlation_id: Option<String>,
28
- /// Optional headers for additional metadata
29
- pub headers: Option<HashMap<String, String>>,
30
- }
14
+ ${envelopeCode}
31
15
 
16
+ // Client-specific extensions for auth integration
32
17
  impl MessageEnvelope {
33
- /// Create a new message envelope with generated ID and current timestamp
34
- pub fn new<T: Serialize>(operation: &str, payload: T) -> Result<Self, serde_json::Error> {
35
- Ok(Self {
36
- id: Uuid::new_v4().to_string(),
37
- operation: operation.to_string(),
38
- payload: serde_json::to_value(payload)?,
39
- timestamp: chrono::Utc::now().to_rfc3339(),
40
- correlation_id: None,
41
- headers: None,
42
- })
43
- }
44
-
45
- /// Create a new message envelope with a specific correlation ID
46
- pub fn new_with_correlation_id<T: Serialize>(
47
- operation: &str,
48
- payload: T,
49
- correlation_id: String,
50
- ) -> Result<Self, serde_json::Error> {
51
- Ok(Self {
52
- id: Uuid::new_v4().to_string(),
53
- operation: operation.to_string(),
54
- payload: serde_json::to_value(payload)?,
55
- timestamp: chrono::Utc::now().to_rfc3339(),
56
- correlation_id: Some(correlation_id),
57
- headers: None,
58
- })
59
- }
60
-
61
- /// Add headers to the envelope
62
- pub fn with_headers(mut self, headers: HashMap<String, String>) -> Self {
63
- self.headers = Some(headers);
64
- self
65
- }
66
-
67
- /// Add a single header to the envelope
68
- pub fn with_header(mut self, key: String, value: String) -> Self {
69
- if let Some(ref mut headers) = self.headers {
70
- headers.insert(key, value);
71
- } else {
72
- let mut headers = HashMap::new();
73
- headers.insert(key, value);
74
- self.headers = Some(headers);
75
- }
76
- self
77
- }
78
-
79
- /// Add authentication headers to the envelope
80
- pub fn with_auth_headers(mut self, auth: &AuthCredentials) -> Self {
81
- let auth_headers = generate_auth_headers(auth);
82
- if !auth_headers.is_empty() {
83
- if let Some(ref mut headers) = self.headers {
84
- headers.extend(auth_headers);
85
- } else {
86
- self.headers = Some(auth_headers);
87
- }
88
- }
89
- self
90
- }
91
-
92
18
  /// Create a new message envelope with authentication headers
93
19
  pub fn new_with_auth<T: Serialize>(
94
20
  operation: &str,
95
21
  payload: T,
96
22
  auth: &AuthCredentials,
97
23
  ) -> Result<Self, serde_json::Error> {
98
- let envelope = Self::new(operation, payload)?;
99
- Ok(envelope.with_auth_headers(auth))
100
- }
101
-
102
- /// Create a new message envelope with correlation ID and authentication headers
103
- pub fn new_with_correlation_id_and_auth<T: Serialize>(
104
- operation: &str,
105
- payload: T,
106
- correlation_id: String,
107
- auth: &AuthCredentials,
108
- ) -> Result<Self, serde_json::Error> {
109
- let envelope = Self::new_with_correlation_id(operation, payload, correlation_id)?;
110
- Ok(envelope.with_auth_headers(auth))
111
- }
112
-
113
- /// Convert the envelope to bytes for NATS transmission
114
- pub fn to_bytes(&self) -> Result<Vec<u8>, serde_json::Error> {
115
- serde_json::to_vec(self)
116
- }
117
-
118
- /// Parse envelope from bytes received from NATS
119
- pub fn from_bytes(bytes: &[u8]) -> Result<Self, serde_json::Error> {
120
- serde_json::from_slice(bytes)
121
- }
122
-
123
- /// Extract the payload as a specific type
124
- pub fn extract_payload<T: for<'de> Deserialize<'de>>(&self) -> Result<T, serde_json::Error> {
125
- serde_json::from_value(self.payload.clone())
126
- }
127
-
128
- /// Get the correlation ID if present
129
- pub fn correlation_id(&self) -> Option<&str> {
130
- self.correlation_id.as_deref()
131
- }
132
- }
133
-
134
- #[cfg(test)]
135
- mod tests {
136
- use super::*;
137
- use serde::{Deserialize, Serialize};
138
-
139
- #[derive(Debug, Serialize, Deserialize, PartialEq)]
140
- struct TestPayload {
141
- message: String,
142
- count: u32,
143
- }
144
-
145
- #[test]
146
- fn test_envelope_creation() {
147
- let payload = TestPayload {
148
- message: "test".to_string(),
149
- count: 42,
150
- };
151
-
152
- let envelope = MessageEnvelope::new("test_operation", &payload).unwrap();
153
-
154
- assert_eq!(envelope.operation, "test_operation");
155
- assert!(!envelope.id.is_empty());
156
- assert!(!envelope.timestamp.is_empty());
157
- assert_eq!(envelope.correlation_id, None);
158
-
159
- let extracted: TestPayload = envelope.extract_payload().unwrap();
160
- assert_eq!(extracted, payload);
161
- }
162
-
163
- #[test]
164
- fn test_envelope_with_correlation_id() {
165
- let payload = TestPayload {
166
- message: "test".to_string(),
167
- count: 42,
168
- };
169
-
170
- let correlation_id = "test-correlation-id".to_string();
171
- let envelope = MessageEnvelope::new_with_correlation_id(
172
- "test_operation",
173
- &payload,
174
- correlation_id.clone(),
175
- ).unwrap();
176
-
177
- assert_eq!(envelope.correlation_id(), Some(correlation_id.as_str()));
178
- }
179
-
180
- #[test]
181
- fn test_envelope_serialization() {
182
- let payload = TestPayload {
183
- message: "test".to_string(),
184
- count: 42,
185
- };
186
-
187
- let envelope = MessageEnvelope::new("test_operation", &payload).unwrap();
188
- let bytes = envelope.to_bytes().unwrap();
189
- let deserialized = MessageEnvelope::from_bytes(&bytes).unwrap();
190
-
191
- assert_eq!(envelope.id, deserialized.id);
192
- assert_eq!(envelope.operation, deserialized.operation);
193
- assert_eq!(envelope.timestamp, deserialized.timestamp);
24
+ let auth_headers = generate_auth_headers(auth);
25
+ Self::new(operation, payload).map(|envelope| envelope.with_auth_headers(auth_headers))
194
26
  }
195
27
  }
196
28
  `}
@@ -0,0 +1,31 @@
1
+ /* eslint-disable no-unused-vars */
2
+ import { File } from '@asyncapi/generator-react-sdk';
3
+ import { generateMessageEnvelope } from '../../../common/src/index.js';
4
+
5
+ export default function ({ asyncapi, params }) {
6
+ // Generate the unified message envelope with error support
7
+ const envelopeCode = generateMessageEnvelope();
8
+
9
+ return (
10
+ <File name="envelope.rs">
11
+ {`//! Message envelope for consistent NATS message format
12
+
13
+ use crate::auth::{AuthCredentials, generate_auth_headers};
14
+ ${envelopeCode}
15
+
16
+ // Client-specific extensions for auth integration
17
+ impl MessageEnvelope {
18
+ /// Create a new message envelope with authentication headers
19
+ pub fn new_with_auth<T: Serialize>(
20
+ operation: &str,
21
+ payload: T,
22
+ auth: &AuthCredentials,
23
+ ) -> Result<Self, serde_json::Error> {
24
+ let auth_headers = generate_auth_headers(auth);
25
+ Self::new(operation, payload).map(|envelope| envelope.with_auth_headers(auth_headers))
26
+ }
27
+ }
28
+ `}
29
+ </File>
30
+ );
31
+ };
@@ -9,6 +9,9 @@ export default function ({ asyncapi, params }) {
9
9
  use crate::auth::AuthError;
10
10
  use thiserror::Error;
11
11
 
12
+ // Import the shared AsyncApiError type from the envelope/models module
13
+ pub use crate::envelope::{AsyncApiError, ErrorSeverity, ErrorCategory, ErrorMetadata, CorrelationId as ErrorCorrelationId};
14
+
12
15
  /// Errors that can occur when using the NATS client
13
16
  #[derive(Debug, Error)]
14
17
  pub enum ClientError {
@@ -39,6 +42,58 @@ pub enum ClientError {
39
42
  /// Unauthorized access
40
43
  #[error("Unauthorized: {0}")]
41
44
  Unauthorized(String),
45
+
46
+ /// Server-side AsyncAPI error
47
+ ///
48
+ /// This error contains rich information from the server including:
49
+ /// - Error severity and category
50
+ /// - Correlation ID for tracing
51
+ /// - Detailed error metadata
52
+ /// - Whether the error is retryable
53
+ #[error("Server error: {0}")]
54
+ AsyncApi(Box<AsyncApiError>),
55
+ }
56
+
57
+ impl ClientError {
58
+ /// Check if this error is retryable
59
+ pub fn is_retryable(&self) -> bool {
60
+ match self {
61
+ ClientError::Nats(_) => true,
62
+ ClientError::Timeout => true,
63
+ ClientError::NoResponse => true,
64
+ ClientError::AsyncApi(err) => err.is_retryable(),
65
+ _ => false,
66
+ }
67
+ }
68
+
69
+ /// Get error severity if available
70
+ pub fn severity(&self) -> Option<ErrorSeverity> {
71
+ match self {
72
+ ClientError::AsyncApi(err) => err.severity(),
73
+ _ => None,
74
+ }
75
+ }
76
+
77
+ /// Get error category if available
78
+ pub fn category(&self) -> Option<ErrorCategory> {
79
+ match self {
80
+ ClientError::AsyncApi(err) => err.category(),
81
+ _ => None,
82
+ }
83
+ }
84
+
85
+ /// Get correlation ID for tracing if available
86
+ pub fn correlation_id(&self) -> Option<&ErrorCorrelationId> {
87
+ match self {
88
+ ClientError::AsyncApi(err) => err.correlation_id(),
89
+ _ => None,
90
+ }
91
+ }
92
+
93
+ /// Check if this is a server-side error
94
+ pub fn is_server_error(&self) -> bool {
95
+ matches!(self, ClientError::AsyncApi(_))
96
+ }
42
97
  }
43
98
 
44
99
  /// Result type alias for client operations