partner_chains_db_sync_data_sources/
data_sources.rs1use figment::Figment;
3use figment::providers::Env;
4use serde::Deserialize;
5#[cfg(feature = "block-source")]
6use sidechain_domain::mainchain_epoch::MainchainEpochConfig;
7pub use sqlx::PgPool;
8use sqlx::postgres::{PgConnectOptions, PgPoolOptions};
9use std::error::Error;
10use std::fmt::Debug;
11use std::fmt::Formatter;
12use std::str::FromStr;
13
14#[cfg(feature = "block-source")]
18pub fn read_mc_epoch_config() -> Result<MainchainEpochConfig, Box<dyn Error + Send + Sync>> {
19 Ok(MainchainEpochConfig::read_from_env()
20 .map_err(|e| format!("Failed to read main chain config: {}", e))?)
21}
22
23#[derive(Debug, Clone, Deserialize)]
25pub struct ConnectionConfig {
26 pub(crate) db_sync_postgres_connection_string: SecretString,
28}
29
30impl ConnectionConfig {
31 pub fn from_env() -> Result<Self, Box<dyn Error + Send + Sync + 'static>> {
33 let config: Self = Figment::new()
34 .merge(Env::raw())
35 .extract()
36 .map_err(|e| format!("Failed to read postgres data source connection: {e}"))?;
37 Ok(config)
38 }
39}
40
41#[derive(Clone, serde::Deserialize, Default)]
42pub(crate) struct SecretString(pub String);
43
44impl Debug for SecretString {
45 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
46 write!(f, "***")
47 }
48}
49
50async fn get_connection(
51 connection_string: &str,
52 acquire_timeout: std::time::Duration,
53) -> Result<PgPool, Box<dyn Error + Send + Sync + 'static>> {
54 let connect_options = PgConnectOptions::from_str(connection_string)?;
55 let pool = PgPoolOptions::new()
56 .max_connections(5)
57 .acquire_timeout(acquire_timeout)
58 .connect_with(connect_options.clone())
59 .await
60 .map_err(|e| {
61 PostgresConnectionError(
62 connect_options.get_host().to_string(),
63 connect_options.get_port(),
64 connect_options.get_database().unwrap_or("cexplorer").to_string(),
65 e.to_string(),
66 )
67 .to_string()
68 })?;
69 Ok(pool)
70}
71
72#[derive(Debug, Clone, thiserror::Error)]
73#[error("Could not connect to database: postgres://***:***@{0}:{1}/{2}; error: {3}")]
74struct PostgresConnectionError(String, u16, String, String);
75
76pub async fn get_connection_from_env() -> Result<PgPool, Box<dyn Error + Send + Sync + 'static>> {
82 let config = ConnectionConfig::from_env()?;
83 get_connection(
84 config.db_sync_postgres_connection_string.0.as_str(),
85 std::time::Duration::from_secs(30),
86 )
87 .await
88}
89
90#[cfg(test)]
91mod tests {
92 use super::*;
93 use sqlx::Error::PoolTimedOut;
94
95 #[tokio::test]
96 async fn display_passwordless_connection_string_on_connection_error() {
97 let expected_connection_error = PostgresConnectionError(
98 "localhost".to_string(),
99 4432,
100 "cexplorer_test".to_string(),
101 PoolTimedOut.to_string(),
102 );
103 let test_connection_string = "postgres://postgres:randompsw@localhost:4432/cexplorer_test";
104 let actual_connection_error =
105 get_connection(test_connection_string, std::time::Duration::from_millis(1)).await;
106 assert_eq!(
107 expected_connection_error.to_string(),
108 actual_connection_error.unwrap_err().to_string()
109 );
110 }
111}