Example to replace tokio_postgres_rustls || postgres_native_tls for this library
Both tokio_postgres_rustls and postgres_native_tls are interchangeable. When I try to swap this library, however, the returned types don't match up. A more complete example would be great.
Happy to submit a PR, if I can get some guidance and if this repo is still being maintained.
For example, if I try to swap this library in, I get an issue where I would need to return a private struct.
error[E0308]: mismatched types
--> services/lynxfleetcloud-iotpass-pairing/src/db/mod.rs:69:28
|
69 | return Ok((client, connection));
| ^^^^^^^^^^ expected `Connection<Socket, MakeRustlsConnect>`, found `Connection<Socket, RustlsStream<...>>`
|
= note: expected struct `tokio_postgres::Connection<_, MakeRustlsConnect>`
found struct `tokio_postgres::Connection<_, tokio_postgres_rustls::private::RustlsStream<Socket>>`
There is a maintained fork with a comprehensive test suite, fixes for SASL/SCRAM channel binding, and unsafe code removed that would welcome any contributions: https://github.com/khorsolutions/tokio-postgres-rustls-improved
A full usage example is:
src/main.rs
use rustls::pki_types::{CertificateDer, PrivateKeyDer, pem::PemObject};
use tokio_postgres::config::{Config, SslMode};
use tokio_postgres_rustls_improved::MakeRustlsConnect;
#[tokio::main]
async fn main() {
let mut roots = rustls::RootCertStore::empty();
roots
.add(CertificateDer::from_pem_file("ca.crt").expect("load ca"))
.unwrap();
let tls_config = rustls::ClientConfig::builder()
.with_root_certificates(roots)
.with_client_auth_cert(
vec![CertificateDer::from_pem_file("client.crt").unwrap()],
PrivateKeyDer::from_pem_file("client.key").unwrap(),
)
.unwrap();
let tls = MakeRustlsConnect::new(tls_config);
let mut pg_config = Config::new();
pg_config
.host("localhost")
.port(5432)
.dbname("postgres")
.user("ssl_user")
.ssl_mode(SslMode::Require);
let (client, conn) = pg_config.connect(tls).await.expect("connect");
tokio::spawn(async move { conn.await.map_err(|e| panic!("{:?}", e)) });
let stmt = client.prepare("SELECT 1::INT4").await.expect("prepare");
let rows = client.query(&stmt, &[]).await.expect("query");
assert_eq!(1, rows.len());
let res: i32 = (&rows[0]).get(0);
assert_eq!(1, res);
println!("SELECT 1 = {}", res)
}
Cargo.toml
[package]
name = "tokio-postgres-rustls-improved-example"
version = "0.1.0"
edition = "2024"
[dependencies]
tokio-postgres = "0.7"
tokio-postgres-rustls-improved = "0.15"
rustls = { version = "0.23" }
tokio = { version = "1", features = ["full"] }
@snspinn, based on your error, I think you're trying to abstract out the actual connection. You'd want to keep that generic, like this:
async fn connect<T>(
pg_config: Config,
tls: T,
) -> Result<(Client, Connection<Socket, T::Stream>), Error>
where
T: MakeTlsConnect<Socket>,
{
pg_config.connect(tls).await
}
Full example again (compiles and runs):
use rustls::pki_types::{CertificateDer, PrivateKeyDer, pem::PemObject};
use tokio_postgres::config::{Config, SslMode};
use tokio_postgres::{Client, Connection, Error, Socket, tls::MakeTlsConnect};
use tokio_postgres_rustls_improved::MakeRustlsConnect;
async fn connect<T>(
pg_config: Config,
tls: T,
) -> Result<(Client, Connection<Socket, T::Stream>), Error>
where
T: MakeTlsConnect<Socket>,
{
pg_config.connect(tls).await
}
#[tokio::main]
async fn main() {
let mut roots = rustls::RootCertStore::empty();
roots
.add(CertificateDer::from_pem_file("ca.crt").expect("load ca"))
.unwrap();
let tls_config = rustls::ClientConfig::builder()
.with_root_certificates(roots)
.with_client_auth_cert(
vec![CertificateDer::from_pem_file("client.crt").unwrap()],
PrivateKeyDer::from_pem_file("client.key").unwrap(),
)
.unwrap();
let tls = MakeRustlsConnect::new(tls_config);
let mut pg_config = Config::new();
pg_config
.host("localhost")
.port(32799)
.dbname("postgres")
.user("ssl_user")
.ssl_mode(SslMode::Require);
let (client, conn) = connect(pg_config, tls).await.unwrap();
tokio::spawn(async move { conn.await.map_err(|e| panic!("{:?}", e)) });
let stmt = client.prepare("SELECT 1::INT4").await.expect("prepare");
let rows = client.query(&stmt, &[]).await.expect("query");
assert_eq!(1, rows.len());
let res: i32 = (&rows[0]).get(0);
assert_eq!(1, res);
println!("SELECT 1 = {}", res)
}
@snspinn, if you want to support either backend with feature flags, a crude (but working) example is:
src/main.rs
use tokio_postgres::config::{Config, SslMode};
use tokio_postgres::{Client, Connection, Error, Socket, tls::MakeTlsConnect};
#[cfg(feature = "native-tls")]
use native_tls::{Certificate, Identity, TlsConnector};
#[cfg(feature = "native-tls")]
use postgres_native_tls::MakeTlsConnector;
#[cfg(feature = "native-tls")]
use std::fs;
#[cfg(feature = "rustls")]
use rustls::pki_types::{CertificateDer, PrivateKeyDer, pem::PemObject};
#[cfg(feature = "rustls")]
use tokio_postgres_rustls_improved::MakeRustlsConnect;
#[cfg(all(feature = "native-tls", feature = "rustls"))]
compile_error!("feature \"native-tls\" and \"rustls\" cannot be enabled at the same time");
#[cfg(feature = "native-tls")]
async fn build_tls_connector() -> MakeTlsConnector {
let ca = fs::read("ca.crt").unwrap();
let ca = Certificate::from_pem(&ca).unwrap();
let cert = fs::read("client.crt").unwrap();
let key = fs::read("client.key").unwrap();
let identity = Identity::from_pkcs8(&cert, &key).unwrap();
let connector = TlsConnector::builder()
.add_root_certificate(ca)
.identity(identity)
.build()
.unwrap();
MakeTlsConnector::new(connector)
}
#[cfg(feature = "rustls")]
async fn build_tls_connector() -> MakeRustlsConnect {
let mut roots = rustls::RootCertStore::empty();
roots
.add(CertificateDer::from_pem_file("ca.crt").expect("load ca"))
.unwrap();
let tls_config = rustls::ClientConfig::builder()
.with_root_certificates(roots)
.with_client_auth_cert(
vec![CertificateDer::from_pem_file("client.crt").unwrap()],
PrivateKeyDer::from_pem_file("client.key").unwrap(),
)
.unwrap();
MakeRustlsConnect::new(tls_config)
}
async fn connect<T>(
pg_config: Config,
tls: T,
) -> Result<(Client, Connection<Socket, T::Stream>), Error>
where
T: MakeTlsConnect<Socket>,
{
pg_config.connect(tls).await
}
#[tokio::main]
async fn main() {
let mut pg_config = Config::new();
pg_config
.host("127.0.0.1")
.port(32827)
.dbname("postgres")
.user("ssl_user")
.ssl_mode(SslMode::Require);
let tls = build_tls_connector().await;
let (client, conn) = connect(pg_config, tls).await.unwrap();
tokio::spawn(async move { conn.await.map_err(|e| panic!("{:?}", e)) });
let stmt = client.prepare("SELECT 1::INT4").await.expect("prepare");
let rows = client.query(&stmt, &[]).await.expect("query");
assert_eq!(1, rows.len());
let res: i32 = (&rows[0]).get(0);
assert_eq!(1, res);
println!("SELECT 1 = {}", res)
}
Cargo.toml
[package]
name = "tokio-postgres-rustls-improved-example"
version = "0.1.0"
edition = "2024"
[features]
default = ["rustls"]
rustls = ["dep:tokio-postgres-rustls-improved", "dep:rustls"]
native-tls = ["dep:postgres-native-tls", "dep:native-tls"]
[dependencies]
tokio-postgres = "0.7"
postgres-native-tls = { version = "0.5.1", optional = true }
tokio-postgres-rustls-improved = { version = "0.15", optional = true }
rustls = { version = "0.23", optional = true }
native-tls = { version = "0.2.14", optional = true }
tokio = { version = "1", features = ["full"] }
The key is making use of traits, not binding to concrete types. The connect function isn't necessary there, it's just included to show the trait bound return type on a function like that.