1use crate::resolve::{interpolate, UnresolvedEnvVarError, RE_PLACEHOLDER};
4use serde::{ser::SerializeMap, Deserialize, Deserializer, Serialize, Serializer};
5use std::{
6 collections::BTreeMap,
7 fmt,
8 ops::{Deref, DerefMut},
9};
10
11#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
13#[serde(transparent)]
14pub struct RpcEndpoints {
15 endpoints: BTreeMap<String, RpcEndpoint>,
16}
17
18impl RpcEndpoints {
19 pub fn new(
21 endpoints: impl IntoIterator<Item = (impl Into<String>, impl Into<RpcEndpointType>)>,
22 ) -> Self {
23 Self {
24 endpoints: endpoints
25 .into_iter()
26 .map(|(name, e)| match e.into() {
27 RpcEndpointType::String(url) => (name.into(), RpcEndpoint::new(url)),
28 RpcEndpointType::Config(config) => (name.into(), config),
29 })
30 .collect(),
31 }
32 }
33
34 pub fn is_empty(&self) -> bool {
36 self.endpoints.is_empty()
37 }
38
39 pub fn resolved(self) -> ResolvedRpcEndpoints {
41 ResolvedRpcEndpoints {
42 endpoints: self.endpoints.into_iter().map(|(name, e)| (name, e.resolve())).collect(),
43 }
44 }
45}
46
47impl Deref for RpcEndpoints {
48 type Target = BTreeMap<String, RpcEndpoint>;
49
50 fn deref(&self) -> &Self::Target {
51 &self.endpoints
52 }
53}
54
55#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
57#[serde(untagged)]
58pub enum RpcEndpointType {
59 String(RpcEndpointUrl),
61 Config(RpcEndpoint),
63}
64
65impl RpcEndpointType {
66 pub fn as_endpoint_string(&self) -> Option<&RpcEndpointUrl> {
68 match self {
69 Self::String(url) => Some(url),
70 Self::Config(_) => None,
71 }
72 }
73
74 pub fn as_endpoint_config(&self) -> Option<&RpcEndpoint> {
76 match self {
77 Self::Config(config) => Some(config),
78 Self::String(_) => None,
79 }
80 }
81
82 pub fn resolve(self) -> Result<String, UnresolvedEnvVarError> {
88 match self {
89 Self::String(url) => url.resolve(),
90 Self::Config(config) => config.endpoint.resolve(),
91 }
92 }
93}
94
95impl fmt::Display for RpcEndpointType {
96 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97 match self {
98 Self::String(url) => url.fmt(f),
99 Self::Config(config) => config.fmt(f),
100 }
101 }
102}
103
104impl TryFrom<RpcEndpointType> for String {
105 type Error = UnresolvedEnvVarError;
106
107 fn try_from(value: RpcEndpointType) -> Result<Self, Self::Error> {
108 match value {
109 RpcEndpointType::String(url) => url.resolve(),
110 RpcEndpointType::Config(config) => config.endpoint.resolve(),
111 }
112 }
113}
114
115#[derive(Clone, Debug, PartialEq, Eq)]
122pub enum RpcEndpointUrl {
123 Url(String),
125 Env(String),
129}
130
131impl RpcEndpointUrl {
132 pub fn as_url(&self) -> Option<&str> {
134 match self {
135 Self::Url(url) => Some(url),
136 Self::Env(_) => None,
137 }
138 }
139
140 pub fn as_env(&self) -> Option<&str> {
142 match self {
143 Self::Env(val) => Some(val),
144 Self::Url(_) => None,
145 }
146 }
147
148 pub fn resolve(self) -> Result<String, UnresolvedEnvVarError> {
154 match self {
155 Self::Url(url) => Ok(url),
156 Self::Env(val) => interpolate(&val),
157 }
158 }
159}
160
161impl fmt::Display for RpcEndpointUrl {
162 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
163 match self {
164 Self::Url(url) => url.fmt(f),
165 Self::Env(var) => var.fmt(f),
166 }
167 }
168}
169
170impl TryFrom<RpcEndpointUrl> for String {
171 type Error = UnresolvedEnvVarError;
172
173 fn try_from(value: RpcEndpointUrl) -> Result<Self, Self::Error> {
174 value.resolve()
175 }
176}
177
178impl Serialize for RpcEndpointUrl {
179 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
180 where
181 S: Serializer,
182 {
183 serializer.serialize_str(&self.to_string())
184 }
185}
186
187impl<'de> Deserialize<'de> for RpcEndpointUrl {
188 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
189 where
190 D: Deserializer<'de>,
191 {
192 let val = String::deserialize(deserializer)?;
193 let endpoint = if RE_PLACEHOLDER.is_match(&val) { Self::Env(val) } else { Self::Url(val) };
194
195 Ok(endpoint)
196 }
197}
198
199impl From<RpcEndpointUrl> for RpcEndpointType {
200 fn from(endpoint: RpcEndpointUrl) -> Self {
201 Self::String(endpoint)
202 }
203}
204
205impl From<RpcEndpointUrl> for RpcEndpoint {
206 fn from(endpoint: RpcEndpointUrl) -> Self {
207 Self { endpoint, ..Default::default() }
208 }
209}
210
211#[derive(Clone, Debug, PartialEq, Eq)]
214pub enum RpcAuth {
215 Raw(String),
216 Env(String),
217}
218
219impl RpcAuth {
220 pub fn resolve(self) -> Result<String, UnresolvedEnvVarError> {
226 match self {
227 Self::Raw(raw_auth) => Ok(raw_auth),
228 Self::Env(var) => interpolate(&var),
229 }
230 }
231}
232
233impl fmt::Display for RpcAuth {
234 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
235 match self {
236 Self::Raw(url) => url.fmt(f),
237 Self::Env(var) => var.fmt(f),
238 }
239 }
240}
241
242impl Serialize for RpcAuth {
243 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
244 where
245 S: Serializer,
246 {
247 serializer.serialize_str(&self.to_string())
248 }
249}
250
251impl<'de> Deserialize<'de> for RpcAuth {
252 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
253 where
254 D: Deserializer<'de>,
255 {
256 let val = String::deserialize(deserializer)?;
257 let auth = if RE_PLACEHOLDER.is_match(&val) { Self::Env(val) } else { Self::Raw(val) };
258
259 Ok(auth)
260 }
261}
262
263#[derive(Debug, Clone, Default, PartialEq, Eq)]
265pub struct RpcEndpointConfig {
266 pub retries: Option<u32>,
268
269 pub retry_backoff: Option<u64>,
271
272 pub compute_units_per_second: Option<u64>,
276}
277
278impl fmt::Display for RpcEndpointConfig {
279 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
280 let Self { retries, retry_backoff, compute_units_per_second } = self;
281
282 if let Some(retries) = retries {
283 write!(f, ", retries={retries}")?;
284 }
285
286 if let Some(retry_backoff) = retry_backoff {
287 write!(f, ", retry_backoff={retry_backoff}")?;
288 }
289
290 if let Some(compute_units_per_second) = compute_units_per_second {
291 write!(f, ", compute_units_per_second={compute_units_per_second}")?;
292 }
293
294 Ok(())
295 }
296}
297
298#[derive(Debug, Clone, PartialEq, Eq)]
300pub struct RpcEndpoint {
301 pub endpoint: RpcEndpointUrl,
303
304 pub auth: Option<RpcAuth>,
306
307 pub config: RpcEndpointConfig,
309}
310
311impl RpcEndpoint {
312 pub fn new(endpoint: RpcEndpointUrl) -> Self {
313 Self { endpoint, ..Default::default() }
314 }
315
316 pub fn resolve(self) -> ResolvedRpcEndpoint {
318 ResolvedRpcEndpoint {
319 endpoint: self.endpoint.resolve(),
320 auth: self.auth.map(|auth| auth.resolve()),
321 config: self.config,
322 }
323 }
324}
325
326impl fmt::Display for RpcEndpoint {
327 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
328 let Self { endpoint, auth, config } = self;
329 write!(f, "{endpoint}")?;
330 write!(f, "{config}")?;
331 if let Some(auth) = auth {
332 write!(f, ", auth={auth}")?;
333 }
334 Ok(())
335 }
336}
337
338impl Serialize for RpcEndpoint {
339 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
340 where
341 S: Serializer,
342 {
343 if self.config.retries.is_none() &&
344 self.config.retry_backoff.is_none() &&
345 self.config.compute_units_per_second.is_none() &&
346 self.auth.is_none()
347 {
348 self.endpoint.serialize(serializer)
350 } else {
351 let mut map = serializer.serialize_map(Some(4))?;
352 map.serialize_entry("endpoint", &self.endpoint)?;
353 map.serialize_entry("retries", &self.config.retries)?;
354 map.serialize_entry("retry_backoff", &self.config.retry_backoff)?;
355 map.serialize_entry("compute_units_per_second", &self.config.compute_units_per_second)?;
356 map.serialize_entry("auth", &self.auth)?;
357 map.end()
358 }
359 }
360}
361
362impl<'de> Deserialize<'de> for RpcEndpoint {
363 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
364 where
365 D: Deserializer<'de>,
366 {
367 let value = serde_json::Value::deserialize(deserializer)?;
368 if value.is_string() {
369 return Ok(Self {
370 endpoint: serde_json::from_value(value).map_err(serde::de::Error::custom)?,
371 ..Default::default()
372 });
373 }
374
375 #[derive(Deserialize)]
376 struct RpcEndpointConfigInner {
377 #[serde(alias = "url")]
378 endpoint: RpcEndpointUrl,
379 retries: Option<u32>,
380 retry_backoff: Option<u64>,
381 compute_units_per_second: Option<u64>,
382 auth: Option<RpcAuth>,
383 }
384
385 let RpcEndpointConfigInner {
386 endpoint,
387 retries,
388 retry_backoff,
389 compute_units_per_second,
390 auth,
391 } = serde_json::from_value(value).map_err(serde::de::Error::custom)?;
392
393 Ok(Self {
394 endpoint,
395 auth,
396 config: RpcEndpointConfig { retries, retry_backoff, compute_units_per_second },
397 })
398 }
399}
400
401impl From<RpcEndpoint> for RpcEndpointType {
402 fn from(config: RpcEndpoint) -> Self {
403 Self::Config(config)
404 }
405}
406
407impl Default for RpcEndpoint {
408 fn default() -> Self {
409 Self {
410 endpoint: RpcEndpointUrl::Url("http://localhost:8545".to_string()),
411 config: RpcEndpointConfig::default(),
412 auth: None,
413 }
414 }
415}
416
417#[derive(Clone, Debug, PartialEq, Eq)]
419pub struct ResolvedRpcEndpoint {
420 pub endpoint: Result<String, UnresolvedEnvVarError>,
421 pub auth: Option<Result<String, UnresolvedEnvVarError>>,
422 pub config: RpcEndpointConfig,
423}
424
425impl ResolvedRpcEndpoint {
426 pub fn url(&self) -> Result<String, UnresolvedEnvVarError> {
428 self.endpoint.clone()
429 }
430
431 pub fn is_unresolved(&self) -> bool {
433 let endpoint_err = self.endpoint.is_err();
434 let auth_err = self.auth.as_ref().map(|auth| auth.is_err()).unwrap_or(false);
435 endpoint_err || auth_err
436 }
437
438 pub fn try_resolve(mut self) -> Self {
440 if !self.is_unresolved() {
441 return self
442 }
443 if let Err(err) = self.endpoint {
444 self.endpoint = err.try_resolve()
445 }
446 if let Some(Err(err)) = self.auth {
447 self.auth = Some(err.try_resolve())
448 }
449 self
450 }
451}
452
453#[derive(Clone, Debug, Default, PartialEq, Eq)]
455pub struct ResolvedRpcEndpoints {
456 endpoints: BTreeMap<String, ResolvedRpcEndpoint>,
457}
458
459impl ResolvedRpcEndpoints {
460 pub fn has_unresolved(&self) -> bool {
462 self.endpoints.values().any(|e| e.is_unresolved())
463 }
464}
465
466impl Deref for ResolvedRpcEndpoints {
467 type Target = BTreeMap<String, ResolvedRpcEndpoint>;
468
469 fn deref(&self) -> &Self::Target {
470 &self.endpoints
471 }
472}
473
474impl DerefMut for ResolvedRpcEndpoints {
475 fn deref_mut(&mut self) -> &mut Self::Target {
476 &mut self.endpoints
477 }
478}
479
480#[cfg(test)]
481mod tests {
482 use super::*;
483
484 #[test]
485 fn serde_rpc_config() {
486 let s = r#"{
487 "endpoint": "http://localhost:8545",
488 "retries": 5,
489 "retry_backoff": 250,
490 "compute_units_per_second": 100,
491 "auth": "Bearer 123"
492 }"#;
493 let config: RpcEndpoint = serde_json::from_str(s).unwrap();
494 assert_eq!(
495 config,
496 RpcEndpoint {
497 endpoint: RpcEndpointUrl::Url("http://localhost:8545".to_string()),
498 config: RpcEndpointConfig {
499 retries: Some(5),
500 retry_backoff: Some(250),
501 compute_units_per_second: Some(100),
502 },
503 auth: Some(RpcAuth::Raw("Bearer 123".to_string())),
504 }
505 );
506
507 let s = "\"http://localhost:8545\"";
508 let config: RpcEndpoint = serde_json::from_str(s).unwrap();
509 assert_eq!(
510 config,
511 RpcEndpoint {
512 endpoint: RpcEndpointUrl::Url("http://localhost:8545".to_string()),
513 config: RpcEndpointConfig {
514 retries: None,
515 retry_backoff: None,
516 compute_units_per_second: None,
517 },
518 auth: None,
519 }
520 );
521 }
522}