Skip to main content

karyon_jsonrpc/client/
builder.rs

1use std::sync::Arc;
2
3use karyon_core::async_runtime::Executor;
4use karyon_net::ToEndpoint;
5
6#[cfg(any(feature = "tcp", feature = "tls", feature = "quic"))]
7use karyon_net::Endpoint;
8
9#[cfg(feature = "quic")]
10use karyon_net::quic::ClientQuicConfig;
11#[cfg(feature = "tcp")]
12use karyon_net::tcp::TcpConfig;
13
14use crate::{
15    client::{
16        subscriptions::{default_notification_decoder, NotificationDecoder},
17        WsCodec,
18    },
19    codec::{JsonCodec, JsonRpcCodec},
20    error::Result,
21};
22
23#[cfg(any(feature = "tcp", feature = "tls", feature = "quic"))]
24use crate::error::Error;
25
26#[cfg(feature = "http")]
27use super::HttpConfig;
28use super::{Client, ClientConfig};
29
30const DEFAULT_TIMEOUT: u64 = 3000;
31const DEFAULT_MAX_SUBSCRIPTION_BUFFER_SIZE: usize = 20000;
32
33/// Builder for constructing an RPC [`Client`].
34///
35/// # Example
36///
37/// ```no_run
38/// use karyon_jsonrpc::client::ClientBuilder;
39///
40/// async {
41///     let client = ClientBuilder::new("tcp://127.0.0.1:60000")
42///         .expect("create builder")
43///         .set_timeout(5000)
44///         .build().await
45///         .expect("build client");
46///
47///     let result: i32 = client
48///         .call("Calc.add", (1, 2))
49///         .await
50///         .expect("call method");
51/// };
52/// ```
53pub struct ClientBuilder<B, W = JsonCodec> {
54    inner: ClientConfig,
55    byte_codec: B,
56    ws_codec: W,
57    executor: Option<Executor>,
58}
59
60impl ClientBuilder<JsonCodec, JsonCodec> {
61    /// Create a builder with the default JSON codec.
62    pub fn new(endpoint: impl ToEndpoint) -> Result<ClientBuilder<JsonCodec, JsonCodec>> {
63        ClientBuilder::new_with_codec(endpoint, JsonCodec::default())
64    }
65}
66
67impl<B: JsonRpcCodec> ClientBuilder<B, JsonCodec> {
68    /// Create a builder with a custom byte-stream codec. The default
69    /// `JsonCodec` is used for `ws://` / `wss://` endpoints; override
70    /// with `with_ws_codec`.
71    pub fn new_with_codec(
72        endpoint: impl ToEndpoint,
73        codec: B,
74    ) -> Result<ClientBuilder<B, JsonCodec>> {
75        let endpoint = endpoint.to_endpoint()?;
76        Ok(ClientBuilder {
77            inner: ClientConfig {
78                endpoint,
79                timeout: Some(DEFAULT_TIMEOUT),
80                #[cfg(feature = "tcp")]
81                tcp_config: Default::default(),
82                #[cfg(feature = "tls")]
83                tls_config: None,
84                #[cfg(feature = "quic")]
85                quic_config: None,
86                #[cfg(feature = "http")]
87                http_config: HttpConfig::default(),
88                subscription_buffer_size: DEFAULT_MAX_SUBSCRIPTION_BUFFER_SIZE,
89                notification_decoder: default_notification_decoder,
90            },
91            byte_codec: codec,
92            ws_codec: JsonCodec::default(),
93            executor: None,
94        })
95    }
96}
97
98impl<B, W> ClientBuilder<B, W>
99where
100    B: JsonRpcCodec,
101    W: WsCodec,
102{
103    /// Set request timeout in milliseconds.
104    pub fn set_timeout(mut self, timeout: u64) -> Self {
105        self.inner.timeout = Some(timeout);
106        self
107    }
108
109    /// Set max subscription buffer size.
110    pub fn set_max_subscription_buffer_size(mut self, size: usize) -> Self {
111        self.inner.subscription_buffer_size = size;
112        self
113    }
114
115    /// Set a custom notification decoder. Use it when the server sends
116    /// notifications in another format (`ServerBuilder::with_notification_encoder`).
117    pub fn with_notification_decoder(mut self, decoder: NotificationDecoder) -> Self {
118        self.inner.notification_decoder = decoder;
119        self
120    }
121
122    /// Set TCP config.
123    #[cfg(feature = "tcp")]
124    pub fn tcp_config(mut self, config: TcpConfig) -> Result<Self> {
125        match self.inner.endpoint {
126            Endpoint::Tcp(..) | Endpoint::Tls(..) | Endpoint::Ws(..) | Endpoint::Wss(..) => {
127                self.inner.tcp_config = config;
128                Ok(self)
129            }
130            _ => Err(Error::UnsupportedProtocol(self.inner.endpoint.to_string())),
131        }
132    }
133
134    /// Set TLS config.
135    #[cfg(feature = "tls")]
136    pub fn tls_config(mut self, config: karyon_net::tls::ClientTlsConfig) -> Result<Self> {
137        match self.inner.endpoint {
138            Endpoint::Tls(..) | Endpoint::Wss(..) => {
139                self.inner.tls_config = Some(config);
140                Ok(self)
141            }
142            _ => Err(Error::UnsupportedProtocol(format!(
143                "Invalid tls config for endpoint: {}",
144                self.inner.endpoint
145            ))),
146        }
147    }
148
149    /// Set QUIC config.
150    #[cfg(feature = "quic")]
151    pub fn quic_config(mut self, config: ClientQuicConfig) -> Result<Self> {
152        match self.inner.endpoint {
153            Endpoint::Quic(..) => {
154                self.inner.quic_config = Some(config);
155                Ok(self)
156            }
157            #[cfg(feature = "http3")]
158            Endpoint::Http(..) => {
159                self.inner.quic_config = Some(config);
160                Ok(self)
161            }
162            _ => Err(Error::UnsupportedProtocol(format!(
163                "Invalid quic config for endpoint: {}",
164                self.inner.endpoint
165            ))),
166        }
167    }
168
169    /// Set HTTP config (HTTP/2, `Host` header) for an `http://` endpoint.
170    #[cfg(feature = "http")]
171    pub fn http_config(mut self, config: HttpConfig) -> Result<Self> {
172        match self.inner.endpoint {
173            Endpoint::Http(..) => {
174                self.inner.http_config = config;
175                Ok(self)
176            }
177            _ => Err(Error::UnsupportedProtocol(format!(
178                "Invalid http config for endpoint: {}",
179                self.inner.endpoint
180            ))),
181        }
182    }
183
184    /// Override the WebSocket codec. Only meaningful for `ws://` /
185    /// `wss://` endpoints.
186    #[cfg(feature = "ws")]
187    pub fn with_ws_codec<W2: WsCodec>(self, ws_codec: W2) -> ClientBuilder<B, W2> {
188        ClientBuilder {
189            inner: self.inner,
190            byte_codec: self.byte_codec,
191            ws_codec,
192            executor: self.executor,
193        }
194    }
195
196    /// Set an executor. Used for the client's task group.
197    pub fn with_executor(mut self, ex: Executor) -> Self {
198        self.executor = Some(ex);
199        self
200    }
201
202    /// Build the client.
203    pub async fn build(self) -> Result<Arc<Client<B, W>>> {
204        Client::init(self.inner, self.byte_codec, self.ws_codec, self.executor).await
205    }
206}