1use std::collections::HashMap;
11use std::future::Future;
12use std::pin::Pin;
13use std::sync::atomic::{AtomicBool, Ordering};
14use std::sync::{Arc, Mutex};
15
16use tokio::sync::mpsc;
17use uuid::Uuid;
18
19use super::super::rpc_types::{RpcContext, RpcMessage, RpcOpts, COMMAND_EVENT_RECV};
20use super::schema::RpcSchema;
21
22pub const DEFAULT_TIMEOUT_MS: i64 = 5000;
25const RESP_CH_SIZE: usize = 32;
26
27pub type HandlerResult = Result<Option<serde_json::Value>, String>;
31
32pub type CommandHandler = Box<
38 dyn Fn(serde_json::Value, RpcContext) -> Pin<Box<dyn Future<Output = HandlerResult> + Send>>
39 + Send
40 + Sync,
41>;
42
43pub type StreamHandler = Box<
45 dyn Fn(
46 serde_json::Value,
47 RpcContext,
48 )
49 -> Pin<Box<dyn Future<Output = Result<mpsc::Receiver<HandlerResult>, String>> + Send>>
50 + Send
51 + Sync,
52>;
53
54enum Handler {
55 Call(CommandHandler),
56 #[allow(dead_code)]
57 Stream(StreamHandler),
58}
59
60pub struct RpcResponseHandler {
65 engine: Arc<WshRpcEngine>,
66 req_id: String,
67 #[allow(dead_code)]
68 source: String,
69 canceled: AtomicBool,
70 done: AtomicBool,
71}
72
73impl RpcResponseHandler {
74 pub fn send_response(&self, data: Option<serde_json::Value>, done: bool) {
77 if self.done.load(Ordering::Relaxed) {
78 return;
79 }
80 let msg = RpcMessage {
81 resid: self.req_id.clone(),
82 data,
83 cont: !done,
84 ..Default::default()
85 };
86 if done {
87 self.done.store(true, Ordering::Relaxed);
88 }
89 self.engine.send_output(msg);
90 }
91
92 pub fn send_error(&self, err: &str) {
94 if self.done.load(Ordering::Relaxed) {
95 return;
96 }
97 self.done.store(true, Ordering::Relaxed);
98 let msg = RpcMessage {
99 resid: self.req_id.clone(),
100 error: err.to_string(),
101 ..Default::default()
102 };
103 self.engine.send_output(msg);
104 }
105
106 #[allow(dead_code)]
108 pub fn is_canceled(&self) -> bool {
109 self.canceled.load(Ordering::Relaxed)
110 }
111
112 #[allow(dead_code)]
114 pub fn get_source(&self) -> &str {
115 &self.source
116 }
117
118 fn cancel(&self) {
120 self.canceled.store(true, Ordering::Relaxed);
121 }
122
123 fn finalize(&self) {
125 if self.done.load(Ordering::Relaxed) {
126 return;
127 }
128 self.send_response(None, true);
129 }
130}
131
132#[allow(dead_code)]
137pub struct RpcRequestHandler {
138 req_id: String,
139 resp_rx: mpsc::Receiver<RpcMessage>,
140 last_was_cont: bool,
141}
142
143impl RpcRequestHandler {
144 #[allow(dead_code)]
146 pub async fn next_response(&mut self) -> Option<Result<serde_json::Value, String>> {
147 if !self.last_was_cont && self.req_id.is_empty() {
148 return None;
149 }
150 match self.resp_rx.recv().await {
151 Some(msg) => {
152 self.last_was_cont = msg.cont;
153 if !msg.error.is_empty() {
154 Some(Err(msg.error))
155 } else {
156 Some(Ok(msg.data.unwrap_or(serde_json::Value::Null)))
157 }
158 }
159 None => None,
160 }
161 }
162
163 #[allow(dead_code)]
165 pub fn is_done(&self) -> bool {
166 !self.last_was_cont
167 }
168
169 #[allow(dead_code)]
171 pub fn req_id(&self) -> &str {
172 &self.req_id
173 }
174}
175
176struct EngineInner {
179 handlers: HashMap<String, Handler>,
180 pending_responses: HashMap<String, mpsc::Sender<RpcMessage>>,
181 active_handlers: HashMap<String, Arc<RpcResponseHandler>>,
182 #[allow(dead_code)]
183 auth_token: String,
184 rpc_context: Option<RpcContext>,
185 schema: RpcSchema,
189}
190
191pub struct WshRpcEngine {
196 inner: Mutex<EngineInner>,
197 output_tx: mpsc::UnboundedSender<RpcMessage>,
198}
199
200impl WshRpcEngine {
201 fn lock_inner(&self) -> std::sync::MutexGuard<'_, EngineInner> {
206 self.inner.lock().unwrap_or_else(|e| e.into_inner())
207 }
208
209 pub fn new() -> (Arc<Self>, mpsc::UnboundedReceiver<RpcMessage>) {
212 let (output_tx, output_rx) = mpsc::unbounded_channel();
213 let engine = Arc::new(Self {
214 inner: Mutex::new(EngineInner {
215 handlers: HashMap::new(),
216 pending_responses: HashMap::new(),
217 active_handlers: HashMap::new(),
218 auth_token: String::new(),
219 rpc_context: None,
220 schema: RpcSchema::new(),
221 }),
222 output_tx,
223 });
224 (engine, output_rx)
225 }
226
227 fn install_call_handler(&self, command: &str, handler: CommandHandler) {
233 let mut inner = self.lock_inner();
234 inner
235 .handlers
236 .insert(command.to_string(), Handler::Call(handler));
237 }
238
239 pub fn register_handler(&self, command: &str, handler: CommandHandler) {
246 self.lock_inner().schema.remove(command);
247 self.install_call_handler(command, handler);
248 }
249
250 pub fn register_typed<Req, Resp, F, Fut>(&self, command: &'static str, handler: F)
282 where
283 Req: serde::de::DeserializeOwned + ts_rs::TS + Send + 'static,
284 Resp: serde::Serialize + ts_rs::TS + Send + 'static,
285 F: Fn(Req, RpcContext) -> Fut + Send + Sync + 'static,
286 Fut: std::future::Future<Output = Result<Resp, String>> + Send + 'static,
287 {
288 {
289 let mut inner = self.lock_inner();
290 inner.schema.record_typed::<Req, Resp>(command);
291 }
292
293 let handler = std::sync::Arc::new(handler);
294 self.install_call_handler(
297 command,
298 Box::new(move |data, ctx| {
299 let handler = handler.clone();
300 Box::pin(async move {
301 let req: Req = serde_json::from_value(data)
302 .map_err(|e| format!("{command}: {e}"))?;
303 let resp = handler(req, ctx).await?;
304 let value = serde_json::to_value(&resp)
305 .map_err(|e| format!("{command}: response is not serializable: {e}"))?;
306 Ok(Some(value))
307 })
308 }),
309 );
310 }
311
312 #[cfg(test)]
316 pub fn schema_json(&self) -> serde_json::Value {
317 self.lock_inner().schema.to_json()
318 }
319
320 #[cfg(test)]
322 pub fn typed_command_count(&self) -> usize {
323 self.lock_inner().schema.len()
324 }
325
326 #[allow(dead_code)]
328 pub fn register_stream_handler(&self, command: &str, handler: StreamHandler) {
329 let mut inner = self.lock_inner();
330 inner.schema.remove(command);
333 inner
334 .handlers
335 .insert(command.to_string(), Handler::Stream(handler));
336 }
337
338 #[allow(dead_code)]
340 pub fn set_auth_token(&self, token: &str) {
341 let mut inner = self.lock_inner();
342 inner.auth_token = token.to_string();
343 }
344
345 #[allow(dead_code)]
347 pub fn get_auth_token(&self) -> String {
348 let inner = self.lock_inner();
349 inner.auth_token.clone()
350 }
351
352 #[allow(dead_code)]
354 pub fn set_rpc_context(&self, ctx: RpcContext) {
355 let mut inner = self.lock_inner();
356 inner.rpc_context = Some(ctx);
357 }
358
359 pub fn handle_message(self: &Arc<Self>, msg: RpcMessage) {
361 if msg.cancel {
363 if !msg.reqid.is_empty() {
364 self.handle_cancel_request(&msg.reqid);
365 }
366 return;
367 }
368
369 if msg.command == COMMAND_EVENT_RECV {
371 return;
373 }
374
375 if !msg.command.is_empty() {
377 let engine = self.clone();
378 tokio::spawn(async move {
379 engine.handle_request(msg).await;
380 });
381 return;
382 }
383
384 if !msg.resid.is_empty() {
386 self.handle_response(msg);
387 }
388 }
389
390 #[allow(dead_code)]
392 pub async fn send_command(
393 self: &Arc<Self>,
394 command: &str,
395 data: serde_json::Value,
396 opts: &RpcOpts,
397 ) -> Result<serde_json::Value, String> {
398 let mut handler = self.send_request(command, data, opts)?;
399 match handler.next_response().await {
400 Some(result) => result,
401 None => Err("no response received".to_string()),
402 }
403 }
404
405 #[allow(dead_code)]
407 pub fn send_request(
408 self: &Arc<Self>,
409 command: &str,
410 data: serde_json::Value,
411 opts: &RpcOpts,
412 ) -> Result<RpcRequestHandler, String> {
413 let req_id = Uuid::new_v4().to_string();
414 let (resp_tx, resp_rx) = mpsc::channel(RESP_CH_SIZE);
415
416 {
417 let mut inner = self.lock_inner();
418 inner
419 .pending_responses
420 .insert(req_id.clone(), resp_tx);
421 }
422
423 let timeout = if opts.timeout > 0 {
424 opts.timeout
425 } else {
426 DEFAULT_TIMEOUT_MS
427 };
428 let route = if opts.route.is_empty() {
429 String::new()
430 } else {
431 opts.route.clone()
432 };
433
434 let msg = RpcMessage {
435 command: command.to_string(),
436 reqid: req_id.clone(),
437 timeout,
438 route,
439 data: Some(data),
440 authtoken: self.get_auth_token(),
441 ..Default::default()
442 };
443 self.send_output(msg);
444
445 Ok(RpcRequestHandler {
446 req_id,
447 resp_rx,
448 last_was_cont: true, })
450 }
451
452 #[allow(dead_code)]
454 pub fn send_command_no_response(
455 &self,
456 command: &str,
457 data: serde_json::Value,
458 route: &str,
459 ) {
460 let msg = RpcMessage {
461 command: command.to_string(),
462 data: Some(data),
463 route: route.to_string(),
464 authtoken: self.get_auth_token(),
465 ..Default::default()
466 };
467 self.send_output(msg);
468 }
469
470 fn send_output(&self, msg: RpcMessage) {
473 if self.output_tx.send(msg).is_err() {
474 tracing::warn!("[rpc-engine] output channel closed — message dropped");
475 }
476 }
477
478 async fn handle_request(self: Arc<Self>, msg: RpcMessage) {
479 let request_start = std::time::Instant::now();
480 let timeout_ms = if msg.timeout > 0 {
481 msg.timeout
482 } else {
483 DEFAULT_TIMEOUT_MS
484 };
485
486 let handler = Arc::new(RpcResponseHandler {
487 engine: self.clone(),
488 req_id: msg.reqid.clone(),
489 source: msg.source.clone(),
490 canceled: AtomicBool::new(false),
491 done: AtomicBool::new(false),
492 });
493
494 if !msg.reqid.is_empty() {
496 let mut inner = self.lock_inner();
497 inner
498 .active_handlers
499 .insert(msg.reqid.clone(), handler.clone());
500 }
501
502 let rpc_context = {
503 let inner = self.lock_inner();
504 inner.rpc_context.clone().unwrap_or_default()
505 };
506
507 let data = msg.data.unwrap_or(serde_json::Value::Null);
508 let command = msg.command.clone();
509
510 let has_call;
512 let has_stream;
513 {
514 let inner = self.lock_inner();
515 match inner.handlers.get(&command) {
516 Some(Handler::Call(_)) => {
517 has_call = true;
518 has_stream = false;
519 }
520 Some(Handler::Stream(_)) => {
521 has_call = false;
522 has_stream = true;
523 }
524 None => {
525 has_call = false;
526 has_stream = false;
527 }
528 }
529 }
530
531 let dispatch_elapsed = request_start.elapsed();
532
533 if !has_call && !has_stream {
534 handler.send_error(&format!("unknown command: {}", command));
535 self.cleanup_handler(&msg.reqid);
536 return;
537 }
538
539 let timeout_dur = std::time::Duration::from_millis(timeout_ms as u64);
540
541 if has_call {
542 let handler_start = std::time::Instant::now();
545 let fut = {
546 let inner = self.lock_inner();
547 match inner.handlers.get(&command) {
548 Some(Handler::Call(h)) => h(data.clone(), rpc_context.clone()),
549 _ => Box::pin(async { Err("handler disappeared".to_string()) }),
550 }
551 };
552 let result = tokio::time::timeout(timeout_dur, fut).await;
553 let handler_elapsed = handler_start.elapsed();
554 let total_elapsed = request_start.elapsed();
555
556 tracing::debug!(
562 "[rpc-perf] command={} dispatch={:.2}ms handler={:.2}ms total={:.2}ms",
563 command,
564 dispatch_elapsed.as_secs_f64() * 1000.0,
565 handler_elapsed.as_secs_f64() * 1000.0,
566 total_elapsed.as_secs_f64() * 1000.0,
567 );
568
569 match result {
570 Ok(Ok(resp_data)) => handler.send_response(resp_data, true),
571 Ok(Err(err)) => handler.send_error(&err),
572 Err(_) => handler.send_error(&format!("EC-TIME: timeout ({}ms)", timeout_ms)),
573 }
574 } else {
575 let fut = {
577 let inner = self.lock_inner();
578 match inner.handlers.get(&command) {
579 Some(Handler::Stream(h)) => h(data.clone(), rpc_context.clone()),
580 _ => Box::pin(async { Err("handler disappeared".to_string()) }),
581 }
582 };
583 let stream_result = tokio::time::timeout(timeout_dur, fut).await;
584
585 match stream_result {
586 Ok(Ok(mut rx)) => {
587 loop {
589 match tokio::time::timeout(timeout_dur, rx.recv()).await {
590 Ok(Some(Ok(resp_data))) => {
591 handler.send_response(resp_data, false);
592 }
593 Ok(Some(Err(err))) => {
594 handler.send_error(&err);
595 break;
596 }
597 Ok(None) => {
598 handler.finalize();
600 break;
601 }
602 Err(_) => {
603 handler.send_error(&format!(
604 "EC-TIME: stream timeout ({}ms)",
605 timeout_ms
606 ));
607 break;
608 }
609 }
610 }
611 }
612 Ok(Err(err)) => handler.send_error(&err),
613 Err(_) => {
614 handler.send_error(&format!("EC-TIME: timeout ({}ms)", timeout_ms))
615 }
616 }
617 }
618
619 self.cleanup_handler(&msg.reqid);
620 }
621
622 fn handle_response(&self, msg: RpcMessage) {
623 let inner = self.lock_inner();
624 if let Some(tx) = inner.pending_responses.get(&msg.resid) {
625 let is_done = !msg.cont;
626 if tx.try_send(msg.clone()).is_err() {
627 tracing::warn!(resid = %msg.resid, "[rpc-engine] response channel full/closed — reply dropped");
628 }
629 if is_done {
630 drop(inner);
631 let mut inner = self.lock_inner();
632 inner.pending_responses.remove(&msg.resid);
633 }
634 }
635 }
636
637 fn handle_cancel_request(&self, req_id: &str) {
638 let inner = self.lock_inner();
639 if let Some(handler) = inner.active_handlers.get(req_id) {
640 handler.cancel();
641 }
642 }
643
644 fn cleanup_handler(&self, req_id: &str) {
645 if req_id.is_empty() {
646 return;
647 }
648 let mut inner = self.lock_inner();
649 inner.active_handlers.remove(req_id);
650 }
651}
652
653#[cfg(test)]
658mod tests {
659 use super::*;
660
661 #[tokio::test]
670 async fn replacing_a_typed_command_through_an_untyped_path_clears_its_schema_entry() {
671 #[derive(serde::Deserialize, ts_rs::TS)]
672 struct Req {
673 #[allow(dead_code)]
674 v: i32,
675 }
676 #[derive(serde::Serialize, ts_rs::TS)]
677 struct Resp {
678 ok: bool,
679 }
680
681 let (engine, _rx) = WshRpcEngine::new();
682 engine.register_typed("typed-then-untyped", |_r: Req, _ctx| async move {
683 Ok(Resp { ok: true })
684 });
685 assert_eq!(engine.typed_command_count(), 1, "typed registration should be recorded");
686
687 engine.register_handler(
689 "typed-then-untyped",
690 Box::new(|_data, _ctx| Box::pin(async move { Ok(Some(serde_json::json!("plain"))) })),
691 );
692 assert_eq!(
693 engine.typed_command_count(),
694 0,
695 "an untyped re-registration must drop the stale typed pair",
696 );
697
698 engine.register_typed("typed-then-stream", |_r: Req, _ctx| async move {
701 Ok(Resp { ok: true })
702 });
703 assert_eq!(engine.typed_command_count(), 1);
704 engine.register_stream_handler(
705 "typed-then-stream",
706 Box::new(|_data, _ctx| {
707 Box::pin(async move {
708 let (_tx, rx) = mpsc::channel(1);
709 Ok(rx)
710 })
711 }),
712 );
713 assert_eq!(
714 engine.typed_command_count(),
715 0,
716 "a streaming re-registration must drop the stale typed pair too",
717 );
718 }
719
720 #[tokio::test]
721 async fn register_typed_still_records_after_the_wrapper_is_installed() {
722 #[derive(serde::Deserialize, ts_rs::TS)]
735 struct Req {
736 #[allow(dead_code)]
737 v: i32,
738 }
739 #[derive(serde::Serialize, ts_rs::TS)]
740 struct Resp {
741 ok: bool,
742 }
743
744 let (engine, _rx) = WshRpcEngine::new();
745 engine.register_typed("stays-recorded", |_r: Req, _ctx| async move {
746 Ok(Resp { ok: true })
747 });
748 let schema = engine.schema_json();
749 let rows = schema.as_array().unwrap();
750 assert_eq!(rows.len(), 1, "register_typed must not evict its own entry");
751 assert_eq!(rows[0]["command"], "stays-recorded");
752 }
753
754 #[tokio::test]
755 async fn test_register_and_call_handler() {
756 let (engine, mut output_rx) = WshRpcEngine::new();
757
758 engine.register_handler(
759 "echo",
760 Box::new(|data, _ctx| {
761 Box::pin(async move { Ok(Some(data)) })
762 }),
763 );
764
765 let msg = RpcMessage {
766 command: "echo".to_string(),
767 reqid: "req-1".to_string(),
768 data: Some(serde_json::json!({"hello": "world"})),
769 ..Default::default()
770 };
771 engine.handle_message(msg);
772
773 let resp = tokio::time::timeout(
775 std::time::Duration::from_secs(1),
776 output_rx.recv(),
777 )
778 .await
779 .unwrap()
780 .unwrap();
781
782 assert_eq!(resp.resid, "req-1");
783 assert!(!resp.cont);
784 assert_eq!(resp.data, Some(serde_json::json!({"hello": "world"})));
785 }
786
787 #[tokio::test]
788 async fn test_unknown_command_returns_error() {
789 let (engine, mut output_rx) = WshRpcEngine::new();
790
791 let msg = RpcMessage {
792 command: "nonexistent".to_string(),
793 reqid: "req-2".to_string(),
794 ..Default::default()
795 };
796 engine.handle_message(msg);
797
798 let resp = tokio::time::timeout(
799 std::time::Duration::from_secs(1),
800 output_rx.recv(),
801 )
802 .await
803 .unwrap()
804 .unwrap();
805
806 assert_eq!(resp.resid, "req-2");
807 assert!(resp.error.contains("unknown command"));
808 }
809
810 #[tokio::test]
811 async fn test_handler_error_returns_error_response() {
812 let (engine, mut output_rx) = WshRpcEngine::new();
813
814 engine.register_handler(
815 "failme",
816 Box::new(|_data, _ctx| {
817 Box::pin(async move { Err("something went wrong".to_string()) })
818 }),
819 );
820
821 let msg = RpcMessage {
822 command: "failme".to_string(),
823 reqid: "req-3".to_string(),
824 ..Default::default()
825 };
826 engine.handle_message(msg);
827
828 let resp = tokio::time::timeout(
829 std::time::Duration::from_secs(1),
830 output_rx.recv(),
831 )
832 .await
833 .unwrap()
834 .unwrap();
835
836 assert_eq!(resp.error, "something went wrong");
837 }
838
839 #[tokio::test]
840 async fn test_send_command_roundtrip() {
841 let (engine, mut output_rx) = WshRpcEngine::new();
842
843 let engine_clone = engine.clone();
845 tokio::spawn(async move {
846 if let Some(msg) = output_rx.recv().await {
847 let resp = RpcMessage {
849 resid: msg.reqid.clone(),
850 data: msg.data.clone(),
851 ..Default::default()
852 };
853 engine_clone.handle_message(resp);
854 }
855 });
856
857 let opts = RpcOpts {
858 timeout: 1000,
859 ..Default::default()
860 };
861 let result = engine
862 .send_command("test", serde_json::json!(42), &opts)
863 .await;
864
865 assert!(result.is_ok());
866 assert_eq!(result.unwrap(), serde_json::json!(42));
867 }
868
869 #[tokio::test]
870 async fn test_stream_handler() {
871 let (engine, mut output_rx) = WshRpcEngine::new();
872
873 engine.register_stream_handler(
874 "counter",
875 Box::new(|_data, _ctx| {
876 Box::pin(async move {
877 let (tx, rx) = mpsc::channel(8);
878 tokio::spawn(async move {
879 for i in 0..3 {
880 let _ = tx.send(Ok(Some(serde_json::json!(i)))).await;
881 }
882 });
884 Ok(rx)
885 })
886 }),
887 );
888
889 let msg = RpcMessage {
890 command: "counter".to_string(),
891 reqid: "req-stream".to_string(),
892 ..Default::default()
893 };
894 engine.handle_message(msg);
895
896 let mut responses = Vec::new();
898 for _ in 0..4 {
899 match tokio::time::timeout(
901 std::time::Duration::from_secs(2),
902 output_rx.recv(),
903 )
904 .await
905 {
906 Ok(Some(resp)) => responses.push(resp),
907 _ => break,
908 }
909 }
910
911 assert!(responses.len() >= 3);
913 for resp in &responses[..3] {
915 assert!(resp.cont);
916 }
917 if responses.len() == 4 {
919 assert!(!responses[3].cont);
920 }
921 }
922
923 #[tokio::test(start_paused = true)]
926 async fn test_cancel_request() {
927 let (engine, mut output_rx) = WshRpcEngine::new();
928
929 let (started_tx, started_rx) = tokio::sync::oneshot::channel::<()>();
930 engine.register_handler(
931 "slow",
932 Box::new(move |_data, _ctx| {
933 Box::pin(async move {
934 tokio::time::sleep(std::time::Duration::from_secs(10)).await;
937 Ok(Some(serde_json::json!("done")))
938 })
939 }),
940 );
941
942 let msg = RpcMessage {
944 command: "slow".to_string(),
945 reqid: "req-cancel".to_string(),
946 timeout: 10000,
947 ..Default::default()
948 };
949 engine.handle_message(msg);
950
951 tokio::time::sleep(std::time::Duration::from_millis(50)).await;
953 let cancel_msg = RpcMessage {
954 cancel: true,
955 reqid: "req-cancel".to_string(),
956 ..Default::default()
957 };
958 engine.handle_message(cancel_msg);
959
960 let resp = tokio::time::timeout(
963 std::time::Duration::from_secs(12),
964 output_rx.recv(),
965 )
966 .await;
967 assert!(resp.is_ok());
968 drop(started_tx);
970 drop(started_rx);
971 }
972
973 #[tokio::test]
974 async fn test_send_command_no_response() {
975 let (engine, mut output_rx) = WshRpcEngine::new();
976
977 engine.send_command_no_response("notify", serde_json::json!({"msg": "hi"}), "");
978
979 let msg = tokio::time::timeout(
980 std::time::Duration::from_millis(100),
981 output_rx.recv(),
982 )
983 .await
984 .unwrap()
985 .unwrap();
986
987 assert_eq!(msg.command, "notify");
988 assert!(msg.reqid.is_empty());
989 }
990
991 #[tokio::test]
992 async fn test_auth_token() {
993 let (engine, _output_rx) = WshRpcEngine::new();
994 assert!(engine.get_auth_token().is_empty());
995
996 engine.set_auth_token("my-secret-token");
997 assert_eq!(engine.get_auth_token(), "my-secret-token");
998 }
999
1000 #[tokio::test]
1001 async fn test_rpc_context() {
1002 let (engine, _output_rx) = WshRpcEngine::new();
1003
1004 let ctx = RpcContext {
1005 client_type: "connserver".to_string(),
1006 blockid: "blk-1".to_string(),
1007 ..Default::default()
1008 };
1009 engine.set_rpc_context(ctx);
1010
1011 engine.register_handler(
1013 "checkctx",
1014 Box::new(|_data, ctx| {
1015 Box::pin(async move {
1016 Ok(Some(serde_json::json!({
1017 "ctype": ctx.client_type,
1018 "blockid": ctx.blockid,
1019 })))
1020 })
1021 }),
1022 );
1023
1024 let msg = RpcMessage {
1025 command: "checkctx".to_string(),
1026 reqid: "req-ctx".to_string(),
1027 ..Default::default()
1028 };
1029 engine.handle_message(msg);
1030
1031 }
1034}