1use anyhow::Result;
2use async_trait::async_trait;
3use serde_json::Value;
4use std::collections::HashMap;
5use std::pin::Pin;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum RpcMode {
10 Unary,
11 ServerStream,
12 ClientStream,
13 Bidi,
14}
15
16#[derive(Debug, Clone)]
18pub struct EndpointMeta {
19 pub rpc_mode: RpcMode,
20 pub input_type: Option<String>,
21 pub output_type: Option<String>,
22}
23
24#[derive(Debug, Clone)]
26pub enum CallStreamItem {
27 Message(Value),
28 Trailers(HashMap<String, String>),
29}
30
31#[derive(Debug, Clone)]
33pub struct CallError {
34 pub code: i32,
35 pub message: String,
36}
37
38pub enum CallRequest {
40 Unary(Value),
41 Streaming(Pin<Box<dyn futures::Stream<Item = Value> + Send>>),
42}
43
44#[async_trait]
49pub trait CallClient: Send {
50 async fn resolve_endpoint(&self, endpoint: &str) -> Result<EndpointMeta>;
52
53 async fn call(
55 &mut self,
56 endpoint: &str,
57 request: CallRequest,
58 ) -> Result<Pin<Box<dyn futures::Stream<Item = Result<CallStreamItem, CallError>> + Send>>>;
59}
60
61#[async_trait]
63pub trait CallClientFactory: Send + Sync {
64 async fn create_client(
65 &self,
66 config: &crate::config::CallClientConfig,
67 ) -> Result<Box<dyn CallClient>>;
68}
69
70#[cfg(test)]
71mod tests {
72 use super::*;
73 use std::collections::HashMap;
74
75 #[test]
76 fn test_rpc_mode_debug() {
77 assert_eq!(format!("{:?}", RpcMode::Unary), "Unary");
78 assert_eq!(format!("{:?}", RpcMode::ServerStream), "ServerStream");
79 assert_eq!(format!("{:?}", RpcMode::ClientStream), "ClientStream");
80 assert_eq!(format!("{:?}", RpcMode::Bidi), "Bidi");
81 }
82
83 #[test]
84 fn test_call_error_new() {
85 let err = CallError {
86 code: 5,
87 message: "not found".into(),
88 };
89 assert_eq!(err.code, 5);
90 assert_eq!(err.message, "not found");
91 }
92
93 #[test]
94 fn test_call_stream_item_message() {
95 let item = CallStreamItem::Message(serde_json::json!({"key": "value"}));
96 match item {
97 CallStreamItem::Message(v) => assert_eq!(v["key"], "value"),
98 _ => panic!("expected Message"),
99 }
100 }
101
102 #[test]
103 fn test_call_stream_item_trailers() {
104 let mut h = HashMap::new();
105 h.insert("x-status".into(), "ok".into());
106 let item = CallStreamItem::Trailers(h);
107 match item {
108 CallStreamItem::Trailers(t) => assert_eq!(t.get("x-status"), Some(&"ok".into())),
109 _ => panic!("expected Trailers"),
110 }
111 }
112
113 #[test]
114 fn test_endpoint_meta_new() {
115 let meta = EndpointMeta {
116 rpc_mode: RpcMode::Unary,
117 input_type: Some("test.Request".into()),
118 output_type: Some("test.Response".into()),
119 };
120 assert_eq!(meta.rpc_mode, RpcMode::Unary);
121 assert_eq!(meta.input_type.as_deref(), Some("test.Request"));
122 }
123}