1use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone, Serialize)]
14pub struct CallMessage {
15 #[serde(rename = "type")]
16 pub msg_type: &'static str, pub call_id: String,
18 pub fn_name: String,
19 pub fn_type: FnType,
20 pub args: serde_json::Value,
21 pub auth: AuthInfo,
22 #[serde(skip_serializing_if = "Option::is_none")]
28 pub request: Option<RequestInfo>,
29}
30
31#[derive(Debug, Clone, Serialize, Deserialize)]
35pub struct RequestInfo {
36 pub method: String,
38 pub path: String,
40 pub headers: std::collections::HashMap<String, String>,
44 pub raw_body: String,
48}
49
50impl CallMessage {
51 pub fn new(
52 call_id: String,
53 fn_name: String,
54 fn_type: FnType,
55 args: serde_json::Value,
56 auth: AuthInfo,
57 ) -> Self {
58 Self {
59 msg_type: "call",
60 call_id,
61 fn_name,
62 fn_type,
63 args,
64 auth,
65 request: None,
66 }
67 }
68
69 pub fn with_request(mut self, request: RequestInfo) -> Self {
72 self.request = Some(request);
73 self
74 }
75}
76
77#[derive(Debug, Clone, Serialize)]
84pub struct DbResultMessage {
85 #[serde(rename = "type")]
86 pub msg_type: &'static str, pub call_id: String,
88 #[serde(skip_serializing_if = "Option::is_none")]
89 pub op_id: Option<String>,
90 #[serde(skip_serializing_if = "Option::is_none")]
91 pub data: Option<serde_json::Value>,
92 #[serde(skip_serializing_if = "Option::is_none")]
93 pub error: Option<ErrorInfo>,
94}
95
96impl DbResultMessage {
97 pub fn ok(call_id: String, data: serde_json::Value) -> Self {
98 Self {
99 msg_type: "result",
100 call_id,
101 op_id: None,
102 data: Some(data),
103 error: None,
104 }
105 }
106
107 pub fn ok_with_op(call_id: String, op_id: Option<String>, data: serde_json::Value) -> Self {
108 Self {
109 msg_type: "result",
110 call_id,
111 op_id,
112 data: Some(data),
113 error: None,
114 }
115 }
116
117 pub fn err(call_id: String, code: &str, message: &str) -> Self {
118 Self {
119 msg_type: "result",
120 call_id,
121 op_id: None,
122 data: None,
123 error: Some(ErrorInfo {
124 code: code.to_string(),
125 message: message.to_string(),
126 }),
127 }
128 }
129
130 pub fn err_with_op(call_id: String, op_id: Option<String>, code: &str, message: &str) -> Self {
131 Self {
132 msg_type: "result",
133 call_id,
134 op_id,
135 data: None,
136 error: Some(ErrorInfo {
137 code: code.to_string(),
138 message: message.to_string(),
139 }),
140 }
141 }
142}
143
144#[derive(Debug, Clone, Deserialize)]
150#[serde(tag = "type")]
151pub enum TsMessage {
152 #[serde(rename = "db")]
154 Db(DbOpMessage),
155
156 #[serde(rename = "stream")]
158 Stream(StreamChunkMessage),
159
160 #[serde(rename = "schedule")]
162 Schedule(ScheduleMessage),
163
164 #[serde(rename = "cancel_schedule")]
166 CancelSchedule(CancelScheduleMessage),
167
168 #[serde(rename = "run_fn")]
170 RunFn(RunFnMessage),
171
172 #[serde(rename = "return")]
174 Return(ReturnMessage),
175
176 #[serde(rename = "error")]
178 Error(ErrorMessage),
179
180 #[serde(rename = "ready")]
183 Ready(ReadyMessage),
184}
185
186#[derive(Debug, Clone, Deserialize)]
188pub struct ReadyMessage {
189 #[serde(default)]
190 pub functions: Vec<crate::registry::FnDef>,
191 #[serde(default)]
192 pub error: Option<String>,
193}
194
195#[derive(Debug, Clone, Deserialize)]
197pub struct DbOpMessage {
198 pub call_id: String,
199 #[serde(default)]
205 pub op_id: Option<String>,
206 pub op: DbOp,
207 pub entity: String,
208 #[serde(default)]
209 pub id: Option<String>,
210 #[serde(default)]
211 pub data: Option<serde_json::Value>,
212 #[serde(default)]
213 pub field: Option<String>,
214 #[serde(default)]
215 pub value: Option<String>,
216 #[serde(default)]
217 pub relation: Option<String>,
218 #[serde(default)]
219 pub target_id: Option<String>,
220 #[serde(default)]
222 pub after: Option<String>,
223 #[serde(default)]
225 pub limit: Option<u32>,
226}
227
228#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
230#[serde(rename_all = "snake_case")]
231pub enum DbOp {
232 Get,
233 List,
234 Paginate,
237 Insert,
238 Update,
239 Delete,
240 Lookup,
241 Query,
242 QueryGraph,
243 Link,
244 Unlink,
245}
246
247#[derive(Debug, Clone, Deserialize)]
249pub struct StreamChunkMessage {
250 pub call_id: String,
251 pub data: String,
252 #[serde(default)]
254 pub event: Option<String>,
255}
256
257#[derive(Debug, Clone, Deserialize)]
259pub struct ScheduleMessage {
260 pub call_id: String,
261 pub fn_name: String,
262 pub args: serde_json::Value,
263 #[serde(default)]
265 pub delay_ms: Option<u64>,
266 #[serde(default)]
268 pub run_at: Option<u64>,
269}
270
271#[derive(Debug, Clone, Deserialize)]
273pub struct CancelScheduleMessage {
274 pub call_id: String,
275 pub schedule_id: String,
276}
277
278#[derive(Debug, Clone, Deserialize)]
280pub struct RunFnMessage {
281 pub call_id: String,
282 pub fn_name: String,
283 pub fn_type: FnType,
284 pub args: serde_json::Value,
285}
286
287#[derive(Debug, Clone, Deserialize)]
289pub struct ReturnMessage {
290 pub call_id: String,
291 pub value: serde_json::Value,
292}
293
294#[derive(Debug, Clone, Deserialize)]
296pub struct ErrorMessage {
297 pub call_id: String,
298 pub code: String,
299 pub message: String,
300}
301
302#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
308#[serde(rename_all = "snake_case")]
309pub enum FnType {
310 Query,
311 Mutation,
312 Action,
313}
314
315#[derive(Debug, Clone, Serialize, Deserialize)]
324pub struct AuthInfo {
325 #[serde(skip_serializing_if = "Option::is_none")]
326 pub user_id: Option<String>,
327 pub is_admin: bool,
328 #[serde(default, skip_serializing_if = "Option::is_none")]
329 pub tenant_id: Option<String>,
330}
331
332#[derive(Debug, Clone, Serialize, Deserialize)]
334pub struct ErrorInfo {
335 pub code: String,
336 pub message: String,
337}
338
339#[cfg(test)]
340mod tests {
341 use super::*;
342
343 #[test]
344 fn call_message_serializes() {
345 let msg = CallMessage::new(
346 "c1".into(),
347 "placeBid".into(),
348 FnType::Mutation,
349 serde_json::json!({"lotId": "lot_1", "amount": 100}),
350 AuthInfo {
351 user_id: Some("user_1".into()),
352 is_admin: false,
353 tenant_id: None,
354 },
355 );
356 let json = serde_json::to_string(&msg).unwrap();
357 assert!(json.contains("\"type\":\"call\""));
358 assert!(json.contains("\"fn_type\":\"mutation\""));
359 }
360
361 #[test]
362 fn ts_message_deserializes_db_op() {
363 let json = r#"{"type":"db","call_id":"c1","op":"get","entity":"Lot","id":"lot_1"}"#;
364 let msg: TsMessage = serde_json::from_str(json).unwrap();
365 match msg {
366 TsMessage::Db(db) => {
367 assert_eq!(db.call_id, "c1");
368 assert_eq!(db.op, DbOp::Get);
369 assert_eq!(db.entity, "Lot");
370 assert_eq!(db.id.as_deref(), Some("lot_1"));
371 }
372 _ => panic!("expected Db message"),
373 }
374 }
375
376 #[test]
377 fn ts_message_deserializes_stream() {
378 let json = r#"{"type":"stream","call_id":"c1","data":"hello"}"#;
379 let msg: TsMessage = serde_json::from_str(json).unwrap();
380 match msg {
381 TsMessage::Stream(s) => {
382 assert_eq!(s.data, "hello");
383 assert!(s.event.is_none());
384 }
385 _ => panic!("expected Stream message"),
386 }
387 }
388
389 #[test]
390 fn ts_message_deserializes_return() {
391 let json = r#"{"type":"return","call_id":"c1","value":{"ok":true}}"#;
392 let msg: TsMessage = serde_json::from_str(json).unwrap();
393 match msg {
394 TsMessage::Return(r) => {
395 assert_eq!(r.value, serde_json::json!({"ok": true}));
396 }
397 _ => panic!("expected Return message"),
398 }
399 }
400
401 #[test]
402 fn ts_message_deserializes_schedule() {
403 let json = r#"{"type":"schedule","call_id":"c1","fn_name":"closeLot","args":{"lotId":"x"},"delay_ms":5000}"#;
404 let msg: TsMessage = serde_json::from_str(json).unwrap();
405 match msg {
406 TsMessage::Schedule(s) => {
407 assert_eq!(s.fn_name, "closeLot");
408 assert_eq!(s.delay_ms, Some(5000));
409 }
410 _ => panic!("expected Schedule message"),
411 }
412 }
413
414 #[test]
415 fn db_result_ok() {
416 let msg = DbResultMessage::ok("c1".into(), serde_json::json!({"id": "x"}));
417 let json = serde_json::to_string(&msg).unwrap();
418 assert!(json.contains("\"type\":\"result\""));
419 assert!(!json.contains("\"error\""));
420 }
421
422 #[test]
423 fn db_result_err() {
424 let msg = DbResultMessage::err("c1".into(), "NOT_FOUND", "not found");
425 let json = serde_json::to_string(&msg).unwrap();
426 assert!(json.contains("\"error\""));
427 assert!(!json.contains("\"data\""));
428 }
429}