faucet_common_bigquery/
lib.rs1#![cfg_attr(docsrs, feature(doc_cfg))]
2
3use faucet_core::FaucetError;
20use gcp_bigquery_client::Client;
21use schemars::JsonSchema;
22use serde::{Deserialize, Serialize};
23
24#[derive(Clone, Serialize, Deserialize, JsonSchema)]
30#[serde(tag = "type", content = "config", rename_all = "snake_case")]
31pub enum BigQueryCredentials {
32 ServiceAccountKeyPath {
34 path: String,
36 },
37 ServiceAccountKey {
39 json: String,
41 },
42 ApplicationDefault,
44}
45
46impl std::fmt::Debug for BigQueryCredentials {
47 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
48 match self {
49 Self::ServiceAccountKeyPath { path } => f
50 .debug_struct("ServiceAccountKeyPath")
51 .field("path", path)
52 .finish(),
53 Self::ServiceAccountKey { .. } => write!(f, "ServiceAccountKey(***)"),
54 Self::ApplicationDefault => write!(f, "ApplicationDefault"),
55 }
56 }
57}
58
59pub async fn build_client(creds: &BigQueryCredentials) -> Result<Client, FaucetError> {
64 match creds {
65 BigQueryCredentials::ServiceAccountKeyPath { path } => {
66 Client::from_service_account_key_file(path)
67 .await
68 .map_err(|e| FaucetError::Auth(format!("BigQuery auth failed: {e}")))
69 }
70 BigQueryCredentials::ServiceAccountKey { json } => {
71 let sa_key = serde_json::from_str(json)
72 .map_err(|e| FaucetError::Auth(format!("invalid service account JSON: {e}")))?;
73 Client::from_service_account_key(sa_key, false)
74 .await
75 .map_err(|e| FaucetError::Auth(format!("BigQuery auth failed: {e}")))
76 }
77 BigQueryCredentials::ApplicationDefault => Client::from_application_default_credentials()
78 .await
79 .map_err(|e| FaucetError::Auth(format!("BigQuery auth failed: {e}"))),
80 }
81}
82
83#[cfg(test)]
84mod tests {
85 use super::*;
86
87 #[test]
88 fn debug_masks_inline_service_account_key() {
89 let creds = BigQueryCredentials::ServiceAccountKey {
90 json: "secret-json".into(),
91 };
92 let debug = format!("{creds:?}");
93 assert!(debug.contains("***"));
94 assert!(!debug.contains("secret-json"));
95 }
96
97 #[test]
98 fn debug_does_not_mask_service_account_key_path() {
99 let creds = BigQueryCredentials::ServiceAccountKeyPath {
100 path: "/path/to/key.json".into(),
101 };
102 let debug = format!("{creds:?}");
103 assert!(debug.contains("/path/to/key.json"));
104 }
105
106 #[test]
107 fn debug_application_default_is_plain() {
108 let creds = BigQueryCredentials::ApplicationDefault;
109 assert_eq!(format!("{creds:?}"), "ApplicationDefault");
110 }
111
112 #[test]
113 fn serde_round_trip_application_default() {
114 let json = serde_json::to_string(&BigQueryCredentials::ApplicationDefault).unwrap();
115 let parsed: BigQueryCredentials = serde_json::from_str(&json).unwrap();
116 assert!(matches!(parsed, BigQueryCredentials::ApplicationDefault));
117 }
118
119 #[test]
120 fn serde_round_trip_service_account_key_path() {
121 let creds = BigQueryCredentials::ServiceAccountKeyPath {
122 path: "/k.json".into(),
123 };
124 let json = serde_json::to_string(&creds).unwrap();
125 assert_eq!(
126 json,
127 r#"{"type":"service_account_key_path","config":{"path":"/k.json"}}"#
128 );
129 let parsed: BigQueryCredentials = serde_json::from_str(&json).unwrap();
130 match parsed {
131 BigQueryCredentials::ServiceAccountKeyPath { path } => assert_eq!(path, "/k.json"),
132 _ => panic!("expected ServiceAccountKeyPath"),
133 }
134 }
135
136 #[tokio::test]
137 async fn build_client_with_invalid_inline_json_surfaces_auth_error() {
138 let creds = BigQueryCredentials::ServiceAccountKey {
139 json: "not-json".into(),
140 };
141 match build_client(&creds).await {
142 Ok(_) => panic!("expected auth error"),
143 Err(FaucetError::Auth(_)) => {}
144 Err(other) => panic!("expected Auth error, got {other:?}"),
145 }
146 }
147}