1use std::fmt;
11use std::future::Future;
12use std::pin::Pin;
13use std::sync::Arc;
14use std::time::Duration;
15
16use tonic::transport::{Certificate, Channel, ClientTlsConfig, Endpoint, Identity};
17
18use crate::retry::RetryConfig;
19use crate::{Error, Result};
20
21pub trait TokenSource: Send + Sync + fmt::Debug {
27 fn fetch_bearer(&self) -> Pin<Box<dyn Future<Output = Result<Option<String>>> + Send + '_>>;
30}
31
32#[derive(Clone)]
38#[non_exhaustive]
39pub enum Auth {
40 None,
42 Static(String),
44 Dynamic(Arc<dyn TokenSource>),
46}
47
48impl Auth {
49 pub async fn bearer(&self) -> Result<Option<String>> {
54 match self {
55 Auth::None => Ok(None),
56 Auth::Static(token) => Ok(Some(token.clone())),
57 Auth::Dynamic(source) => source.fetch_bearer().await,
58 }
59 }
60}
61
62impl fmt::Debug for Auth {
63 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64 match self {
65 Auth::None => f.write_str("None"),
66 Auth::Static(_) => f.write_str("Static(<redacted>)"),
67 Auth::Dynamic(source) => write!(f, "Dynamic({source:?})"),
68 }
69 }
70}
71
72#[derive(Clone, Debug, Default)]
79#[non_exhaustive]
80pub struct TlsConfig {
81 pub ca_certificate_pem: Option<Vec<u8>>,
84 pub domain_name: Option<String>,
87 pub client_identity_pem: Option<(Vec<u8>, Vec<u8>)>,
89}
90
91impl TlsConfig {
92 #[must_use]
94 pub fn new() -> Self {
95 Self::default()
96 }
97
98 #[must_use]
100 pub fn with_ca_certificate(mut self, ca_pem: impl Into<Vec<u8>>) -> Self {
101 self.ca_certificate_pem = Some(ca_pem.into());
102 self
103 }
104
105 #[must_use]
107 pub fn with_domain_name(mut self, domain: impl Into<String>) -> Self {
108 self.domain_name = Some(domain.into());
109 self
110 }
111
112 #[must_use]
114 pub fn with_client_identity(
115 mut self,
116 certificate_pem: impl Into<Vec<u8>>,
117 private_key_pem: impl Into<Vec<u8>>,
118 ) -> Self {
119 self.client_identity_pem = Some((certificate_pem.into(), private_key_pem.into()));
120 self
121 }
122}
123
124fn build_tls(tls: Option<&TlsConfig>) -> ClientTlsConfig {
127 let mut config = ClientTlsConfig::new();
128 match tls.and_then(|t| t.ca_certificate_pem.as_ref()) {
129 Some(ca) => config = config.ca_certificate(Certificate::from_pem(ca.clone())),
130 None => config = config.with_native_roots(),
131 }
132 if let Some(domain) = tls.and_then(|t| t.domain_name.as_ref()) {
133 config = config.domain_name(domain.clone());
134 }
135 if let Some((cert, key)) = tls.and_then(|t| t.client_identity_pem.as_ref()) {
136 config = config.identity(Identity::from_pem(cert.clone(), key.clone()));
137 }
138 config
139}
140
141#[derive(Clone, Debug)]
144pub struct Config {
145 endpoint: String,
146 auth: Auth,
147 retry: Option<RetryConfig>,
148 tls: Option<TlsConfig>,
149 timeout: Option<Duration>,
150}
151
152impl Config {
153 pub fn new(endpoint: impl Into<String>) -> Self {
156 Self {
157 endpoint: endpoint.into(),
158 auth: Auth::None,
159 retry: None,
160 tls: None,
161 timeout: None,
162 }
163 }
164
165 #[must_use]
167 pub fn with_token(mut self, token: impl Into<String>) -> Self {
168 self.auth = Auth::Static(token.into());
169 self
170 }
171
172 #[must_use]
175 pub fn with_oidc<T: TokenSource + 'static>(mut self, provider: T) -> Self {
176 self.auth = Auth::Dynamic(Arc::new(provider));
177 self
178 }
179
180 #[must_use]
182 pub fn with_retry(mut self, retry: RetryConfig) -> Self {
183 self.retry = Some(retry);
184 self
185 }
186
187 #[must_use]
189 pub fn with_tls(mut self, tls: TlsConfig) -> Self {
190 self.tls = Some(tls);
191 self
192 }
193
194 #[must_use]
197 pub fn with_timeout(mut self, timeout: Duration) -> Self {
198 self.timeout = Some(timeout);
199 self
200 }
201
202 #[must_use]
204 pub fn endpoint(&self) -> &str {
205 &self.endpoint
206 }
207
208 #[must_use]
210 pub fn auth(&self) -> &Auth {
211 &self.auth
212 }
213
214 #[must_use]
216 pub fn retry(&self) -> Option<&RetryConfig> {
217 self.retry.as_ref()
218 }
219
220 pub fn connect_channel(&self) -> Result<Channel> {
229 let mut endpoint = Endpoint::from_shared(self.endpoint.clone())
230 .map_err(|e| {
231 Error::InvalidRequest(format!("invalid endpoint uri {:?}: {e}", self.endpoint))
232 })?
233 .timeout(self.timeout.unwrap_or(Duration::from_secs(30)))
234 .connect_timeout(Duration::from_secs(10))
235 .http2_keep_alive_interval(Duration::from_secs(30))
236 .keep_alive_timeout(Duration::from_secs(20))
237 .keep_alive_while_idle(true)
238 .tcp_keepalive(Some(Duration::from_secs(60)))
239 .tcp_nodelay(true);
240
241 if self.tls.is_some() || self.endpoint.starts_with("https") {
242 endpoint = endpoint
243 .tls_config(build_tls(self.tls.as_ref()))
244 .map_err(|e| Error::InvalidRequest(format!("invalid TLS config: {e}")))?;
245 }
246
247 Ok(endpoint.connect_lazy())
248 }
249}