init4_bin_base/utils/
provider.rs1use crate::utils::from_env::{FromEnvErr, FromEnvVar};
2use alloy::{
3 providers::{IpcConnect, RootProvider, WsConnect},
4 pubsub::{ConnectionHandle, PubSubConnect},
5 rpc::client::BuiltInConnectionString,
6 transports::{
7 BoxTransport, TransportConnect, TransportError, TransportErrorKind, TransportResult,
8 },
9};
10
11#[derive(Debug, thiserror::Error, Clone, PartialEq, Eq)]
13pub enum ProviderConnectError {
14 #[error("pubsub is not available for the configured transport")]
16 PubsubUnavailable,
17 #[error("{0}")]
19 Custom(String),
20}
21
22impl From<TransportErrorKind> for ProviderConnectError {
23 fn from(err: TransportErrorKind) -> Self {
24 match err {
25 TransportErrorKind::Custom(err) => ProviderConnectError::Custom(err.to_string()),
26 TransportErrorKind::PubsubUnavailable => ProviderConnectError::PubsubUnavailable,
27 _ => panic!("Unexpected TransportErrorKind variant: {err:?}"),
28 }
29 }
30}
31
32impl From<TransportError> for ProviderConnectError {
33 fn from(err: TransportError) -> Self {
34 match err {
35 TransportError::Transport(e) => e.into(),
36 _ => panic!("Unexpected TransportError variant: {err:?}"),
37 }
38 }
39}
40
41impl FromEnvVar for BuiltInConnectionString {
42 type Error = ProviderConnectError;
43
44 fn from_env_var(env_var: &str) -> Result<Self, FromEnvErr<Self::Error>> {
45 let conn_str = String::from_env_var(env_var).map_err(FromEnvErr::infallible_into)?;
46 let built_in = conn_str.parse().map_err(ProviderConnectError::from)?;
47 Ok(built_in)
48 }
49}
50
51#[derive(Debug, Clone, PartialEq, Eq)]
53pub struct ProviderConfig {
54 connection_string: BuiltInConnectionString,
55}
56
57impl ProviderConfig {
58 pub const fn new(connection_string: BuiltInConnectionString) -> Self {
60 Self { connection_string }
61 }
62
63 pub const fn connection_string(&self) -> &BuiltInConnectionString {
65 &self.connection_string
66 }
67
68 pub async fn connect(&self) -> TransportResult<RootProvider> {
70 RootProvider::connect_with(self.clone()).await
71 }
72}
73
74impl FromEnvVar for ProviderConfig {
75 type Error = ProviderConnectError;
76
77 fn from_env_var(env_var: &str) -> Result<Self, FromEnvErr<Self::Error>> {
78 let connection_string = BuiltInConnectionString::from_env_var(env_var)?;
79 Ok(Self { connection_string })
80 }
81}
82
83impl TransportConnect for ProviderConfig {
84 fn is_local(&self) -> bool {
85 self.connection_string.is_local()
86 }
87
88 fn get_transport(
89 &self,
90 ) -> alloy::transports::impl_future!(<Output = Result<BoxTransport, TransportError>>) {
91 self.connection_string.get_transport()
92 }
93}
94
95#[derive(Debug, Clone, PartialEq, Eq)]
98pub struct PubSubConfig {
99 connection_string: BuiltInConnectionString,
100}
101
102impl PubSubConfig {
103 pub const fn connection_string(&self) -> &BuiltInConnectionString {
105 &self.connection_string
106 }
107
108 pub async fn connect(&self) -> TransportResult<RootProvider> {
110 RootProvider::connect_with(self.clone()).await
111 }
112}
113
114impl TryFrom<BuiltInConnectionString> for PubSubConfig {
115 type Error = ProviderConnectError;
116
117 fn try_from(connection_string: BuiltInConnectionString) -> Result<Self, Self::Error> {
118 if !matches!(
119 connection_string,
120 BuiltInConnectionString::Ws(_, _) | BuiltInConnectionString::Ipc(_)
121 ) {
122 return Err(ProviderConnectError::PubsubUnavailable);
123 }
124 Ok(Self { connection_string })
125 }
126}
127
128impl FromEnvVar for PubSubConfig {
129 type Error = ProviderConnectError;
130
131 fn from_env_var(env_var: &str) -> Result<Self, FromEnvErr<Self::Error>> {
132 let cs = BuiltInConnectionString::from_env_var(env_var)?;
133 Self::try_from(cs).map_err(FromEnvErr::ParseError)
134 }
135}
136
137impl TransportConnect for PubSubConfig {
138 fn is_local(&self) -> bool {
139 self.connection_string.is_local()
140 }
141
142 fn get_transport(
143 &self,
144 ) -> alloy::transports::impl_future!(<Output = Result<BoxTransport, TransportError>>) {
145 self.connection_string.get_transport()
146 }
147}
148
149impl PubSubConnect for PubSubConfig {
150 fn is_local(&self) -> bool {
151 self.connection_string.is_local()
152 }
153
154 fn connect(
155 &self,
156 ) -> alloy::transports::impl_future!(<Output = TransportResult<ConnectionHandle>>) {
157 async move {
158 match &self.connection_string {
159 BuiltInConnectionString::Ws(ws, auth) => {
160 WsConnect::new(ws.as_str())
161 .with_auth_opt(auth.clone())
162 .connect()
163 .await
164 }
165 BuiltInConnectionString::Ipc(ipc) => IpcConnect::new(ipc.clone()).connect().await,
166 _ => unreachable!("can't instantiate http variant"),
167 }
168 }
169 }
170}
171
172#[cfg(test)]
173mod test {
174 use super::*;
175 use crate::utils::from_env::FromEnv;
176
177 #[derive(FromEnv, Debug, Clone, PartialEq, Eq)]
178 #[from_env(crate)]
179 struct CompileCheck {
180 #[from_env(var = "COOL_DUDE", desc = "provider")]
181 cool_dude: ProviderConfig,
182 #[from_env(var = "COOL_DUDE2", desc = "provider2")]
183 cool_dude2: PubSubConfig,
184 }
185}