Skip to content

feat(rust): OAuth auth config, U2M and M2M providers#322

Merged
vikrantpuppala merged 7 commits intoadbc-drivers:mainfrom
vikrantpuppala:stack/pr-u2m-provider
Mar 16, 2026
Merged

feat(rust): OAuth auth config, U2M and M2M providers#322
vikrantpuppala merged 7 commits intoadbc-drivers:mainfrom
vikrantpuppala:stack/pr-u2m-provider

Conversation

@vikrantpuppala
Copy link
Collaborator

@vikrantpuppala vikrantpuppala commented Mar 8, 2026

🥞 Stacked PR

Use this link to review incremental changes.


Summary

Adds the AuthType configuration model and both OAuth authentication providers, wired into Database::new_connection():

Auth config (config.rs, database.rs)

  • AuthType enumAccessToken, OAuthM2m, OAuthU2m parsed from databricks.auth.type string option
  • AuthConfig — collects all auth options (auth_type, client_id, client_secret, scopes, token_endpoint, redirect_port) with validation
  • Database::set_option / new_connection() — flat match auth_type dispatches to the appropriate provider

OAuth providers

  • CallbackServer (callback.rs) — lightweight TCP server on localhost for OAuth redirect callbacks with CSRF state validation
  • AuthorizationCodeProvider (U2M) (u2m.rs) — authorization code + PKCE flow: OIDC discovery, browser launch, callback capture, token exchange, refresh with disk caching
  • ClientCredentialsProvider (M2M) (m2m.rs) — client credentials grant with TokenStore-managed lifecycle

Configuration

databricks.auth.type = "access_token"    # Personal access token
databricks.auth.type = "oauth_m2m"       # Client credentials (service principal)
databricks.auth.type = "oauth_u2m"       # Authorization code + PKCE (browser)

Key files

  • src/auth/config.rsAuthType enum and AuthConfig validation
  • src/auth/oauth/callback.rs — OAuth redirect callback server
  • src/auth/oauth/u2m.rs — U2M provider
  • src/auth/oauth/m2m.rs — M2M provider
  • src/database.rs — option parsing and auth provider creation

This pull request was AI-assisted by Isaac.

@vikrantpuppala vikrantpuppala force-pushed the stack/pr-u2m-provider branch from 104743c to 6006df1 Compare March 9, 2026 05:18
@vikrantpuppala vikrantpuppala changed the title Implement CallbackServer for U2M browser redirect\n\nTask ID: task-3.1-callback-server [PECOBLR-2160] feat(rust/oauth): U2M and M2M providers with callback server Mar 9, 2026
@vikrantpuppala vikrantpuppala marked this pull request as ready for review March 9, 2026 05:43
@vikrantpuppala vikrantpuppala force-pushed the stack/pr-u2m-provider branch 4 times, most recently from bba99fe to 0919bed Compare March 12, 2026 07:34
@vikrantpuppala
Copy link
Collaborator Author

Range-diff: stack/pr-database-config (bba99fe -> 0919bed)
rust/src/auth/mod.rs
@@ -3,7 +3,7 @@
 +++ b/rust/src/auth/mod.rs
  pub mod pat;
  
- pub use config::{AuthConfig, AuthFlow, AuthMechanism};
+ pub use config::{AuthConfig, AuthType};
 +pub use oauth::{AuthorizationCodeProvider, ClientCredentialsProvider};
  pub use pat::PersonalAccessToken;
  
rust/src/database.rs
@@ -3,7 +3,7 @@
 +++ b/rust/src/database.rs
  //! Database implementation for the Databricks ADBC driver.
  
- use crate::auth::config::{AuthConfig, AuthFlow, AuthMechanism};
+ use crate::auth::config::{AuthConfig, AuthType};
 -use crate::auth::PersonalAccessToken;
 +use crate::auth::{AuthProvider, AuthorizationCodeProvider, PersonalAccessToken};
  use crate::client::{
@@ -31,11 +31,16 @@
  
          // Validate auth configuration
 -        self.auth_config
-+        let mechanism = self
++        let auth_type = self
 +            .auth_config
              .validate(&self.access_token)
              .map_err(|e| e.to_adbc())?;
  
+-        // Get access_token if needed (for PAT or TokenPassthrough)
++        // Get access_token if needed (for AccessToken type)
+         let access_token = self.access_token.as_ref();
+ 
+         // Create HTTP client (without auth provider - two-phase initialization)
          let http_client =
              Arc::new(DatabricksHttpClient::new(self.http_config.clone()).map_err(|e| e.to_adbc())?);
  
@@ -58,69 +63,52 @@
                  .to_adbc()
          })?;
  
-+        // Create auth provider based on mechanism and flow
-+        let auth_provider: Arc<dyn AuthProvider> = match mechanism {
-+            AuthMechanism::Pat => {
-+                // PAT mechanism - use PersonalAccessToken provider
++        // Create auth provider based on auth type
++        let auth_provider: Arc<dyn AuthProvider> = match auth_type {
++            AuthType::AccessToken => {
 +                Arc::new(PersonalAccessToken::new(
 +                    access_token
 +                        .expect("access_token should be validated above")
 +                        .clone(),
 +                ))
 +            }
-+            AuthMechanism::OAuth => {
-+                let flow = self
++            AuthType::OAuthM2m => {
++                // Client credentials flow not yet implemented
++                return Err(DatabricksErrorHelper::invalid_state()
++                    .message("Client credentials flow (M2M) not yet implemented")
++                    .to_adbc());
++            }
++            AuthType::OAuthU2m => {
++                // U2M flow - create AuthorizationCodeProvider
++                let client_id = self
 +                    .auth_config
-+                    .flow
-+                    .expect("flow should be validated above");
-+                match flow {
-+                    AuthFlow::TokenPassthrough => {
-+                        // Token passthrough - use PersonalAccessToken provider with the provided token
-+                        Arc::new(PersonalAccessToken::new(
-+                            access_token
-+                                .expect("access_token should be validated above")
-+                                .clone(),
-+                        ))
-+                    }
-+                    AuthFlow::ClientCredentials => {
-+                        // Client credentials flow not yet implemented
-+                        return Err(DatabricksErrorHelper::invalid_state()
-+                            .message("Client credentials flow (M2M) not yet implemented")
-+                            .to_adbc());
-+                    }
-+                    AuthFlow::Browser => {
-+                        // Browser flow (U2M) - create AuthorizationCodeProvider
-+                        let client_id = self
-+                            .auth_config
-+                            .client_id
-+                            .as_deref()
-+                            .unwrap_or("databricks-cli");
-+                        let scopes_str = self
-+                            .auth_config
-+                            .scopes
-+                            .as_deref()
-+                            .unwrap_or("all-apis offline_access");
-+                        let scopes: Vec<String> =
-+                            scopes_str.split_whitespace().map(String::from).collect();
-+                        let redirect_port = self.auth_config.redirect_port.unwrap_or(8020);
-+                        let callback_timeout = Duration::from_secs(120); // 2 minutes default
++                    .client_id
++                    .as_deref()
++                    .unwrap_or("databricks-cli");
++                let scopes_str = self
++                    .auth_config
++                    .scopes
++                    .as_deref()
++                    .unwrap_or("all-apis offline_access");
++                let scopes: Vec<String> =
++                    scopes_str.split_whitespace().map(String::from).collect();
++                let redirect_port = self.auth_config.redirect_port.unwrap_or(8020);
++                let callback_timeout = Duration::from_secs(120); // 2 minutes default
 +
-+                        // Create the provider (async operation - needs runtime)
-+                        let provider = runtime
-+                            .block_on(AuthorizationCodeProvider::new_with_full_config(
-+                                host,
-+                                client_id,
-+                                http_client.clone(),
-+                                scopes,
-+                                redirect_port,
-+                                callback_timeout,
-+                                self.auth_config.token_endpoint.clone(),
-+                            ))
-+                            .map_err(|e| e.to_adbc())?;
++                // Create the provider (async operation - needs runtime)
++                let provider = runtime
++                    .block_on(AuthorizationCodeProvider::new_with_full_config(
++                        host,
++                        client_id,
++                        http_client.clone(),
++                        scopes,
++                        redirect_port,
++                        callback_timeout,
++                        self.auth_config.token_endpoint.clone(),
++                    ))
++                    .map_err(|e| e.to_adbc())?;
 +
-+                        Arc::new(provider)
-+                    }
-+                }
++                Arc::new(provider)
 +            }
 +        };
 +
@@ -156,11 +144,10 @@
          );
      }
 +
-+    // Browser flow (U2M) configuration tests
++    // U2M (OAuth) configuration tests
 +
 +    #[test]
-+    fn test_database_browser_flow_config_with_defaults() {
-+        // Test that Browser flow accepts minimal configuration and uses defaults
++    fn test_database_oauth_u2m_config_with_defaults() {
 +        let mut db = Database::new();
 +        db.set_option(
 +            OptionDatabase::Uri,
@@ -173,48 +160,23 @@
 +        )
 +        .unwrap();
 +        db.set_option(
-+            OptionDatabase::Other("databricks.auth.mechanism".into()),
-+            OptionValue::Int(11), // OAuth
-+        )
-+        .unwrap();
-+        db.set_option(
-+            OptionDatabase::Other("databricks.auth.flow".into()),
-+            OptionValue::Int(2), // Browser
++            OptionDatabase::Other("databricks.auth.type".into()),
++            OptionValue::String("oauth_u2m".into()),
 +        )
 +        .unwrap();
 +
-+        // Verify config is valid - client_id, scopes, redirect_port should use defaults
-+        // We can't actually create a connection in unit tests (requires OIDC discovery),
-+        // but we can verify the config fields are accepted
-+        assert_eq!(db.auth_config.mechanism, Some(AuthMechanism::OAuth));
-+        assert_eq!(db.auth_config.flow, Some(AuthFlow::Browser));
++        assert_eq!(db.auth_config.auth_type, Some(AuthType::OAuthU2m));
 +        assert_eq!(db.auth_config.client_id, None); // Will use default "databricks-cli"
 +        assert_eq!(db.auth_config.scopes, None); // Will use default "all-apis offline_access"
 +        assert_eq!(db.auth_config.redirect_port, None); // Will use default 8020
 +    }
 +
 +    #[test]
-+    fn test_database_browser_flow_config_with_overrides() {
-+        // Test that Browser flow accepts custom configuration
++    fn test_database_oauth_u2m_config_with_overrides() {
 +        let mut db = Database::new();
 +        db.set_option(
-+            OptionDatabase::Uri,
-+            OptionValue::String("https://example.databricks.com".into()),
-+        )
-+        .unwrap();
-+        db.set_option(
-+            OptionDatabase::Other("databricks.warehouse_id".into()),
-+            OptionValue::String("test123".into()),
-+        )
-+        .unwrap();
-+        db.set_option(
-+            OptionDatabase::Other("databricks.auth.mechanism".into()),
-+            OptionValue::Int(11), // OAuth
-+        )
-+        .unwrap();
-+        db.set_option(
-+            OptionDatabase::Other("databricks.auth.flow".into()),
-+            OptionValue::Int(2), // Browser
++            OptionDatabase::Other("databricks.auth.type".into()),
++            OptionValue::String("oauth_u2m".into()),
 +        )
 +        .unwrap();
 +        db.set_option(
@@ -238,9 +200,7 @@
 +        )
 +        .unwrap();
 +
-+        // Verify config fields are set
-+        assert_eq!(db.auth_config.mechanism, Some(AuthMechanism::OAuth));
-+        assert_eq!(db.auth_config.flow, Some(AuthFlow::Browser));
++        assert_eq!(db.auth_config.auth_type, Some(AuthType::OAuthU2m));
 +        assert_eq!(
 +            db.auth_config.client_id,
 +            Some("custom-client-id".to_string())
@@ -257,36 +217,14 @@
 +    }
 +
 +    #[test]
-+    fn test_database_browser_flow_validation_passes() {
-+        // Test that Browser flow passes validation (no required fields beyond mechanism/flow)
-+        let mut db = Database::new();
-+        db.set_option(
-+            OptionDatabase::Uri,
-+            OptionValue::String("https://example.databricks.com".into()),
-+        )
-+        .unwrap();
-+        db.set_option(
-+            OptionDatabase::Other("databricks.warehouse_id".into()),
-+            OptionValue::String("test123".into()),
-+        )
-+        .unwrap();
-+        db.set_option(
-+            OptionDatabase::Other("databricks.auth.mechanism".into()),
-+            OptionValue::Int(11), // OAuth
-+        )
-+        .unwrap();
-+        db.set_option(
-+            OptionDatabase::Other("databricks.auth.flow".into()),
-+            OptionValue::Int(2), // Browser
-+        )
-+        .unwrap();
-+
-+        // Validation should pass - Browser flow doesn't require client_id, scopes, etc.
-+        // (they all have defaults)
-+        // We can't actually create a connection without mocking OIDC discovery,
-+        // but we can verify that validation logic doesn't reject minimal config
-+        // by checking the internal state
-+        assert_eq!(db.auth_config.mechanism, Some(AuthMechanism::OAuth));
-+        assert_eq!(db.auth_config.flow, Some(AuthFlow::Browser));
++    fn test_database_oauth_u2m_validation_passes() {
++        let config = AuthConfig {
++            auth_type: Some(AuthType::OAuthU2m),
++            ..Default::default()
++        };
++        // U2M doesn't require client_id, scopes, etc. (all have defaults)
++        let result = config.validate(&None);
++        assert!(result.is_ok());
++        assert_eq!(result.unwrap(), AuthType::OAuthU2m);
 +    }
  }
\ No newline at end of file
rust/tests/integration.rs
@@ -1,19 +1,10 @@
 diff --git a/rust/tests/integration.rs b/rust/tests/integration.rs
 --- a/rust/tests/integration.rs
 +++ b/rust/tests/integration.rs
- 
- #[test]
- fn test_auth_providers() {
--    use databricks_adbc::auth::{AuthProvider, OAuthCredentials, PersonalAccessToken};
-+    use databricks_adbc::auth::{AuthProvider, PersonalAccessToken};
- 
      // Test PAT
      let pat = PersonalAccessToken::new("test-token");
      assert_eq!(pat.get_auth_header().unwrap(), "Bearer test-token");
- 
--    // Test OAuth (not yet implemented)
--    let oauth = OAuthCredentials::new("client-id", "client-secret");
--    assert!(oauth.get_auth_header().is_err());
++
 +    // OAuth providers (ClientCredentialsProvider and AuthorizationCodeProvider)
 +    // require async initialization and HTTP client, so they are tested in unit tests
  }
rust/src/auth/config.rs
@@ -1,16 +0,0 @@
-diff --git a/rust/src/auth/config.rs b/rust/src/auth/config.rs
---- a/rust/src/auth/config.rs
-+++ b/rust/src/auth/config.rs
-         match mechanism {
-             AuthMechanism::Pat => {
-                 if access_token.is_none() {
--                    return Err(DatabricksErrorHelper::invalid_argument()
--                        .message(
--                            "databricks.access_token is required when auth mechanism is 0 (PAT)",
--                        ));
-+                    return Err(DatabricksErrorHelper::invalid_argument().message(
-+                        "databricks.access_token is required when auth mechanism is 0 (PAT)",
-+                    ));
-                 }
-             }
-             AuthMechanism::OAuth => {
\ No newline at end of file

Reproduce locally: git range-diff f23bf89..bba99fe 2e70c60..0919bed | Disable: git config gitstack.push-range-diff false

@vikrantpuppala vikrantpuppala force-pushed the stack/pr-u2m-provider branch from 0919bed to c966899 Compare March 12, 2026 13:49
vikrantpuppala added a commit that referenced this pull request Mar 13, 2026
## 🥞 Stacked PR
Use this
[link](https://github.com/adbc-drivers/databricks/pull/319/files) to
review incremental changes.
-
[**stack/oauth-u2m-m2m-design**](#319)
[[Files
changed](https://github.com/adbc-drivers/databricks/pull/319/files)]
-
[stack/pr-oauth-foundation](#320)
[[Files
changed](https://github.com/adbc-drivers/databricks/pull/320/files/250ff3d91c3001f671f08084f68e949e556bc5d2..bd474c189621aa70c1f14e97c32d64605275e07d)]
-
[stack/pr-database-config](#321)
[[Files
changed](https://github.com/adbc-drivers/databricks/pull/321/files/bd474c189621aa70c1f14e97c32d64605275e07d..296931cd396d82dccb1b548a51f6b9d31be3683e)]
-
[stack/pr-u2m-provider](#322)
[[Files
changed](https://github.com/adbc-drivers/databricks/pull/322/files/296931cd396d82dccb1b548a51f6b9d31be3683e..c96689981e79c04f43e8251f2cbd5690371dfca5)]
-
[stack/pr-integration-tests](#323)
[[Files
changed](https://github.com/adbc-drivers/databricks/pull/323/files/c96689981e79c04f43e8251f2cbd5690371dfca5..83d639337ca30688abb7bdba85aa16426d76eb31)]
-
[stack/pr-final-validation](#324)
[[Files
changed](https://github.com/adbc-drivers/databricks/pull/324/files/83d639337ca30688abb7bdba85aa16426d76eb31..e2cd82bf1e9510169735774784591074f30351d3)]

---------
## Summary

- Design document for adding OAuth 2.0 authentication to the Rust ADBC
driver covering both U2M (Authorization Code + PKCE) and M2M (Client
Credentials) flows
- Sprint plan breaking the implementation into 3 tasks: foundation +
HTTP client changes, M2M provider, U2M provider
- Uses the `oauth2` crate for protocol-level operations, unified
`DatabricksHttpClient` with two-phase `OnceLock` init, and ODBC-aligned
numeric config values (`AuthMech`/`Auth_Flow`)

## Key decisions and alternatives considered

- **`oauth2` crate adoption** over hand-rolling OAuth protocol
(eliminates ~200 lines of boilerplate, handles PKCE/token
exchange/refresh)
- **Unified HTTP client** (`DatabricksHttpClient` with `OnceLock`) over
separate `reqwest::Client` for token calls (shared retry logic,
connection pooling)
- **ODBC-aligned numeric config** (`mechanism=0/11`, `flow=0/1/2`) over
string-based or auto-detection (explicit, predictable, matches ODBC
driver)
- **Separate U2M/M2M providers** over single OAuthProvider (different
flows, refresh strategies, caching needs)
- **Separate token cache** (`~/.config/databricks-adbc/oauth/`) over
sharing Python SDK cache (fragile cross-SDK compatibility)

## Areas needing specific review focus

- Two-phase HTTP client initialization pattern (OnceLock for auth
provider) — is this the right approach for breaking the circular
dependency?
- Token refresh state machine (FRESH/STALE/EXPIRED) — are the thresholds
(40s expiry buffer, min(TTL*0.5, 20min) stale) appropriate?
- Config option naming (`databricks.auth.mechanism`,
`databricks.auth.flow`) — alignment with ODBC driver
- Sprint plan task breakdown — is the scope realistic for 2 weeks?

---

*Replaces #318 (closed — converted to stacked branch)*

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
@vikrantpuppala vikrantpuppala force-pushed the stack/pr-u2m-provider branch 2 times, most recently from ec7600d to 5e41800 Compare March 13, 2026 12:21
@vikrantpuppala vikrantpuppala changed the title [PECOBLR-2160] feat(rust/oauth): U2M and M2M providers with callback server feat(rust): U2M and M2M providers with callback server Mar 13, 2026
@vikrantpuppala vikrantpuppala force-pushed the stack/pr-u2m-provider branch 3 times, most recently from 1374f0b to abc00ce Compare March 13, 2026 15:59
vikrantpuppala added a commit that referenced this pull request Mar 13, 2026
…tore (#320)

## 🥞 Stacked PR
Use this
[link](https://github.com/adbc-drivers/databricks/pull/320/files) to
review incremental changes.
-
[**stack/pr-oauth-foundation**](#320)
[[Files
changed](https://github.com/adbc-drivers/databricks/pull/320/files)]
-
[stack/pr-database-config](#321)
[[Files
changed](https://github.com/adbc-drivers/databricks/pull/321/files/78b9ec88459f895c76bd1aea99fcb47e5eb94893..164ada04d14660306c7e44dd3d52a7943050aa27)]
-
[stack/pr-u2m-provider](#322)
[[Files
changed](https://github.com/adbc-drivers/databricks/pull/322/files/164ada04d14660306c7e44dd3d52a7943050aa27..abc00ced51d89f1a652f78209f692775eba05e73)]
-
[stack/pr-integration-tests](#323)
[[Files
changed](https://github.com/adbc-drivers/databricks/pull/323/files/abc00ced51d89f1a652f78209f692775eba05e73..75b18d6c594eeba89a30450152d6d6f672239614)]
-
[stack/pr-final-validation](#324)
[[Files
changed](https://github.com/adbc-drivers/databricks/pull/324/files/75b18d6c594eeba89a30450152d6d6f672239614..2d6ccb09e121015aa6a0da6e992529a686bb0f04)]

---------
## Summary

Adds the core OAuth token infrastructure used by both U2M and M2M flows:

- **`OAuthToken`** — token struct with expiry tracking, stale detection
(40s buffer / 50% TTL), and serde support
- **OIDC discovery** — fetches `authorization_endpoint` and
`token_endpoint` from `/.well-known/oauth-authorization-server`
- **`TokenCache`** — file-based persistence at
`~/.config/databricks-adbc/oauth/` with SHA-256 hashed filenames and
`0o600` permissions
- **`TokenStore`** — thread-safe token lifecycle (Empty → Fresh → Stale
→ Expired) with coordinated refresh via `RwLock` + `AtomicBool`
- **Cargo dependencies** — `oauth2`, `sha2`, `dirs`, `serde`, `open`
crates
- **`DatabricksHttpClient`** — extended with `OnceLock`-based auth
provider and `inner()` accessor for the `oauth2` crate

### Key files
- `src/auth/oauth/token.rs` — `OAuthToken` struct
- `src/auth/oauth/oidc.rs` — OIDC endpoint discovery
- `src/auth/oauth/cache.rs` — file-based token cache
- `src/auth/oauth/token_store.rs` — token lifecycle state machine
- `src/client/http.rs` — HTTP client auth provider integration
@vikrantpuppala vikrantpuppala force-pushed the stack/pr-u2m-provider branch from abc00ce to 42daa83 Compare March 13, 2026 16:33
@vikrantpuppala vikrantpuppala force-pushed the stack/pr-u2m-provider branch from 42daa83 to af7f3c8 Compare March 13, 2026 17:07
@vikrantpuppala vikrantpuppala changed the title feat(rust): U2M and M2M providers with callback server feat(rust): OAuth auth config, U2M and M2M providers Mar 13, 2026
@vikrantpuppala vikrantpuppala force-pushed the stack/pr-u2m-provider branch from af7f3c8 to 3730127 Compare March 13, 2026 17:24
@vikrantpuppala vikrantpuppala added the integration-test Trigger integration tests in internal repo label Mar 13, 2026
@vikrantpuppala vikrantpuppala requested a review from gopalldb March 14, 2026 04:06
if let Some(v) = Self::parse_int_option(&value) {
if !(0..=65535).contains(&v) {
return Err(DatabricksErrorHelper::invalid_argument()
.message(format!(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we also log this? Or is logging implicit in the error handling?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Errors propagate to the caller via Result — the ADBC caller handles them. Adding logging here would duplicate the error path. We could add tracing::debug! for successful option sets, but that's noisy for normal operation. I'd leave it as-is.

.expect("access_token should be validated above")
.clone(),
)),
AuthType::OAuthM2m => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but you are adding these providers, will this stitching be done in separate PR?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes — M2M is fully wired up in PR #323 (the next PR in the stack). That PR adds ClientCredentialsProvider to the AuthType::OAuthM2m match arm in new_connection(), along with integration tests for both M2M and U2M.

/// Receiver for callback results.
result_rx: Option<oneshot::Receiver<CallbackResult>>,
/// Handle to the server task.
server_handle: Option<tokio::task::JoinHandle<()>>,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shall we also implement Drop, which can abort the JoinHandle, if wait_for_code is not called due to some error?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — added impl Drop for CallbackServer that aborts the server_handle if it's still running. Handles the case where an error occurs before wait_for_code is called.

@vikrantpuppala vikrantpuppala force-pushed the stack/pr-u2m-provider branch from 3730127 to b67c125 Compare March 16, 2026 06:18
@vikrantpuppala vikrantpuppala merged commit fe717ca into adbc-drivers:main Mar 16, 2026
9 of 15 checks passed
@eric-wang-1990 eric-wang-1990 added the e2e-test Trigger E2E tests on this PR label Mar 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

e2e-test Trigger E2E tests on this PR integration-test Trigger integration tests in internal repo

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants