init4_bin_base/utils/
provider.rs

1use 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/// Errors when connecting a provider
12#[derive(Debug, thiserror::Error, Clone, PartialEq, Eq)]
13pub enum ProviderConnectError {
14    /// Pubsub is not available for the configured transport
15    #[error("pubsub is not available for the configured transport")]
16    PubsubUnavailable,
17    /// Custom error message
18    #[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/// Configuration for an Alloy provider, sourced from an environment variable.
52#[derive(Debug, Clone, PartialEq, Eq)]
53pub struct ProviderConfig {
54    connection_string: BuiltInConnectionString,
55}
56
57impl ProviderConfig {
58    /// Creates a new `ProviderConfig` from a connection string.
59    pub const fn new(connection_string: BuiltInConnectionString) -> Self {
60        Self { connection_string }
61    }
62
63    /// Returns the connection string.
64    pub const fn connection_string(&self) -> &BuiltInConnectionString {
65        &self.connection_string
66    }
67
68    /// Connects to the provider using the connection string.
69    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/// Configuration for an Alloy provider, used to create a client, enforces
96/// pubsub availability (WS or IPC connection).
97#[derive(Debug, Clone, PartialEq, Eq)]
98pub struct PubSubConfig {
99    connection_string: BuiltInConnectionString,
100}
101
102impl PubSubConfig {
103    /// Returns the connection string.
104    pub const fn connection_string(&self) -> &BuiltInConnectionString {
105        &self.connection_string
106    }
107
108    /// Connects to the provider using the connection string.
109    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}