Skip to main content

rustbridge_transport/
envelope.rs

1//! Request and response envelope types for FFI transport
2
3use serde::{Deserialize, Serialize};
4
5/// Request envelope wrapping a message for FFI transport
6///
7/// The type_tag identifies the handler, and payload contains the
8/// serialized request data.
9#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct RequestEnvelope {
11    /// Message type identifier (e.g., "user.create", "order.submit")
12    pub type_tag: String,
13
14    /// Serialized request payload (JSON)
15    pub payload: serde_json::Value,
16
17    /// Optional request ID for correlation
18    #[serde(skip_serializing_if = "Option::is_none")]
19    pub request_id: Option<u64>,
20
21    /// Optional correlation ID for distributed tracing
22    #[serde(skip_serializing_if = "Option::is_none")]
23    pub correlation_id: Option<String>,
24}
25
26impl RequestEnvelope {
27    /// Create a new request envelope
28    pub fn new(type_tag: impl Into<String>, payload: serde_json::Value) -> Self {
29        Self {
30            type_tag: type_tag.into(),
31            payload,
32            request_id: None,
33            correlation_id: None,
34        }
35    }
36
37    /// Set request ID
38    pub fn with_request_id(mut self, id: u64) -> Self {
39        self.request_id = Some(id);
40        self
41    }
42
43    /// Set correlation ID
44    pub fn with_correlation_id(mut self, id: impl Into<String>) -> Self {
45        self.correlation_id = Some(id.into());
46        self
47    }
48
49    /// Create from type tag and serializable payload
50    pub fn from_typed<T: Serialize>(
51        type_tag: impl Into<String>,
52        payload: &T,
53    ) -> Result<Self, serde_json::Error> {
54        Ok(Self {
55            type_tag: type_tag.into(),
56            payload: serde_json::to_value(payload)?,
57            request_id: None,
58            correlation_id: None,
59        })
60    }
61
62    /// Deserialize the payload to a typed value
63    pub fn payload_as<T: for<'de> Deserialize<'de>>(&self) -> Result<T, serde_json::Error> {
64        serde_json::from_value(self.payload.clone())
65    }
66
67    /// Serialize to bytes for FFI transport
68    pub fn to_bytes(&self) -> Result<Vec<u8>, serde_json::Error> {
69        serde_json::to_vec(self)
70    }
71
72    /// Deserialize from bytes
73    pub fn from_bytes(data: &[u8]) -> Result<Self, serde_json::Error> {
74        serde_json::from_slice(data)
75    }
76}
77
78/// Response status indicating success or failure
79#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
80#[serde(rename_all = "snake_case")]
81pub enum ResponseStatus {
82    /// Request completed successfully
83    Success,
84    /// Request failed with an error
85    Error,
86}
87
88/// Response envelope wrapping a response for FFI transport
89#[derive(Debug, Clone, Serialize, Deserialize)]
90pub struct ResponseEnvelope {
91    /// Response status
92    pub status: ResponseStatus,
93
94    /// Serialized response payload (on success) or null
95    #[serde(skip_serializing_if = "Option::is_none")]
96    pub payload: Option<serde_json::Value>,
97
98    /// Error code (on failure)
99    #[serde(skip_serializing_if = "Option::is_none")]
100    pub error_code: Option<u32>,
101
102    /// Error message (on failure)
103    #[serde(skip_serializing_if = "Option::is_none")]
104    pub error_message: Option<String>,
105
106    /// Original request ID for correlation
107    #[serde(skip_serializing_if = "Option::is_none")]
108    pub request_id: Option<u64>,
109}
110
111impl ResponseEnvelope {
112    /// Create a success response with payload
113    pub fn success(payload: serde_json::Value) -> Self {
114        Self {
115            status: ResponseStatus::Success,
116            payload: Some(payload),
117            error_code: None,
118            error_message: None,
119            request_id: None,
120        }
121    }
122
123    /// Create a success response from a serializable value
124    pub fn success_typed<T: Serialize>(value: &T) -> Result<Self, serde_json::Error> {
125        Ok(Self::success(serde_json::to_value(value)?))
126    }
127
128    /// Create a success response from raw bytes (already JSON-encoded)
129    pub fn success_raw(data: &[u8]) -> Result<Self, serde_json::Error> {
130        let payload: serde_json::Value = serde_json::from_slice(data)?;
131        Ok(Self::success(payload))
132    }
133
134    /// Create an error response
135    pub fn error(code: u32, message: impl Into<String>) -> Self {
136        Self {
137            status: ResponseStatus::Error,
138            payload: None,
139            error_code: Some(code),
140            error_message: Some(message.into()),
141            request_id: None,
142        }
143    }
144
145    /// Create an error response from a PluginError
146    pub fn from_error(err: &rustbridge_core::PluginError) -> Self {
147        Self::error(err.error_code(), err.to_string())
148    }
149
150    /// Set request ID for correlation
151    pub fn with_request_id(mut self, id: u64) -> Self {
152        self.request_id = Some(id);
153        self
154    }
155
156    /// Check if this is a success response
157    pub fn is_success(&self) -> bool {
158        self.status == ResponseStatus::Success
159    }
160
161    /// Get the payload if success
162    pub fn payload_as<T: for<'de> Deserialize<'de>>(&self) -> Result<Option<T>, serde_json::Error> {
163        match &self.payload {
164            Some(v) => Ok(Some(serde_json::from_value(v.clone())?)),
165            None => Ok(None),
166        }
167    }
168
169    /// Serialize to bytes for FFI transport
170    pub fn to_bytes(&self) -> Result<Vec<u8>, serde_json::Error> {
171        serde_json::to_vec(self)
172    }
173
174    /// Deserialize from bytes
175    pub fn from_bytes(data: &[u8]) -> Result<Self, serde_json::Error> {
176        serde_json::from_slice(data)
177    }
178}
179
180impl Default for ResponseEnvelope {
181    fn default() -> Self {
182        Self::success(serde_json::Value::Null)
183    }
184}
185
186#[cfg(test)]
187#[path = "envelope/envelope_tests.rs"]
188mod envelope_tests;