1use crate::resolve::{RE_PLACEHOLDER, UnresolvedEnvVarError, interpolate};
4use serde::{Deserialize, Deserializer, Serialize, Serializer, ser::SerializeMap};
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 resolve(self) -> Result<String, UnresolvedEnvVarError> {
72 match self {
73 Self::String(url) => url.resolve(),
74 Self::Config(config) => config.endpoint.resolve(),
75 }
76 }
77}
78
79impl fmt::Display for RpcEndpointType {
80 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
81 match self {
82 Self::String(url) => url.fmt(f),
83 Self::Config(config) => config.fmt(f),
84 }
85 }
86}
87
88impl TryFrom<RpcEndpointType> for String {
89 type Error = UnresolvedEnvVarError;
90
91 fn try_from(value: RpcEndpointType) -> Result<Self, Self::Error> {
92 match value {
93 RpcEndpointType::String(url) => url.resolve(),
94 RpcEndpointType::Config(config) => config.endpoint.resolve(),
95 }
96 }
97}
98
99#[derive(Clone, Debug, PartialEq, Eq)]
106pub enum RpcEndpointUrl {
107 Url(String),
109 Env(String),
113}
114
115impl RpcEndpointUrl {
116 pub fn as_url(&self) -> Option<&str> {
118 match self {
119 Self::Url(url) => Some(url),
120 Self::Env(_) => None,
121 }
122 }
123
124 pub fn resolve(self) -> Result<String, UnresolvedEnvVarError> {
130 match self {
131 Self::Url(url) => Ok(url),
132 Self::Env(val) => interpolate(&val),
133 }
134 }
135}
136
137impl fmt::Display for RpcEndpointUrl {
138 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
139 match self {
140 Self::Url(url) => url.fmt(f),
141 Self::Env(var) => var.fmt(f),
142 }
143 }
144}
145
146impl TryFrom<RpcEndpointUrl> for String {
147 type Error = UnresolvedEnvVarError;
148
149 fn try_from(value: RpcEndpointUrl) -> Result<Self, Self::Error> {
150 value.resolve()
151 }
152}
153
154impl Serialize for RpcEndpointUrl {
155 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
156 where
157 S: Serializer,
158 {
159 serializer.serialize_str(&self.to_string())
160 }
161}
162
163impl<'de> Deserialize<'de> for RpcEndpointUrl {
164 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
165 where
166 D: Deserializer<'de>,
167 {
168 let val = String::deserialize(deserializer)?;
169 let endpoint = if RE_PLACEHOLDER.is_match(&val) { Self::Env(val) } else { Self::Url(val) };
170
171 Ok(endpoint)
172 }
173}
174
175impl From<RpcEndpointUrl> for RpcEndpointType {
176 fn from(endpoint: RpcEndpointUrl) -> Self {
177 Self::String(endpoint)
178 }
179}
180
181impl From<RpcEndpointUrl> for RpcEndpoint {
182 fn from(endpoint: RpcEndpointUrl) -> Self {
183 Self { endpoint, ..Default::default() }
184 }
185}
186
187#[derive(Clone, Debug, PartialEq, Eq)]
190pub enum RpcAuth {
191 Raw(String),
192 Env(String),
193}
194
195impl RpcAuth {
196 pub fn resolve(self) -> Result<String, UnresolvedEnvVarError> {
202 match self {
203 Self::Raw(raw_auth) => Ok(raw_auth),
204 Self::Env(var) => interpolate(&var),
205 }
206 }
207}
208
209impl fmt::Display for RpcAuth {
210 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
211 match self {
212 Self::Raw(url) => url.fmt(f),
213 Self::Env(var) => var.fmt(f),
214 }
215 }
216}
217
218impl Serialize for RpcAuth {
219 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
220 where
221 S: Serializer,
222 {
223 serializer.serialize_str(&self.to_string())
224 }
225}
226
227impl<'de> Deserialize<'de> for RpcAuth {
228 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
229 where
230 D: Deserializer<'de>,
231 {
232 let val = String::deserialize(deserializer)?;
233 let auth = if RE_PLACEHOLDER.is_match(&val) { Self::Env(val) } else { Self::Raw(val) };
234
235 Ok(auth)
236 }
237}
238
239#[derive(Debug, Clone, Default, PartialEq, Eq)]
241pub struct RpcEndpointConfig {
242 pub retries: Option<u32>,
244
245 pub retry_backoff: Option<u64>,
247
248 pub compute_units_per_second: Option<u64>,
252}
253
254impl fmt::Display for RpcEndpointConfig {
255 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
256 let Self { retries, retry_backoff, compute_units_per_second } = self;
257
258 if let Some(retries) = retries {
259 write!(f, ", retries={retries}")?;
260 }
261
262 if let Some(retry_backoff) = retry_backoff {
263 write!(f, ", retry_backoff={retry_backoff}")?;
264 }
265
266 if let Some(compute_units_per_second) = compute_units_per_second {
267 write!(f, ", compute_units_per_second={compute_units_per_second}")?;
268 }
269
270 Ok(())
271 }
272}
273
274#[derive(Debug, Clone, PartialEq, Eq)]
276pub struct RpcEndpoint {
277 pub endpoint: RpcEndpointUrl,
279
280 pub auth: Option<RpcAuth>,
282
283 pub config: RpcEndpointConfig,
285}
286
287impl RpcEndpoint {
288 pub fn new(endpoint: RpcEndpointUrl) -> Self {
289 Self { endpoint, ..Default::default() }
290 }
291
292 pub fn resolve(self) -> ResolvedRpcEndpoint {
294 ResolvedRpcEndpoint {
295 endpoint: self.endpoint.resolve(),
296 auth: self.auth.map(|auth| auth.resolve()),
297 config: self.config,
298 }
299 }
300}
301
302impl fmt::Display for RpcEndpoint {
303 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
304 let Self { endpoint, auth, config } = self;
305 write!(f, "{endpoint}")?;
306 write!(f, "{config}")?;
307 if let Some(auth) = auth {
308 write!(f, ", auth={auth}")?;
309 }
310 Ok(())
311 }
312}
313
314impl Serialize for RpcEndpoint {
315 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
316 where
317 S: Serializer,
318 {
319 if self.config.retries.is_none()
320 && self.config.retry_backoff.is_none()
321 && self.config.compute_units_per_second.is_none()
322 && self.auth.is_none()
323 {
324 self.endpoint.serialize(serializer)
326 } else {
327 let mut map = serializer.serialize_map(Some(5))?;
328 map.serialize_entry("endpoint", &self.endpoint)?;
329 map.serialize_entry("retries", &self.config.retries)?;
330 map.serialize_entry("retry_backoff", &self.config.retry_backoff)?;
331 map.serialize_entry("compute_units_per_second", &self.config.compute_units_per_second)?;
332 map.serialize_entry("auth", &self.auth)?;
333 map.end()
334 }
335 }
336}
337
338impl<'de> Deserialize<'de> for RpcEndpoint {
339 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
340 where
341 D: Deserializer<'de>,
342 {
343 let value = serde_json::Value::deserialize(deserializer)?;
344 if value.is_string() {
345 return Ok(Self {
346 endpoint: serde_json::from_value(value).map_err(serde::de::Error::custom)?,
347 ..Default::default()
348 });
349 }
350
351 #[derive(Deserialize)]
352 struct RpcEndpointConfigInner {
353 #[serde(alias = "url")]
354 endpoint: RpcEndpointUrl,
355 retries: Option<u32>,
356 retry_backoff: Option<u64>,
357 compute_units_per_second: Option<u64>,
358 auth: Option<RpcAuth>,
359 }
360
361 let RpcEndpointConfigInner {
362 endpoint,
363 retries,
364 retry_backoff,
365 compute_units_per_second,
366 auth,
367 } = serde_json::from_value(value).map_err(serde::de::Error::custom)?;
368
369 Ok(Self {
370 endpoint,
371 auth,
372 config: RpcEndpointConfig { retries, retry_backoff, compute_units_per_second },
373 })
374 }
375}
376
377impl From<RpcEndpoint> for RpcEndpointType {
378 fn from(config: RpcEndpoint) -> Self {
379 Self::Config(config)
380 }
381}
382
383impl Default for RpcEndpoint {
384 fn default() -> Self {
385 Self {
386 endpoint: RpcEndpointUrl::Url("http://localhost:8545".to_string()),
387 config: RpcEndpointConfig::default(),
388 auth: None,
389 }
390 }
391}
392
393#[derive(Clone, Debug, PartialEq, Eq)]
395pub struct ResolvedRpcEndpoint {
396 pub endpoint: Result<String, UnresolvedEnvVarError>,
397 pub auth: Option<Result<String, UnresolvedEnvVarError>>,
398 pub config: RpcEndpointConfig,
399}
400
401impl ResolvedRpcEndpoint {
402 pub fn url(&self) -> Result<String, UnresolvedEnvVarError> {
404 self.endpoint.clone()
405 }
406
407 pub fn is_unresolved(&self) -> bool {
409 let endpoint_err = self.endpoint.is_err();
410 let auth_err = self.auth.as_ref().map(|auth| auth.is_err()).unwrap_or(false);
411 endpoint_err || auth_err
412 }
413
414 pub fn try_resolve(mut self) -> Self {
416 if !self.is_unresolved() {
417 return self;
418 }
419 if let Err(err) = self.endpoint {
420 self.endpoint = err.try_resolve()
421 }
422 if let Some(Err(err)) = self.auth {
423 self.auth = Some(err.try_resolve())
424 }
425 self
426 }
427}
428
429#[derive(Clone, Debug, Default, PartialEq, Eq)]
431pub struct ResolvedRpcEndpoints {
432 endpoints: BTreeMap<String, ResolvedRpcEndpoint>,
433}
434
435impl ResolvedRpcEndpoints {
436 pub fn has_unresolved(&self) -> bool {
438 self.endpoints.values().any(|e| e.is_unresolved())
439 }
440}
441
442impl Deref for ResolvedRpcEndpoints {
443 type Target = BTreeMap<String, ResolvedRpcEndpoint>;
444
445 fn deref(&self) -> &Self::Target {
446 &self.endpoints
447 }
448}
449
450impl DerefMut for ResolvedRpcEndpoints {
451 fn deref_mut(&mut self) -> &mut Self::Target {
452 &mut self.endpoints
453 }
454}
455
456#[cfg(test)]
457mod tests {
458 use super::*;
459
460 #[test]
461 fn serde_rpc_config() {
462 let s = r#"{
463 "endpoint": "http://localhost:8545",
464 "retries": 5,
465 "retry_backoff": 250,
466 "compute_units_per_second": 100,
467 "auth": "Bearer 123"
468 }"#;
469 let config: RpcEndpoint = serde_json::from_str(s).unwrap();
470 assert_eq!(
471 config,
472 RpcEndpoint {
473 endpoint: RpcEndpointUrl::Url("http://localhost:8545".to_string()),
474 config: RpcEndpointConfig {
475 retries: Some(5),
476 retry_backoff: Some(250),
477 compute_units_per_second: Some(100),
478 },
479 auth: Some(RpcAuth::Raw("Bearer 123".to_string())),
480 }
481 );
482
483 let s = "\"http://localhost:8545\"";
484 let config: RpcEndpoint = serde_json::from_str(s).unwrap();
485 assert_eq!(
486 config,
487 RpcEndpoint {
488 endpoint: RpcEndpointUrl::Url("http://localhost:8545".to_string()),
489 config: RpcEndpointConfig {
490 retries: None,
491 retry_backoff: None,
492 compute_units_per_second: None,
493 },
494 auth: None,
495 }
496 );
497 }
498}