diff --git a/Cargo.toml b/Cargo.toml index 036e2d3..40cc5dd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ name = "init4-bin-base" description = "Internal utilities for binaries produced by the init4 team" keywords = ["init4", "bin", "base"] -version = "0.9.0" +version = "0.9.1" edition = "2021" rust-version = "1.81" authors = ["init4", "James Prestwich"] diff --git a/src/lib.rs b/src/lib.rs index a2bd866..9984fd4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -34,6 +34,10 @@ pub mod utils { /// OpenTelemetry utilities. pub mod otlp; + #[cfg(feature = "alloy")] + /// Alloy Provider configuration and instantiation + pub mod provider; + #[cfg(feature = "alloy")] /// Signer using a local private key or AWS KMS key. pub mod signer; diff --git a/src/utils/provider.rs b/src/utils/provider.rs new file mode 100644 index 0000000..eafddaf --- /dev/null +++ b/src/utils/provider.rs @@ -0,0 +1,139 @@ +use crate::utils::from_env::{FromEnvErr, FromEnvVar}; +use alloy::{ + providers::{IpcConnect, RootProvider, WsConnect}, + pubsub::{ConnectionHandle, PubSubConnect}, + rpc::client::BuiltInConnectionString, + transports::{ + BoxTransport, TransportConnect, TransportError, TransportErrorKind, TransportResult, + }, +}; + +impl FromEnvVar for BuiltInConnectionString { + type Error = TransportError; + + fn from_env_var(env_var: &str) -> Result> { + let conn_str = String::from_env_var(env_var).map_err(FromEnvErr::infallible_into)?; + conn_str.parse().map_err(Into::into) + } +} + +/// Configuration for an Alloy provider, sourced from an environment variable. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ProviderConfig { + connection_string: BuiltInConnectionString, +} + +impl ProviderConfig { + /// Creates a new `ProviderConfig` from a connection string. + pub const fn new(connection_string: BuiltInConnectionString) -> Self { + Self { connection_string } + } + + /// Returns the connection string. + pub const fn connection_string(&self) -> &BuiltInConnectionString { + &self.connection_string + } + + /// Connects to the provider using the connection string. + pub async fn connect(&self) -> TransportResult { + RootProvider::connect_with(self.clone()).await + } +} + +impl FromEnvVar for ProviderConfig { + type Error = TransportError; + + fn from_env_var(env_var: &str) -> Result> { + let connection_string = BuiltInConnectionString::from_env_var(env_var)?; + Ok(Self { connection_string }) + } +} + +impl TransportConnect for ProviderConfig { + fn is_local(&self) -> bool { + self.connection_string.is_local() + } + + fn get_transport( + &self, + ) -> alloy::transports::impl_future!(>) { + self.connection_string.get_transport() + } +} + +/// Configuration for an Alloy provider, used to create a client, enforces +/// pubsub availability (WS or IPC connection). +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct PubSubConfig { + connection_string: BuiltInConnectionString, +} + +impl PubSubConfig { + /// Returns the connection string. + pub const fn connection_string(&self) -> &BuiltInConnectionString { + &self.connection_string + } + + /// Connects to the provider using the connection string. + pub async fn connect(&self) -> TransportResult { + RootProvider::connect_with(self.clone()).await + } +} + +impl TryFrom for PubSubConfig { + type Error = TransportError; + + fn try_from(connection_string: BuiltInConnectionString) -> Result { + if !matches!( + connection_string, + BuiltInConnectionString::Ws(_, _) | BuiltInConnectionString::Ipc(_) + ) { + return Err(TransportErrorKind::pubsub_unavailable()); + } + Ok(Self { connection_string }) + } +} + +impl FromEnvVar for PubSubConfig { + type Error = TransportError; + + fn from_env_var(env_var: &str) -> Result> { + let cs = BuiltInConnectionString::from_env_var(env_var)?; + Self::try_from(cs).map_err(FromEnvErr::ParseError) + } +} + +impl TransportConnect for PubSubConfig { + fn is_local(&self) -> bool { + self.connection_string.is_local() + } + + fn get_transport( + &self, + ) -> alloy::transports::impl_future!(>) { + self.connection_string.get_transport() + } +} + +impl PubSubConnect for PubSubConfig { + fn is_local(&self) -> bool { + self.connection_string.is_local() + } + + fn connect( + &self, + ) -> alloy::transports::impl_future!(>) { + async move { + match &self.connection_string { + BuiltInConnectionString::Ws(ws, auth) => { + WsConnect::new(ws.as_str()) + .with_auth_opt(auth.clone()) + .connect() + .await + } + BuiltInConnectionString::Ipc(ipc) => IpcConnect::new(ipc.clone()).connect().await, + _ => unreachable!("can't instantiate http variant"), + } + } + } +}