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 Search,
250}
251
252#[derive(Debug, Clone, Deserialize)]
254pub struct StreamChunkMessage {
255 pub call_id: String,
256 pub data: String,
257 #[serde(default)]
259 pub event: Option<String>,
260}
261
262#[derive(Debug, Clone, Deserialize)]
264pub struct ScheduleMessage {
265 pub call_id: String,
266 pub fn_name: String,
267 pub args: serde_json::Value,
268 #[serde(default)]
270 pub delay_ms: Option<u64>,
271 #[serde(default)]
273 pub run_at: Option<u64>,
274}
275
276#[derive(Debug, Clone, Deserialize)]
278pub struct CancelScheduleMessage {
279 pub call_id: String,
280 pub schedule_id: String,
281}
282
283#[derive(Debug, Clone, Deserialize)]
285pub struct RunFnMessage {
286 pub call_id: String,
287 pub fn_name: String,
288 pub fn_type: FnType,
289 pub args: serde_json::Value,
290}
291
292#[derive(Debug, Clone, Deserialize)]
294pub struct ReturnMessage {
295 pub call_id: String,
296 pub value: serde_json::Value,
297}
298
299#[derive(Debug, Clone, Deserialize)]
301pub struct ErrorMessage {
302 pub call_id: String,
303 pub code: String,
304 pub message: String,
305}
306
307#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
313#[serde(rename_all = "snake_case")]
314pub enum FnType {
315 Query,
316 Mutation,
317 Action,
318}
319
320#[derive(Debug, Clone, Serialize, Deserialize)]
329pub struct AuthInfo {
330 #[serde(skip_serializing_if = "Option::is_none")]
331 pub user_id: Option<String>,
332 pub is_admin: bool,
333 #[serde(default, skip_serializing_if = "Option::is_none")]
334 pub tenant_id: Option<String>,
335}
336
337#[derive(Debug, Clone, Serialize, Deserialize)]
339pub struct ErrorInfo {
340 pub code: String,
341 pub message: String,
342}
343
344#[cfg(test)]
345mod tests {
346 use super::*;
347
348 #[test]
349 fn call_message_serializes() {
350 let msg = CallMessage::new(
351 "c1".into(),
352 "placeBid".into(),
353 FnType::Mutation,
354 serde_json::json!({"lotId": "lot_1", "amount": 100}),
355 AuthInfo {
356 user_id: Some("user_1".into()),
357 is_admin: false,
358 tenant_id: None,
359 },
360 );
361 let json = serde_json::to_string(&msg).unwrap();
362 assert!(json.contains("\"type\":\"call\""));
363 assert!(json.contains("\"fn_type\":\"mutation\""));
364 }
365
366 #[test]
367 fn ts_message_deserializes_db_op() {
368 let json = r#"{"type":"db","call_id":"c1","op":"get","entity":"Lot","id":"lot_1"}"#;
369 let msg: TsMessage = serde_json::from_str(json).unwrap();
370 match msg {
371 TsMessage::Db(db) => {
372 assert_eq!(db.call_id, "c1");
373 assert_eq!(db.op, DbOp::Get);
374 assert_eq!(db.entity, "Lot");
375 assert_eq!(db.id.as_deref(), Some("lot_1"));
376 }
377 _ => panic!("expected Db message"),
378 }
379 }
380
381 #[test]
382 fn ts_message_deserializes_stream() {
383 let json = r#"{"type":"stream","call_id":"c1","data":"hello"}"#;
384 let msg: TsMessage = serde_json::from_str(json).unwrap();
385 match msg {
386 TsMessage::Stream(s) => {
387 assert_eq!(s.data, "hello");
388 assert!(s.event.is_none());
389 }
390 _ => panic!("expected Stream message"),
391 }
392 }
393
394 #[test]
395 fn ts_message_deserializes_return() {
396 let json = r#"{"type":"return","call_id":"c1","value":{"ok":true}}"#;
397 let msg: TsMessage = serde_json::from_str(json).unwrap();
398 match msg {
399 TsMessage::Return(r) => {
400 assert_eq!(r.value, serde_json::json!({"ok": true}));
401 }
402 _ => panic!("expected Return message"),
403 }
404 }
405
406 #[test]
407 fn ts_message_deserializes_schedule() {
408 let json = r#"{"type":"schedule","call_id":"c1","fn_name":"closeLot","args":{"lotId":"x"},"delay_ms":5000}"#;
409 let msg: TsMessage = serde_json::from_str(json).unwrap();
410 match msg {
411 TsMessage::Schedule(s) => {
412 assert_eq!(s.fn_name, "closeLot");
413 assert_eq!(s.delay_ms, Some(5000));
414 }
415 _ => panic!("expected Schedule message"),
416 }
417 }
418
419 #[test]
420 fn db_result_ok() {
421 let msg = DbResultMessage::ok("c1".into(), serde_json::json!({"id": "x"}));
422 let json = serde_json::to_string(&msg).unwrap();
423 assert!(json.contains("\"type\":\"result\""));
424 assert!(!json.contains("\"error\""));
425 }
426
427 #[test]
428 fn db_result_err() {
429 let msg = DbResultMessage::err("c1".into(), "NOT_FOUND", "not found");
430 let json = serde_json::to_string(&msg).unwrap();
431 assert!(json.contains("\"error\""));
432 assert!(!json.contains("\"data\""));
433 }
434}