1use crate::error::{ClientError, Result};
2use std::time::Duration;
3use tonic::transport::{Channel, ClientTlsConfig, Endpoint};
4
5#[derive(Clone, Debug)]
6pub struct Client {
7 channel: Channel,
8 endpoint: String,
9}
10
11#[derive(Debug)]
13pub struct ClientBuilder {
14 endpoint: String,
15 timeout: Option<Duration>,
16 connect_timeout: Option<Duration>,
17 tls_config: Option<ClientTlsConfig>,
18}
19
20impl Client {
21 pub async fn connect(endpoint: impl Into<String>) -> Result<Self> {
23 let endpoint = endpoint.into();
24 let channel = Self::create_channel(&endpoint).await?;
25
26 Ok(Self { channel, endpoint })
27 }
28
29 pub fn builder() -> ClientBuilder {
31 ClientBuilder {
32 endpoint: String::new(),
33 timeout: None,
34 connect_timeout: None,
35 tls_config: None,
36 }
37 }
38
39 pub async fn connect_with_config<F>(endpoint: impl Into<String>, config: F) -> Result<Self>
41 where
42 F: FnOnce(Endpoint) -> Endpoint,
43 {
44 let endpoint_str = endpoint.into();
45 let endpoint = Endpoint::from_shared(endpoint_str.clone())
46 .map_err(|e| ClientError::InvalidEndpoint(e.to_string()))?;
47
48 let endpoint = config(endpoint);
49 let channel = endpoint.connect().await.map_err(ClientError::Transport)?;
50
51 Ok(Self {
52 channel,
53 endpoint: endpoint_str,
54 })
55 }
56
57 pub fn channel(&self) -> &Channel {
59 &self.channel
60 }
61
62 pub fn endpoint(&self) -> &str {
64 &self.endpoint
65 }
66
67 async fn create_channel(endpoint: &str) -> Result<Channel> {
68 let endpoint = Endpoint::from_shared(endpoint.to_string())
69 .map_err(|e| ClientError::InvalidEndpoint(e.to_string()))?
70 .timeout(Duration::from_secs(10))
71 .connect_timeout(Duration::from_secs(5));
72
73 let channel = endpoint.connect().await.map_err(ClientError::Transport)?;
74
75 Ok(channel)
76 }
77}
78
79impl ClientBuilder {
80 pub fn endpoint(mut self, endpoint: impl Into<String>) -> Self {
82 self.endpoint = endpoint.into();
83 self
84 }
85
86 pub fn timeout(mut self, timeout: Duration) -> Self {
88 self.timeout = Some(timeout);
89 self
90 }
91
92 pub fn connect_timeout(mut self, timeout: Duration) -> Self {
94 self.connect_timeout = Some(timeout);
95 self
96 }
97
98 pub fn tls(mut self) -> Self {
100 self.tls_config = Some(ClientTlsConfig::new());
101 self
102 }
103
104 pub fn tls_config(mut self, config: ClientTlsConfig) -> Self {
106 self.tls_config = Some(config);
107 self
108 }
109
110 pub async fn build(self) -> Result<Client> {
112 if self.endpoint.is_empty() {
113 return Err(ClientError::InvalidEndpoint(
114 "Endpoint cannot be empty".to_string(),
115 ));
116 }
117
118 let endpoint = Endpoint::from_shared(self.endpoint.clone())
119 .map_err(|e| ClientError::InvalidEndpoint(e.to_string()))?;
120
121 let endpoint = if let Some(timeout) = self.timeout {
123 endpoint.timeout(timeout)
124 } else {
125 endpoint.timeout(Duration::from_secs(10))
126 };
127
128 let endpoint = if let Some(connect_timeout) = self.connect_timeout {
129 endpoint.connect_timeout(connect_timeout)
130 } else {
131 endpoint.connect_timeout(Duration::from_secs(5))
132 };
133
134 let endpoint = if let Some(tls_config) = self.tls_config {
136 endpoint.tls_config(tls_config)?
137 } else {
138 endpoint
139 };
140
141 let channel = endpoint.connect().await.map_err(ClientError::Transport)?;
142
143 Ok(Client {
144 channel,
145 endpoint: self.endpoint,
146 })
147 }
148}