wasmcloud_provider_sqldb_postgres/
config.rs

1use tracing::warn;
2use wasmcloud_provider_sdk::{core::secrets::SecretValue, LinkConfig};
3
4const POSTGRES_DEFAULT_PORT: u16 = 5432;
5
6/// Creation options for a Postgres connection
7#[derive(Debug, Clone, PartialEq, Eq)]
8pub(crate) struct ConnectionCreateOptions {
9    /// Hostname of the Postgres cluster to connect to
10    pub host: String,
11    /// Port on which to connect to the Postgres cluster
12    pub port: u16,
13    /// Username used when accessing the Postgres cluster
14    pub username: String,
15    /// Password used when accessing the Postgres cluster
16    pub password: String,
17    /// Database to connect to
18    pub database: String,
19    /// Whether TLS is required for the connection
20    pub tls_required: bool,
21    /// Optional connection pool size
22    pub pool_size: Option<usize>,
23}
24
25impl From<ConnectionCreateOptions> for deadpool_postgres::Config {
26    fn from(opts: ConnectionCreateOptions) -> Self {
27        let mut cfg = deadpool_postgres::Config::new();
28        cfg.host = Some(opts.host);
29        cfg.user = Some(opts.username);
30        cfg.password = Some(opts.password);
31        cfg.dbname = Some(opts.database);
32        cfg.port = Some(opts.port);
33        if let Some(pool_size) = opts.pool_size {
34            cfg.pool = Some(deadpool_postgres::PoolConfig {
35                max_size: pool_size,
36                ..deadpool_postgres::PoolConfig::default()
37            });
38        }
39        cfg
40    }
41}
42
43/// Parse the options for Postgres configuration from a [`HashMap`], with a given prefix to the keys
44///
45/// For example given a prefix like `EXAMPLE_`, and a Hashmap that contains an entry like ("EXAMPLE_HOST", "localhost"),
46/// the parsed [`ConnectionCreateOptions`] would contain "localhost" as the host.
47pub(crate) fn extract_prefixed_conn_config(
48    prefix: &str,
49    link_config: &LinkConfig,
50) -> Option<ConnectionCreateOptions> {
51    let LinkConfig {
52        config, secrets, ..
53    } = link_config;
54
55    let keys = [
56        format!("{prefix}HOST"),
57        format!("{prefix}PORT"),
58        format!("{prefix}USERNAME"),
59        format!("{prefix}PASSWORD"),
60        format!("{prefix}DATABASE"),
61        format!("{prefix}TLS_REQUIRED"),
62        format!("{prefix}POOL_SIZE"),
63    ];
64    match keys
65        .iter()
66        .map(|k| {
67            // Prefer fetching from secrets, but fall back to config if not found
68            match (secrets.get(k).and_then(SecretValue::as_string), config.get(k)) {
69                (Some(s), Some(_)) => {
70                    warn!("secret value [{k}] was found in secrets, but also exists in config. The value in secrets will be used.");
71                    Some(s)
72                }
73                (Some(s), _) => Some(s),
74                // Offer a warning for the password, but other values are fine to be in config
75                (None, Some(c)) if k == &format!("{prefix}PASSWORD") => {
76                    warn!("secret value [{k}] was not found in secrets, but exists in config. Prefer using secrets for sensitive values.");
77                    Some(c.as_str())
78                }
79                (None, Some(c)) => {
80                    Some(c.as_str())
81                }
82                (_, None) => None,
83            }
84        })
85        .collect::<Vec<Option<&str>>>()[..]
86    {
87        [Some(host), Some(port), Some(username), Some(password), Some(database), tls_required, pool_size] =>
88        {
89            let pool_size = pool_size.and_then(|pool_size| {
90                pool_size.parse::<usize>().ok().or_else(|| {
91                    warn!("invalid pool size value [{pool_size}], using default");
92                    None
93                })
94            });
95
96            Some(ConnectionCreateOptions {
97                host: host.to_string(),
98                port: port.parse::<u16>().unwrap_or_else(|_e| {
99                    warn!("invalid port value [{port}], using {POSTGRES_DEFAULT_PORT}");
100                    POSTGRES_DEFAULT_PORT
101                }),
102                username: username.to_string(),
103                password: password.to_string(),
104                tls_required: tls_required.is_some_and(|tls_required| {
105                    matches!(tls_required.to_lowercase().as_str(), "true" | "yes")
106                }),
107                database: database.to_string(),
108                pool_size,
109            })
110        }
111        _ => {
112            warn!("failed to find required keys in configuration: [{:?}]", keys);
113            None
114        }
115    }
116}