v0.3.2-pre.005

This commit is contained in:
2026-08-29 19:33:43 +02:00
parent de3cec6a23
commit d93184d41d
20 changed files with 945 additions and 120 deletions

View File

@@ -1,5 +1,5 @@
# file: crates/ksp-store-postgres-lib/Cargo.toml
# version: 1
# version: 2
[package]
name = "ksp-store-postgres-lib"
@@ -8,7 +8,14 @@ edition.workspace = true
repository.workspace = true
[dependencies]
deadpool-postgres = { workspace = true, features = ["rt_tokio_1"] }
ksp-logging-lib = { path = "../ksp-logging-lib" }
ksp-store-api = { path = "../ksp-store-api" }
rustls = { workspace = true, features = ["aws_lc_rs", "std", "tls12"] }
rustls-native-certs.workspace = true
tokio = { workspace = true, features = ["rt", "time"] }
tokio-postgres = { workspace = true, features = ["runtime"] }
tokio-postgres-rustls = { workspace = true, features = ["aws-lc-rs"] }
[lints]
workspace = true

View File

@@ -0,0 +1,45 @@
// file: crates/ksp-store-postgres-lib/src/error.rs
// version: 1
/// Safe backend-local classification used by the Store facade for stable error mapping.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum PostgresBackendErrorKind {
/// The supplied physical PostgreSQL configuration is unsupported or malformed.
ConfigInvalid,
/// A physical PostgreSQL connection could not be established.
ConnectFailed,
/// A bounded pool wait, create or recycle operation reached its deadline.
PoolTimeout,
/// Explicit backend shutdown did not drain inside the supplied deadline.
ShutdownTimeout,
/// Verified TLS configuration or negotiation could not be established.
TlsFailed,
}
/// Redacted PostgreSQL backend error carrying only a safe classification and static lifecycle phase.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct PostgresBackendError {
kind: PostgresBackendErrorKind,
phase: &'static str,
}
impl PostgresBackendError {
/// Creates one backend error without retaining external error text or sensitive connection material.
#[must_use]
pub(crate) const fn new(kind: PostgresBackendErrorKind, phase: &'static str) -> Self {
return Self { kind, phase };
}
/// Returns the safe backend-local error classification.
#[must_use]
pub const fn kind(&self) -> PostgresBackendErrorKind {
return self.kind;
}
/// Returns the static safe lifecycle phase associated with the failure.
#[must_use]
pub const fn phase(&self) -> &'static str {
return self.phase;
}
}

View File

@@ -1,25 +1,36 @@
// file: crates/ksp-store-postgres-lib/src/lib.rs
// version: 2
// version: 3
#![warn(missing_docs)]
#![deny(unreachable_pub)]
#![forbid(unsafe_code)]
//! Official PostgreSQL backend implementation scaffold for KSP Store.
//! Official PostgreSQL backend implementation for KSP Store.
//!
//! `0.3.2-pre.002` establishes only the backend crate boundary. PostgreSQL
//! driver, pooling, TLS, SQL, migrations and health behavior are intentionally
//! absent until their dedicated prereleases.
//! `0.3.2-pre.005` owns the physical `tokio-postgres` connection, bounded
//! Deadpool pool and explicit Rustls TLS policy. SQL migrations and business
//! persistence remain absent until their dedicated prereleases.
//!
//! This crate depends on `ksp-store-api` and never on `ksp-store-lib`, which
//! keeps backend implementation ownership acyclic and reusable behind the
//! common facade.
//! This crate depends on `ksp-store-api` and never on `ksp-store-lib`. The
//! common facade consumes only this crate's narrow backend bridge and never
//! exposes PostgreSQL pool, client, row or statement types.
mod constants;
mod error;
mod runtime;
/// Crate-owned tracing target reserved for later PostgreSQL backend behavior.
/// Safe backend-local error returned to the common Store facade.
pub use self::error::PostgresBackendError;
/// Safe backend-local error classification used by the common Store facade.
pub use self::error::PostgresBackendErrorKind;
/// Opaque physical PostgreSQL backend owning its connection pool.
pub use self::runtime::PostgresBackend;
/// Physical PostgreSQL settings bridge consumed only by the backend crate.
pub use self::runtime::PostgresBackendSettings;
/// TLS mode accepted by the physical PostgreSQL settings bridge.
pub use self::runtime::PostgresBackendTlsMode;
/// Crate-owned tracing target for PostgreSQL backend behavior.
pub(crate) use self::constants::TRACING_TARGET;
// Keep the mandatory crate-owned tracing target part of the compiled scaffold
// without inventing runtime logging before the first behavioral tranche.
const _: &str = TRACING_TARGET;
const _: &str = crate::TRACING_TARGET;

View File

@@ -0,0 +1,303 @@
// file: crates/ksp-store-postgres-lib/src/runtime.rs
// version: 1
const APPLICATION_NAME: &str = "ksp-store";
const MAX_CONNECTION_URI_BYTES: usize = 4_096;
const SHUTDOWN_POLL_INTERVAL_MS: u64 = 10;
/// TLS mode accepted by the physical PostgreSQL backend bridge.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum PostgresBackendTlsMode {
/// Disable TLS for the selected PostgreSQL target.
Disabled,
/// Require TLS with system-root trust and server-identity verification.
VerifyFull,
}
impl PostgresBackendTlsMode {
/// Returns the stable safe TLS mode code used only in diagnostics.
#[must_use]
pub const fn code(&self) -> &'static str {
return match self {
Self::Disabled => "disabled",
Self::VerifyFull => "verify_full",
};
}
}
/// Physical settings consumed only by the PostgreSQL backend crate.
pub struct PostgresBackendSettings {
connect_timeout: std::time::Duration,
connection_uri: std::string::String,
create_timeout: std::time::Duration,
max_connections: u32,
network: ksp_store_api::RawNetworkId,
recycle_timeout: std::time::Duration,
tls_mode: PostgresBackendTlsMode,
wait_timeout: std::time::Duration,
}
impl PostgresBackendSettings {
/// Creates the physical PostgreSQL settings bridge from already validated facade-owned values.
#[must_use]
pub fn new(
network: ksp_store_api::RawNetworkId,
connection_uri: impl std::convert::Into<std::string::String>,
max_connections: u32,
connect_timeout: std::time::Duration,
wait_timeout: std::time::Duration,
create_timeout: std::time::Duration,
recycle_timeout: std::time::Duration,
tls_mode: PostgresBackendTlsMode,
) -> Self {
return Self {
connect_timeout,
connection_uri: connection_uri.into(),
create_timeout,
max_connections,
network,
recycle_timeout,
tls_mode,
wait_timeout,
};
}
/// Returns the logical network bound to this one backend instance.
#[must_use]
pub const fn network(&self) -> &ksp_store_api::RawNetworkId {
return &self.network;
}
/// Returns the selected safe TLS mode.
#[must_use]
pub const fn tls_mode(&self) -> PostgresBackendTlsMode {
return self.tls_mode;
}
}
impl std::fmt::Debug for PostgresBackendSettings {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
return formatter
.debug_struct("PostgresBackendSettings")
.field("network", &self.network)
.field("connection_uri", &"<redacted>")
.field("max_connections", &self.max_connections)
.field("connect_timeout", &self.connect_timeout)
.field("wait_timeout", &self.wait_timeout)
.field("create_timeout", &self.create_timeout)
.field("recycle_timeout", &self.recycle_timeout)
.field("tls_mode", &self.tls_mode)
.finish();
}
}
/// Opaque physical PostgreSQL backend owning the bounded Deadpool connection pool.
pub struct PostgresBackend {
network: ksp_store_api::RawNetworkId,
pool: deadpool_postgres::Pool,
}
impl PostgresBackend {
/// Parses and normalizes one supplied URI, builds a bounded pool and proves one physical connection before returning readiness.
pub async fn open(settings: PostgresBackendSettings) -> std::result::Result<Self, crate::PostgresBackendError> {
let normalized = normalized_config(&settings);
let pg_config = match normalized {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
ksp_logging_lib::debug!(
target: crate::TRACING_TARGET,
network = settings.network().as_str(),
tls_mode = settings.tls_mode().code(),
max_connections = settings.max_connections,
"opening PostgreSQL Store backend pool"
);
let pool_result = match settings.tls_mode {
PostgresBackendTlsMode::Disabled => build_pool(pg_config, tokio_postgres::NoTls, &settings),
PostgresBackendTlsMode::VerifyFull => {
let tls_result = build_verified_tls();
let tls = match tls_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
build_pool(pg_config, tls, &settings)
},
};
let pool = match pool_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let probe = pool.get().await;
match probe {
std::result::Result::Ok(client) => drop(client),
std::result::Result::Err(error) => return std::result::Result::Err(map_pool_error(error)),
}
ksp_logging_lib::debug!(
target: crate::TRACING_TARGET,
network = settings.network().as_str(),
tls_mode = settings.tls_mode().code(),
"PostgreSQL Store backend established initial physical connection"
);
return std::result::Result::Ok(Self { network: settings.network, pool });
}
/// Explicitly closes the pool and waits for all owned pooled objects to drain inside the supplied bound.
pub async fn close(self, timeout: std::time::Duration) -> std::result::Result<(), crate::PostgresBackendError> {
self.pool.close();
let drain = async {
loop {
if self.pool.status().size == 0 {
return;
}
tokio::time::sleep(std::time::Duration::from_millis(SHUTDOWN_POLL_INTERVAL_MS)).await;
}
};
let result = tokio::time::timeout(timeout, drain).await;
return match result {
std::result::Result::Ok(()) => {
ksp_logging_lib::debug!(target: crate::TRACING_TARGET, network = self.network.as_str(), "PostgreSQL Store backend pool closed");
std::result::Result::Ok(())
},
std::result::Result::Err(_) => {
std::result::Result::Err(crate::PostgresBackendError::new(crate::PostgresBackendErrorKind::ShutdownTimeout, "pool_drain"))
},
};
}
}
impl std::fmt::Debug for PostgresBackend {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
return formatter.debug_struct("PostgresBackend").field("network", &self.network).field("state", &"open").finish();
}
}
impl std::ops::Drop for PostgresBackend {
fn drop(&mut self) {
self.pool.close();
}
}
fn normalized_config(settings: &PostgresBackendSettings) -> std::result::Result<tokio_postgres::Config, crate::PostgresBackendError> {
if settings.connection_uri.is_empty() || settings.connection_uri.len() > MAX_CONNECTION_URI_BYTES {
return std::result::Result::Err(crate::PostgresBackendError::new(crate::PostgresBackendErrorKind::ConfigInvalid, "connection_uri"));
}
let parsed = settings.connection_uri.parse::<tokio_postgres::Config>();
let mut config = match parsed {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => {
return std::result::Result::Err(crate::PostgresBackendError::new(crate::PostgresBackendErrorKind::ConfigInvalid, "connection_uri"));
},
};
if config.get_options().is_some() {
return std::result::Result::Err(crate::PostgresBackendError::new(crate::PostgresBackendErrorKind::ConfigInvalid, "server_options"));
}
if config.get_hosts().is_empty() && config.get_hostaddrs().is_empty() {
return std::result::Result::Err(crate::PostgresBackendError::new(crate::PostgresBackendErrorKind::ConfigInvalid, "host"));
}
if settings.tls_mode == PostgresBackendTlsMode::VerifyFull {
if config.get_hosts().is_empty() {
return std::result::Result::Err(crate::PostgresBackendError::new(crate::PostgresBackendErrorKind::ConfigInvalid, "tls_server_identity"));
}
for host in config.get_hosts() {
if !is_tcp_host(host) {
return std::result::Result::Err(crate::PostgresBackendError::new(crate::PostgresBackendErrorKind::ConfigInvalid, "tls_server_identity"));
}
}
}
config.application_name(APPLICATION_NAME);
config.connect_timeout(settings.connect_timeout);
config.ssl_negotiation(tokio_postgres::config::SslNegotiation::Postgres);
match settings.tls_mode {
PostgresBackendTlsMode::Disabled => {
config.ssl_mode(tokio_postgres::config::SslMode::Disable);
},
PostgresBackendTlsMode::VerifyFull => {
config.ssl_mode(tokio_postgres::config::SslMode::Require);
},
}
return std::result::Result::Ok(config);
}
fn is_tcp_host(host: &tokio_postgres::config::Host) -> bool {
return match host {
tokio_postgres::config::Host::Tcp(_) => true,
#[cfg(unix)]
tokio_postgres::config::Host::Unix(_) => false,
};
}
fn build_pool<T>(
pg_config: tokio_postgres::Config,
tls: T,
settings: &PostgresBackendSettings,
) -> std::result::Result<deadpool_postgres::Pool, crate::PostgresBackendError>
where
T: tokio_postgres::tls::MakeTlsConnect<tokio_postgres::Socket> + std::clone::Clone + std::marker::Send + std::marker::Sync + 'static,
T::Stream: std::marker::Send + std::marker::Sync,
T::TlsConnect: std::marker::Send + std::marker::Sync,
<T::TlsConnect as tokio_postgres::tls::TlsConnect<tokio_postgres::Socket>>::Future: std::marker::Send,
{
let manager_config = deadpool_postgres::ManagerConfig { recycling_method: deadpool_postgres::RecyclingMethod::Verified };
let manager = deadpool_postgres::Manager::from_config(pg_config, tls, manager_config);
let built = deadpool_postgres::Pool::builder(manager)
.max_size(settings.max_connections as usize)
.wait_timeout(std::option::Option::Some(settings.wait_timeout))
.create_timeout(std::option::Option::Some(settings.create_timeout))
.recycle_timeout(std::option::Option::Some(settings.recycle_timeout))
.runtime(deadpool_postgres::Runtime::Tokio1)
.build();
return match built {
std::result::Result::Ok(pool) => std::result::Result::Ok(pool),
std::result::Result::Err(_) => std::result::Result::Err(crate::PostgresBackendError::new(crate::PostgresBackendErrorKind::ConfigInvalid, "pool_build")),
};
}
fn build_verified_tls() -> std::result::Result<tokio_postgres_rustls::MakeRustlsConnect, crate::PostgresBackendError> {
let native = rustls_native_certs::load_native_certs();
let native_error_count = native.errors.len();
if native.certs.is_empty() {
ksp_logging_lib::warn!(target: crate::TRACING_TARGET, native_error_count, "no system TLS roots available for PostgreSQL verify_full");
return std::result::Result::Err(crate::PostgresBackendError::new(crate::PostgresBackendErrorKind::TlsFailed, "native_roots"));
}
let mut roots = rustls::RootCertStore::empty();
let (added, ignored) = roots.add_parsable_certificates(native.certs);
if added == 0 {
ksp_logging_lib::warn!(target: crate::TRACING_TARGET, native_error_count, ignored, "system TLS roots could not be admitted for PostgreSQL verify_full");
return std::result::Result::Err(crate::PostgresBackendError::new(crate::PostgresBackendErrorKind::TlsFailed, "native_roots"));
}
if native_error_count > 0 || ignored > 0 {
ksp_logging_lib::debug!(target: crate::TRACING_TARGET, added, ignored, native_error_count, "loaded PostgreSQL system TLS roots with partial diagnostics");
}
let provider = std::sync::Arc::new(rustls::crypto::aws_lc_rs::default_provider());
let builder_result = rustls::ClientConfig::builder_with_provider(provider).with_safe_default_protocol_versions();
let builder = match builder_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => {
return std::result::Result::Err(crate::PostgresBackendError::new(crate::PostgresBackendErrorKind::TlsFailed, "protocol_versions"));
},
};
let client_config = builder.with_root_certificates(roots).with_no_client_auth();
return std::result::Result::Ok(tokio_postgres_rustls::MakeRustlsConnect::new(client_config));
}
fn map_pool_error(error: deadpool_postgres::PoolError) -> crate::PostgresBackendError {
return match error {
deadpool_postgres::PoolError::Timeout(timeout_type) => crate::PostgresBackendError::new(
crate::PostgresBackendErrorKind::PoolTimeout,
match timeout_type {
deadpool_postgres::TimeoutType::Wait => "pool_wait",
deadpool_postgres::TimeoutType::Create => "pool_create",
deadpool_postgres::TimeoutType::Recycle => "pool_recycle",
},
),
deadpool_postgres::PoolError::Backend(_) => crate::PostgresBackendError::new(crate::PostgresBackendErrorKind::ConnectFailed, "physical_connect"),
deadpool_postgres::PoolError::Closed => crate::PostgresBackendError::new(crate::PostgresBackendErrorKind::ConnectFailed, "pool_closed"),
deadpool_postgres::PoolError::NoRuntimeSpecified => crate::PostgresBackendError::new(crate::PostgresBackendErrorKind::ConfigInvalid, "pool_runtime"),
deadpool_postgres::PoolError::PostCreateHook(_) => crate::PostgresBackendError::new(crate::PostgresBackendErrorKind::ConnectFailed, "pool_post_create"),
};
}
#[cfg(test)]
#[path = "../unit_tests/runtime.rs"]
mod tests;

View File

@@ -1,36 +1,60 @@
// file: crates/ksp-store-postgres-lib/tests/dependency_boundary.rs
// version: 2
// version: 3
#![warn(missing_docs)]
#![deny(unreachable_pub)]
#![forbid(unsafe_code)]
//! Dependency-boundary canaries for the PostgreSQL Store backend scaffold.
//! Dependency and ownership canaries for the physical PostgreSQL Store backend.
#[test]
fn pre_002_backend_depends_on_store_api_without_reverse_facade_edge() {
fn pre_005_backend_owns_exact_physical_runtime_dependencies_without_reverse_facade_edge() {
let manifest = include_str!("../Cargo.toml");
assert!(manifest.contains("ksp-store-api = { path = \"../ksp-store-api\" }"));
for forbidden in ["ksp-store-lib", "ksp-config-lib", "ksp-materializer", "ksp-program", "ksp-onchain-transport-lib", "ksp-offchain-transport-lib"] {
for required in ["deadpool-postgres", "ksp-logging-lib", "ksp-store-api", "rustls", "rustls-native-certs", "tokio-postgres", "tokio-postgres-rustls"] {
assert!(manifest.contains(required), "missing PostgreSQL backend dependency: {required}");
}
for forbidden in ["ksp-store-lib", "ksp-config-lib", "ksp-materializer", "ksp-program", "ksp-onchain-transport-lib", "ksp-offchain-transport-lib", "sqlx"] {
assert!(!manifest.contains(forbidden), "forbidden PostgreSQL backend dependency detected: {forbidden}");
}
return;
}
#[test]
fn pre_002_backend_does_not_advance_connection_pool_tls_or_migrations() {
let manifest = include_str!("../Cargo.toml");
for forbidden in ["tokio", "tokio-postgres", "deadpool-postgres", "tokio-postgres-rustls", "rustls", "sha2"] {
assert!(!manifest.contains(forbidden), "premature PostgreSQL runtime dependency detected: {forbidden}");
}
fn pre_005_backend_keeps_environment_sql_migrations_and_physical_types_private() {
let crate_root = include_str!("../src/lib.rs");
assert!(crate_root.contains("mod constants;"));
assert!(crate_root.contains("pub(crate) use self::constants::TRACING_TARGET;"));
assert!(crate_root.contains("const _: &str = TRACING_TARGET;"));
for forbidden in ["pub mod ", "pub struct", "pub enum", "pub trait", "tokio_postgres", "deadpool_postgres", "mod migration", "SELECT ", "CREATE TABLE"] {
assert!(!crate_root.contains(forbidden), "premature PostgreSQL backend surface detected: {forbidden}");
assert!(crate_root.contains("mod error;"));
assert!(crate_root.contains("mod runtime;"));
assert!(crate_root.contains("const _: &str = crate::TRACING_TARGET;"));
for forbidden in [
"pub mod ",
"ksp_store_lib",
"ksp_config_lib",
"tokio_postgres::Client",
"tokio_postgres::Row",
"tokio_postgres::Statement",
"deadpool_postgres::Pool;",
] {
assert!(!crate_root.contains(forbidden), "forbidden PostgreSQL crate-root surface detected: {forbidden}");
}
let runtime = include_str!("../src/runtime.rs");
for forbidden in [
"std::env",
"dotenv",
"KSP_",
"KSPB_",
"PGHOST",
"PGPORT",
"PGUSER",
"PGPASSWORD",
".pgpass",
"CREATE TABLE",
"INSERT INTO",
"UPDATE ",
"DELETE FROM",
"SELECT ",
"mod migration",
] {
assert!(!runtime.contains(forbidden), "forbidden PostgreSQL backend ownership/scope content detected: {forbidden}");
}
let constants = include_str!("../src/constants.rs");
assert!(constants.contains("pub(crate) const TRACING_TARGET: &str = \"ksp-store-postgres-lib\";"));
return;
}

View File

@@ -0,0 +1,44 @@
// file: crates/ksp-store-postgres-lib/tests/public_api.rs
// version: 1
#![warn(missing_docs)]
#![deny(unreachable_pub)]
#![forbid(unsafe_code)]
//! Narrow physical bridge canaries consumed by `ksp-store-lib` without exposing driver or pool types.
#[test]
fn pre_005_backend_bridge_is_constructible_without_io() {
let network = match ksp_store_api::RawNetworkId::new("devnet") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("valid backend bridge network rejected: {error:?}"),
};
let settings = ksp_store_postgres_lib::PostgresBackendSettings::new(
network,
"postgresql://operator:secret@localhost/ksp",
8,
std::time::Duration::from_secs(10),
std::time::Duration::from_secs(5),
std::time::Duration::from_secs(10),
std::time::Duration::from_secs(5),
ksp_store_postgres_lib::PostgresBackendTlsMode::VerifyFull,
);
assert_eq!(settings.network().as_str(), "devnet");
assert_eq!(settings.tls_mode().code(), "verify_full");
let _open = ksp_store_postgres_lib::PostgresBackend::open;
let _close = ksp_store_postgres_lib::PostgresBackend::close;
return;
}
#[test]
fn pre_005_backend_error_projection_is_safe_and_static() {
let kinds = [
ksp_store_postgres_lib::PostgresBackendErrorKind::ConfigInvalid,
ksp_store_postgres_lib::PostgresBackendErrorKind::ConnectFailed,
ksp_store_postgres_lib::PostgresBackendErrorKind::PoolTimeout,
ksp_store_postgres_lib::PostgresBackendErrorKind::ShutdownTimeout,
ksp_store_postgres_lib::PostgresBackendErrorKind::TlsFailed,
];
assert_eq!(kinds.len(), 5);
return;
}

View File

@@ -0,0 +1,99 @@
// file: crates/ksp-store-postgres-lib/unit_tests/runtime.rs
// version: 1
fn network() -> ksp_store_api::RawNetworkId {
return match ksp_store_api::RawNetworkId::new("devnet") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("valid backend test network rejected: {error:?}"),
};
}
fn settings(connection_uri: &str, tls_mode: crate::PostgresBackendTlsMode) -> crate::PostgresBackendSettings {
return crate::PostgresBackendSettings::new(
network(),
connection_uri,
8,
std::time::Duration::from_secs(10),
std::time::Duration::from_secs(5),
std::time::Duration::from_secs(10),
std::time::Duration::from_secs(5),
tls_mode,
);
}
#[test]
fn physical_settings_debug_redacts_connection_uri() {
let secret = "postgresql://secret-user:secret-password@localhost/ksp";
let value = settings(secret, crate::PostgresBackendTlsMode::VerifyFull);
let rendered = format!("{value:?}");
assert!(!rendered.contains("secret-user"));
assert!(!rendered.contains("secret-password"));
assert!(rendered.contains("<redacted>"));
return;
}
#[test]
fn malformed_or_oversized_uri_is_rejected_without_retaining_input() {
let malformed = "not-a-postgresql-uri-secret-canary";
let malformed_result = super::normalized_config(&settings(malformed, crate::PostgresBackendTlsMode::Disabled));
let malformed_error = malformed_result.err();
assert_eq!(malformed_error.as_ref().map(|value| return value.kind()), std::option::Option::Some(crate::PostgresBackendErrorKind::ConfigInvalid));
assert!(!format!("{malformed_error:?}").contains("secret-canary"));
let oversized = "x".repeat(super::MAX_CONNECTION_URI_BYTES + 1);
let oversized_result = super::normalized_config(&settings(oversized.as_str(), crate::PostgresBackendTlsMode::Disabled));
assert_eq!(oversized_result.err().map(|value| return value.kind()), std::option::Option::Some(crate::PostgresBackendErrorKind::ConfigInvalid));
return;
}
#[test]
fn typed_disabled_policy_overrides_uri_tls_and_connection_controls() {
let value = settings(
"postgresql://operator:secret@localhost/ksp?sslmode=require&application_name=hostile&connect_timeout=1&sslnegotiation=direct",
crate::PostgresBackendTlsMode::Disabled,
);
let config = match super::normalized_config(&value) {
std::result::Result::Ok(config) => config,
std::result::Result::Err(error) => panic!("valid disabled config rejected: {error:?}"),
};
assert_eq!(config.get_ssl_mode(), tokio_postgres::config::SslMode::Disable);
assert_eq!(config.get_ssl_negotiation(), tokio_postgres::config::SslNegotiation::Postgres);
assert_eq!(config.get_application_name(), std::option::Option::Some(super::APPLICATION_NAME));
assert_eq!(config.get_connect_timeout(), std::option::Option::Some(std::time::Duration::from_secs(10)));
return;
}
#[test]
fn typed_verify_full_policy_forces_tls_and_rejects_hostaddr_only_identity() {
let value = settings(
"postgresql://operator:secret@localhost/ksp?sslmode=disable&application_name=hostile&connect_timeout=1",
crate::PostgresBackendTlsMode::VerifyFull,
);
let config = match super::normalized_config(&value) {
std::result::Result::Ok(config) => config,
std::result::Result::Err(error) => panic!("valid verify_full config rejected: {error:?}"),
};
assert_eq!(config.get_ssl_mode(), tokio_postgres::config::SslMode::Require);
assert_eq!(config.get_ssl_negotiation(), tokio_postgres::config::SslNegotiation::Postgres);
assert_eq!(config.get_application_name(), std::option::Option::Some(super::APPLICATION_NAME));
assert_eq!(config.get_connect_timeout(), std::option::Option::Some(std::time::Duration::from_secs(10)));
let hostaddr_only = settings("hostaddr=127.0.0.1 user=operator dbname=ksp", crate::PostgresBackendTlsMode::VerifyFull);
let rejected = super::normalized_config(&hostaddr_only);
assert_eq!(rejected.err().map(|value| return value.kind()), std::option::Option::Some(crate::PostgresBackendErrorKind::ConfigInvalid));
#[cfg(unix)]
{
let unix_socket = settings("host=/var/run/postgresql user=operator dbname=ksp", crate::PostgresBackendTlsMode::VerifyFull);
let unix_rejected = super::normalized_config(&unix_socket);
assert_eq!(unix_rejected.err().map(|value| return value.kind()), std::option::Option::Some(crate::PostgresBackendErrorKind::ConfigInvalid));
}
return;
}
#[test]
fn libpq_server_options_are_rejected_in_foundation_runtime() {
let value = settings("host=localhost user=operator dbname=ksp options='-c statement_timeout=0'", crate::PostgresBackendTlsMode::Disabled);
let rejected = super::normalized_config(&value);
let error = rejected.err();
assert_eq!(error.as_ref().map(|value| return value.kind()), std::option::Option::Some(crate::PostgresBackendErrorKind::ConfigInvalid));
assert_eq!(error.map(|value| return value.phase()), std::option::Option::Some("server_options"));
return;
}