1use std::str::FromStr;
2use std::sync::Arc;
3
4use futures::future::BoxFuture;
5use serde::{Deserialize, Serialize};
6use tokio::runtime::Handle;
7
8use super::cfg_file::load_cfg_profile;
9use super::credential::{
10 DatabricksCredential, DatabricksGcpTokenExchangeProvider, DatabricksM2MProvider,
11 OidcEnvTokenProvider, OidcFileTokenProvider,
12};
13use crate::azure::credential::{
14 AZURE_DATABRICKS_RESOURCE, AZURE_DATABRICKS_SCOPE, AzureCliCredential,
15 ClientSecretOAuthProvider, ImdsManagedIdentityProvider,
16};
17use crate::service::make_service;
18use crate::{
19 ClientConfigKey, ClientOptions, CredentialProvider, RequestSigner, Result, RetryConfig,
20 StaticCredentialProvider, TokenCredentialProvider,
21};
22
23const DEFAULT_DATABRICKS_SCOPE: &str = "unity-catalog";
25
26const DEFAULT_OIDC_TOKEN_ENV: &str = "DATABRICKS_OIDC_TOKEN";
28
29type DatabricksCredentialProvider = Arc<dyn CredentialProvider<Credential = DatabricksCredential>>;
30
31#[derive(Debug, Clone)]
33pub struct DatabricksConfig {
34 pub credentials: DatabricksCredentialProvider,
35 pub retry_config: RetryConfig,
36 pub client_options: ClientOptions,
37}
38
39impl RequestSigner for DatabricksConfig {
40 fn sign<'a>(
41 &'a self,
42 req: reqwest::RequestBuilder,
43 ) -> BoxFuture<'a, Result<reqwest::RequestBuilder>> {
44 Box::pin(async move {
45 let cred = self.credentials.get_credential().await?;
46 Ok(req.bearer_auth(&cred.bearer))
47 })
48 }
49}
50
51#[derive(PartialEq, Eq, Hash, Clone, Debug, Copy, Deserialize, Serialize)]
53#[non_exhaustive]
54pub enum DatabricksConfigKey {
55 Host,
59
60 Token,
64
65 ClientId,
69
70 ClientSecret,
74
75 AccountId,
79
80 AzureResourceId,
84
85 AzureTenantId,
94
95 AzureAuthorityHost,
100
101 Scope,
105
106 ConfigFile,
110
111 ConfigProfile,
115
116 AuthType,
121
122 OidcTokenEnv,
127
128 OidcTokenFilepath,
132
133 Client(ClientConfigKey),
135}
136
137impl AsRef<str> for DatabricksConfigKey {
138 fn as_ref(&self) -> &str {
139 match self {
140 Self::Host => "databricks_host",
141 Self::Token => "databricks_token",
142 Self::ClientId => "databricks_client_id",
143 Self::ClientSecret => "databricks_client_secret",
144 Self::AccountId => "databricks_account_id",
145 Self::AzureResourceId => "databricks_azure_resource_id",
146 Self::AzureTenantId => "databricks_azure_tenant_id",
147 Self::AzureAuthorityHost => "databricks_azure_authority_host",
148 Self::Scope => "databricks_oauth_scope",
149 Self::ConfigFile => "databricks_config_file",
150 Self::ConfigProfile => "databricks_config_profile",
151 Self::AuthType => "databricks_auth_type",
152 Self::OidcTokenEnv => "databricks_oidc_token_env",
153 Self::OidcTokenFilepath => "databricks_oidc_token_filepath",
154 Self::Client(key) => key.as_ref(),
155 }
156 }
157}
158
159impl FromStr for DatabricksConfigKey {
160 type Err = crate::Error;
161
162 fn from_str(s: &str) -> Result<Self, Self::Err> {
163 match s {
164 "databricks_host" | "host" => Ok(Self::Host),
165 "databricks_token" | "token" => Ok(Self::Token),
166 "databricks_client_id" | "client_id" => Ok(Self::ClientId),
167 "databricks_client_secret" | "client_secret" => Ok(Self::ClientSecret),
168 "databricks_account_id" | "account_id" => Ok(Self::AccountId),
169 "databricks_azure_resource_id" | "azure_resource_id" => Ok(Self::AzureResourceId),
170 "databricks_azure_tenant_id" | "azure_tenant_id" => Ok(Self::AzureTenantId),
171 "databricks_azure_authority_host" | "azure_authority_host" => {
172 Ok(Self::AzureAuthorityHost)
173 }
174 "databricks_oauth_scope" | "oauth_scope" => Ok(Self::Scope),
175 "databricks_config_file" | "config_file" => Ok(Self::ConfigFile),
176 "databricks_config_profile" | "config_profile" | "profile" => Ok(Self::ConfigProfile),
177 "databricks_auth_type" | "auth_type" => Ok(Self::AuthType),
178 "databricks_oidc_token_env" | "oidc_token_env" => Ok(Self::OidcTokenEnv),
179 "databricks_oidc_token_filepath" | "oidc_token_filepath" => Ok(Self::OidcTokenFilepath),
180 _ => match s.strip_prefix("databricks_").unwrap_or(s).parse() {
181 Ok(key) => Ok(Self::Client(key)),
182 Err(_) => Err(crate::Error::UnknownConfigurationKey { key: s.into() }),
183 },
184 }
185 }
186}
187
188#[derive(Default, Clone)]
208pub struct DatabricksBuilder {
209 host: Option<String>,
210 token: Option<String>,
211 client_id: Option<String>,
212 client_secret: Option<String>,
213 account_id: Option<String>,
214 azure_resource_id: Option<String>,
215 azure_tenant_id: Option<String>,
216 azure_authority_host: Option<String>,
217 scope: Option<String>,
218 config_file: Option<String>,
219 config_profile: Option<String>,
220 auth_type: Option<String>,
221 oidc_token_env: Option<String>,
222 oidc_token_filepath: Option<String>,
223 retry_config: RetryConfig,
224 client_options: ClientOptions,
225 credentials: Option<DatabricksCredentialProvider>,
226}
227
228impl std::fmt::Debug for DatabricksBuilder {
229 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
230 write!(f, "DatabricksBuilder {{ host: {:?} }}", self.host)
231 }
232}
233
234impl DatabricksBuilder {
235 pub fn new() -> Self {
237 Default::default()
238 }
239
240 pub fn from_env() -> Self {
251 let mut builder = Self::default();
252
253 let mut env_map: Vec<(DatabricksConfigKey, String)> = Vec::new();
256 let mut config_file_env: Option<String> = None;
257 let mut config_profile_env: Option<String> = None;
258
259 for (os_key, os_value) in std::env::vars_os() {
260 if let (Some(key), Some(value)) = (os_key.to_str(), os_value.to_str())
261 && (key.starts_with("DATABRICKS_") || key == "GOOGLE_APPLICATION_CREDENTIALS")
262 && let Ok(config_key) = key.to_ascii_lowercase().parse::<DatabricksConfigKey>()
263 {
264 match config_key {
265 DatabricksConfigKey::ConfigFile => config_file_env = Some(value.to_owned()),
266 DatabricksConfigKey::ConfigProfile => {
267 config_profile_env = Some(value.to_owned())
268 }
269 _ => env_map.push((config_key, value.to_owned())),
270 }
271 }
272 }
273
274 if let Ok(profile_values) =
276 load_cfg_profile(config_file_env.as_deref(), config_profile_env.as_deref())
277 {
278 for (k, v) in profile_values {
279 builder = builder.with_config(k, v);
280 }
281 }
282
283 for (key, value) in env_map {
285 builder = builder.with_config(key, value);
286 }
287
288 if let Some(v) = config_file_env {
290 builder.config_file = Some(v);
291 }
292 if let Some(v) = config_profile_env {
293 builder.config_profile = Some(v);
294 }
295
296 builder
297 }
298
299 pub fn with_config(mut self, key: DatabricksConfigKey, value: impl Into<String>) -> Self {
301 match key {
302 DatabricksConfigKey::Host => self.host = Some(value.into()),
303 DatabricksConfigKey::Token => self.token = Some(value.into()),
304 DatabricksConfigKey::ClientId => self.client_id = Some(value.into()),
305 DatabricksConfigKey::ClientSecret => self.client_secret = Some(value.into()),
306 DatabricksConfigKey::AccountId => self.account_id = Some(value.into()),
307 DatabricksConfigKey::AzureResourceId => self.azure_resource_id = Some(value.into()),
308 DatabricksConfigKey::AzureTenantId => self.azure_tenant_id = Some(value.into()),
309 DatabricksConfigKey::AzureAuthorityHost => {
310 self.azure_authority_host = Some(value.into())
311 }
312 DatabricksConfigKey::Scope => self.scope = Some(value.into()),
313 DatabricksConfigKey::ConfigFile => self.config_file = Some(value.into()),
314 DatabricksConfigKey::ConfigProfile => self.config_profile = Some(value.into()),
315 DatabricksConfigKey::AuthType => self.auth_type = Some(value.into()),
316 DatabricksConfigKey::OidcTokenEnv => self.oidc_token_env = Some(value.into()),
317 DatabricksConfigKey::OidcTokenFilepath => self.oidc_token_filepath = Some(value.into()),
318 DatabricksConfigKey::Client(key) => {
319 self.client_options = self.client_options.with_config(key, value)
320 }
321 }
322 self
323 }
324
325 pub fn with_credentials(mut self, credentials: DatabricksCredentialProvider) -> Self {
327 self.credentials = Some(credentials);
328 self
329 }
330
331 pub fn with_host(mut self, host: impl Into<String>) -> Self {
333 self.host = Some(host.into());
334 self
335 }
336
337 pub fn with_token(mut self, token: impl Into<String>) -> Self {
339 self.token = Some(token.into());
340 self
341 }
342
343 pub fn with_auth_type(mut self, auth_type: impl Into<String>) -> Self {
345 self.auth_type = Some(auth_type.into());
346 self
347 }
348
349 pub fn with_oidc_token_env(mut self, env_var: impl Into<String>) -> Self {
351 self.oidc_token_env = Some(env_var.into());
352 self
353 }
354
355 pub fn with_oidc_token_filepath(mut self, path: impl Into<String>) -> Self {
357 self.oidc_token_filepath = Some(path.into());
358 self
359 }
360
361 pub fn with_retry(mut self, retry_config: RetryConfig) -> Self {
363 self.retry_config = retry_config;
364 self
365 }
366
367 pub fn with_client_options(mut self, options: ClientOptions) -> Self {
369 self.client_options = options;
370 self
371 }
372
373 fn azure_client_secret_provider(
376 &self,
377 runtime: Option<&Handle>,
378 ) -> Result<DatabricksCredentialProvider> {
379 let (Some(client_id), Some(client_secret), Some(tenant_id)) =
380 (&self.client_id, &self.client_secret, &self.azure_tenant_id)
381 else {
382 return Err(crate::Error::Generic {
383 source: "auth_type=azure-client-secret requires DATABRICKS_CLIENT_ID, \
384 DATABRICKS_CLIENT_SECRET and DATABRICKS_AZURE_TENANT_ID to be set"
385 .into(),
386 });
387 };
388 let provider = ClientSecretOAuthProvider::new_with_scope(
389 client_id.clone(),
390 client_secret.clone(),
391 tenant_id,
392 self.azure_authority_host.clone(),
393 AZURE_DATABRICKS_SCOPE,
394 );
395 let client = self.client_options.client()?;
396 let service = make_service(client.clone(), runtime);
397 let azure =
398 TokenCredentialProvider::new(provider, client, service, self.retry_config.clone());
399 Ok(Arc::new(AzureToDatabricksBridge {
400 inner: Arc::new(azure),
401 }))
402 }
403
404 pub fn build(self, runtime: Option<&Handle>) -> Result<DatabricksConfig> {
408 let scope = self
409 .scope
410 .as_deref()
411 .unwrap_or(DEFAULT_DATABRICKS_SCOPE)
412 .to_owned();
413
414 let has_pat = self.token.is_some();
416 let has_m2m = self.client_id.is_some() && self.client_secret.is_some();
417 let has_azure_sp = has_m2m && self.azure_tenant_id.is_some();
418 let has_env_oidc =
419 self.oidc_token_env.is_some() || std::env::var(DEFAULT_OIDC_TOKEN_ENV).is_ok();
420 let has_file_oidc = self.oidc_token_filepath.is_some();
421 let has_azure_msi = self.azure_resource_id.is_some();
422 let has_gcp = std::env::var("GOOGLE_APPLICATION_CREDENTIALS").is_ok();
423
424 let retry_config = self.retry_config.clone();
426 let client_options = self.client_options.clone();
427
428 let credentials: DatabricksCredentialProvider = if let Some(creds) = self.credentials {
429 creds
431 } else if let Some(auth_type) = self.auth_type.clone() {
432 self.build_forced_auth(
434 auth_type,
435 scope,
436 runtime,
437 has_pat,
438 has_m2m,
439 has_env_oidc,
440 has_file_oidc,
441 has_azure_msi,
442 has_gcp,
443 )?
444 } else if has_pat {
445 Arc::new(StaticCredentialProvider::new(DatabricksCredential {
447 bearer: self.token.unwrap(),
448 }))
449 } else if has_azure_sp {
450 self.azure_client_secret_provider(runtime)?
452 } else if has_m2m {
453 let host = self.host.as_deref().unwrap_or("").trim_end_matches('/');
455 let token_url = format!("{host}/oidc/v1/token");
456 let provider = DatabricksM2MProvider {
457 token_url,
458 client_id: self.client_id.unwrap(),
459 client_secret: self.client_secret.unwrap(),
460 scope,
461 };
462 let client = client_options.client()?;
463 let service = make_service(client.clone(), runtime);
464 Arc::new(TokenCredentialProvider::new(
465 provider,
466 client,
467 service,
468 retry_config.clone(),
469 )) as _
470 } else if has_env_oidc {
471 let env_var = self
473 .oidc_token_env
474 .unwrap_or_else(|| DEFAULT_OIDC_TOKEN_ENV.to_owned());
475 let provider = OidcEnvTokenProvider { env_var };
476 let client = client_options.client()?;
477 let service = make_service(client.clone(), runtime);
478 Arc::new(TokenCredentialProvider::new(
479 provider,
480 client,
481 service,
482 retry_config.clone(),
483 )) as _
484 } else if has_file_oidc {
485 let provider = OidcFileTokenProvider {
487 filepath: self.oidc_token_filepath.unwrap(),
488 };
489 let client = client_options.client()?;
490 let service = make_service(client.clone(), runtime);
491 Arc::new(TokenCredentialProvider::new(
492 provider,
493 client,
494 service,
495 retry_config.clone(),
496 )) as _
497 } else if has_azure_msi {
498 let msi_provider = ImdsManagedIdentityProvider::new_with_resource(
500 None,
501 None,
502 None,
503 None,
504 AZURE_DATABRICKS_RESOURCE,
505 );
506 let client = client_options.metadata_client()?;
507 let service = make_service(client.clone(), runtime);
508 let azure_provider =
509 TokenCredentialProvider::new(msi_provider, client, service, retry_config.clone());
510 Arc::new(AzureToDatabricksBridge {
511 inner: Arc::new(azure_provider),
512 }) as _
513 } else if has_gcp {
514 let host = self.host.as_deref().unwrap_or("").trim_end_matches('/');
516 let token_url = format!("{host}/oidc/v1/token");
517 let gcp_config = crate::gcp::GoogleBuilder::new().build(runtime)?;
518 let provider = DatabricksGcpTokenExchangeProvider {
519 token_url,
520 gcp_provider: gcp_config.credentials,
521 };
522 let client = client_options.client()?;
523 let service = make_service(client.clone(), runtime);
524 Arc::new(TokenCredentialProvider::new(
525 provider,
526 client,
527 service,
528 retry_config.clone(),
529 )) as _
530 } else {
531 return Err(crate::Error::Generic {
532 source: "No Databricks credentials configured. Set DATABRICKS_TOKEN, \
533 DATABRICKS_CLIENT_ID + DATABRICKS_CLIENT_SECRET, \
534 DATABRICKS_OIDC_TOKEN (env-oidc), DATABRICKS_OIDC_TOKEN_FILEPATH (file-oidc), \
535 DATABRICKS_AZURE_RESOURCE_ID, or GOOGLE_APPLICATION_CREDENTIALS."
536 .into(),
537 });
538 };
539
540 Ok(DatabricksConfig {
541 credentials,
542 retry_config,
543 client_options,
544 })
545 }
546
547 #[allow(clippy::too_many_arguments)]
549 fn build_forced_auth(
550 self,
551 auth_type: String,
552 scope: String,
553 runtime: Option<&Handle>,
554 has_pat: bool,
555 has_m2m: bool,
556 has_env_oidc: bool,
557 has_file_oidc: bool,
558 has_azure_msi: bool,
559 has_gcp: bool,
560 ) -> Result<DatabricksCredentialProvider> {
561 match auth_type.as_str() {
562 "azure-client-secret" => self.azure_client_secret_provider(runtime),
563 "azure-cli" => Ok(Arc::new(AzureToDatabricksBridge {
564 inner: Arc::new(AzureCliCredential::new_with_scope(AZURE_DATABRICKS_SCOPE)),
565 })),
566 "pat" => {
567 if !has_pat {
568 return Err(crate::Error::Generic {
569 source: "auth_type=pat requires DATABRICKS_TOKEN to be set".into(),
570 });
571 }
572 Ok(Arc::new(StaticCredentialProvider::new(DatabricksCredential {
573 bearer: self.token.unwrap(),
574 })))
575 }
576 "oauth-m2m" => {
577 if !has_m2m {
578 return Err(crate::Error::Generic {
579 source: "auth_type=oauth-m2m requires DATABRICKS_CLIENT_ID and \
580 DATABRICKS_CLIENT_SECRET to be set"
581 .into(),
582 });
583 }
584 let host = self.host.as_deref().unwrap_or("").trim_end_matches('/');
585 let token_url = format!("{host}/oidc/v1/token");
586 let provider = DatabricksM2MProvider {
587 token_url,
588 client_id: self.client_id.unwrap(),
589 client_secret: self.client_secret.unwrap(),
590 scope,
591 };
592 let client = self.client_options.client()?;
593 let service = make_service(client.clone(), runtime);
594 Ok(Arc::new(TokenCredentialProvider::new(
595 provider,
596 client,
597 service,
598 self.retry_config,
599 )) as _)
600 }
601 "env-oidc" => {
602 if !has_env_oidc {
603 return Err(crate::Error::Generic {
604 source: "auth_type=env-oidc requires DATABRICKS_OIDC_TOKEN or \
605 DATABRICKS_OIDC_TOKEN_ENV to be set"
606 .into(),
607 });
608 }
609 let env_var = self
610 .oidc_token_env
611 .unwrap_or_else(|| DEFAULT_OIDC_TOKEN_ENV.to_owned());
612 let provider = OidcEnvTokenProvider { env_var };
613 let client = self.client_options.client()?;
614 let service = make_service(client.clone(), runtime);
615 Ok(Arc::new(TokenCredentialProvider::new(
616 provider,
617 client,
618 service,
619 self.retry_config,
620 )) as _)
621 }
622 "file-oidc" => {
623 if !has_file_oidc {
624 return Err(crate::Error::Generic {
625 source: "auth_type=file-oidc requires DATABRICKS_OIDC_TOKEN_FILEPATH \
626 to be set"
627 .into(),
628 });
629 }
630 let provider = OidcFileTokenProvider {
631 filepath: self.oidc_token_filepath.unwrap(),
632 };
633 let client = self.client_options.client()?;
634 let service = make_service(client.clone(), runtime);
635 Ok(Arc::new(TokenCredentialProvider::new(
636 provider,
637 client,
638 service,
639 self.retry_config,
640 )) as _)
641 }
642 "azure-msi" => {
643 if !has_azure_msi {
644 return Err(crate::Error::Generic {
645 source: "auth_type=azure-msi requires DATABRICKS_AZURE_RESOURCE_ID \
646 to be set"
647 .into(),
648 });
649 }
650 let msi_provider = ImdsManagedIdentityProvider::new_with_resource(
651 None,
652 None,
653 None,
654 None,
655 AZURE_DATABRICKS_RESOURCE,
656 );
657 let client = self.client_options.metadata_client()?;
658 let service = make_service(client.clone(), runtime);
659 let azure_provider = TokenCredentialProvider::new(
660 msi_provider,
661 client,
662 service,
663 self.retry_config,
664 );
665 Ok(Arc::new(AzureToDatabricksBridge {
666 inner: Arc::new(azure_provider),
667 }) as _)
668 }
669 "gcp" => {
670 if !has_gcp {
671 return Err(crate::Error::Generic {
672 source: "auth_type=gcp requires GOOGLE_APPLICATION_CREDENTIALS to be set"
673 .into(),
674 });
675 }
676 let host = self.host.as_deref().unwrap_or("").trim_end_matches('/');
677 let token_url = format!("{host}/oidc/v1/token");
678 let gcp_config = crate::gcp::GoogleBuilder::new().build(runtime)?;
679 let provider = DatabricksGcpTokenExchangeProvider {
680 token_url,
681 gcp_provider: gcp_config.credentials,
682 };
683 let client = self.client_options.client()?;
684 let service = make_service(client.clone(), runtime);
685 Ok(Arc::new(TokenCredentialProvider::new(
686 provider,
687 client,
688 service,
689 self.retry_config,
690 )) as _)
691 }
692 other => Err(crate::Error::Generic {
693 source: format!("Unknown auth_type: {other:?}. Valid values: pat, oauth-m2m, env-oidc, file-oidc, azure-msi, gcp").into(),
694 }),
695 }
696 }
697}
698
699#[derive(Debug)]
704struct AzureToDatabricksBridge {
705 inner: Arc<dyn CredentialProvider<Credential = crate::azure::credential::AzureCredential>>,
706}
707
708#[async_trait::async_trait]
709impl CredentialProvider for AzureToDatabricksBridge {
710 type Credential = DatabricksCredential;
711
712 async fn get_credential(&self) -> crate::Result<Arc<DatabricksCredential>> {
713 use crate::azure::credential::AzureCredential;
714 let cred = self.inner.get_credential().await?;
715 let AzureCredential::BearerToken(token) = cred.as_ref();
716 Ok(Arc::new(DatabricksCredential {
717 bearer: token.clone(),
718 }))
719 }
720}
721
722#[cfg(test)]
723mod tests {
724 use std::collections::HashMap;
725
726 use super::*;
727
728 #[test]
729 fn test_databricks_config_from_map() {
730 let options = HashMap::from([
731 (
732 "databricks_host",
733 "https://my-workspace.azuredatabricks.net",
734 ),
735 ("databricks_token", "dapi1234567890"),
736 ("databricks_oauth_scope", "all-apis"),
737 ]);
738
739 let builder = options
740 .into_iter()
741 .fold(DatabricksBuilder::new(), |b, (k, v)| {
742 b.with_config(k.parse().unwrap(), v)
743 });
744
745 assert_eq!(
746 builder.host.as_deref(),
747 Some("https://my-workspace.azuredatabricks.net")
748 );
749 assert_eq!(builder.token.as_deref(), Some("dapi1234567890"));
750 assert_eq!(builder.scope.as_deref(), Some("all-apis"));
751 }
752
753 #[test]
754 fn test_databricks_config_key_roundtrip() {
755 let cases = [
756 ("databricks_host", DatabricksConfigKey::Host),
757 ("host", DatabricksConfigKey::Host),
758 ("databricks_token", DatabricksConfigKey::Token),
759 ("token", DatabricksConfigKey::Token),
760 ("databricks_client_id", DatabricksConfigKey::ClientId),
761 ("client_id", DatabricksConfigKey::ClientId),
762 (
763 "databricks_client_secret",
764 DatabricksConfigKey::ClientSecret,
765 ),
766 ("client_secret", DatabricksConfigKey::ClientSecret),
767 ("databricks_account_id", DatabricksConfigKey::AccountId),
768 ("account_id", DatabricksConfigKey::AccountId),
769 (
770 "databricks_azure_resource_id",
771 DatabricksConfigKey::AzureResourceId,
772 ),
773 ("azure_resource_id", DatabricksConfigKey::AzureResourceId),
774 ("databricks_oauth_scope", DatabricksConfigKey::Scope),
775 ("oauth_scope", DatabricksConfigKey::Scope),
776 ("databricks_config_file", DatabricksConfigKey::ConfigFile),
777 ("config_file", DatabricksConfigKey::ConfigFile),
778 (
779 "databricks_config_profile",
780 DatabricksConfigKey::ConfigProfile,
781 ),
782 ("config_profile", DatabricksConfigKey::ConfigProfile),
783 ("profile", DatabricksConfigKey::ConfigProfile),
784 ("databricks_auth_type", DatabricksConfigKey::AuthType),
785 ("auth_type", DatabricksConfigKey::AuthType),
786 (
787 "databricks_oidc_token_env",
788 DatabricksConfigKey::OidcTokenEnv,
789 ),
790 ("oidc_token_env", DatabricksConfigKey::OidcTokenEnv),
791 (
792 "databricks_oidc_token_filepath",
793 DatabricksConfigKey::OidcTokenFilepath,
794 ),
795 (
796 "oidc_token_filepath",
797 DatabricksConfigKey::OidcTokenFilepath,
798 ),
799 ];
800
801 for (s, expected) in cases {
802 assert_eq!(
803 s.parse::<DatabricksConfigKey>().unwrap(),
804 expected,
805 "failed for {s}"
806 );
807 }
808 }
809
810 #[tokio::test]
811 async fn test_build_with_pat() {
812 let config = DatabricksBuilder::new()
813 .with_host("https://my.azuredatabricks.net")
814 .with_token("dapimytoken")
815 .build(None)
816 .unwrap();
817
818 let cred = config.credentials.get_credential().await.unwrap();
819 assert_eq!(cred.bearer, "dapimytoken");
820 }
821
822 #[test]
823 fn test_auth_type_enforcement_missing_fields() {
824 let err = DatabricksBuilder::new()
825 .with_auth_type("pat")
826 .build(None)
827 .unwrap_err();
828 assert!(err.to_string().contains("pat"));
829 }
830}