From 36f3259b7f0b2cccd3f1b3f40b8486cd21ece0bf Mon Sep 17 00:00:00 2001 From: jyi Date: Tue, 16 Jun 2026 12:50:22 -0700 Subject: [PATCH] feat(spanner): support Private Service Connect (PSC) endpoint override MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When ClientConfig.endpoint is set to a full HTTPS URL such as https://spanner-nonprod.p.googleapis.com (a GCP Private Service Connect endpoint), use it as both the TLS SNI hostname and the gRPC connection target URL. Previously, GRPCConnectionManager was always called with the hardcoded AUDIENCE constant (https://spanner.googleapis.com/) as the connection target, so configuring a custom endpoint only affected the TLS SNI name but not the actual dial target — PSC connections silently fell back to the public endpoint. This mirrors the behaviour of the Java SDK's SpannerOptions.setHost(): https://cloud.google.com/spanner/docs/private-service-connect Backwards-compatible: when endpoint is a plain hostname (the default), the existing AUDIENCE constant is used unchanged. --- spanner/src/apiv1/conn_pool.rs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/spanner/src/apiv1/conn_pool.rs b/spanner/src/apiv1/conn_pool.rs index 7666a777..70785f71 100644 --- a/spanner/src/apiv1/conn_pool.rs +++ b/spanner/src/apiv1/conn_pool.rs @@ -21,8 +21,23 @@ impl ConnectionManager { domain: &str, conn_options: &ConnectionOptions, ) -> Result { + // Support Private Service Connect (PSC) endpoints for Spanner. + // + // When `domain` is a full HTTPS URL (e.g. "https://spanner-nonprod.p.googleapis.com") + // we use it as both the TLS SNI hostname and the gRPC connection target, mirroring + // the Java SDK's `SpannerOptions.setHost("https://...")` behaviour. + // + // When `domain` is a plain hostname (default: "spanner.googleapis.com") the existing + // behaviour is preserved: the public AUDIENCE constant is used as the target. + let (sni_domain, audience): (String, &str) = if domain.starts_with("https://") { + let host = domain.trim_start_matches("https://").trim_end_matches('/'); + (host.to_string(), domain) + } else { + (domain.to_string(), AUDIENCE) + }; + Ok(ConnectionManager { - inner: GRPCConnectionManager::new(pool_size, domain, AUDIENCE, environment, conn_options).await?, + inner: GRPCConnectionManager::new(pool_size, sni_domain, audience, environment, conn_options).await?, }) }