use std::path::PathBuf;
use hyper::client::Client;
use crate::Auth;
const SERVICE_ACCOUNT_ENV_VAR: &str = "GOOGLE_APPLICATION_CREDENTIALS";
#[derive(Debug, Clone, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum ServiceAccountAuth {
EnvVar,
Path(PathBuf),
}
impl Default for ServiceAccountAuth {
fn default() -> Self {
Self::EnvVar
}
}
#[derive(Debug, Clone, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum AuthFlow {
ServiceAccount(ServiceAccountAuth),
NoAuth,
}
impl Default for AuthFlow {
fn default() -> Self {
AuthFlow::ServiceAccount(ServiceAccountAuth::default())
}
}
config_default! {
#[derive(Debug, Clone, serde::Deserialize)]
#[non_exhaustive]
pub struct ClientBuilderConfig {
@default(AuthFlow::ServiceAccount(ServiceAccountAuth::EnvVar), "ClientBuilderConfig::default_auth_flow")
pub auth_flow: AuthFlow,
}
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum CreateBuilderError {
#[error("failed to read service account key {}", _1.display())]
ReadServiceAccountKey(#[source] std::io::Error, PathBuf),
#[error(
"environment variable {SERVICE_ACCOUNT_ENV_VAR} isn't set. Consider either setting the \
variable, or specifying the path with ServiceAccountAuth::Path(...)"
)]
CredentialsVarMissing,
#[error("failed to initialize authenticator")]
Authenticator(#[source] std::io::Error),
#[error("failed to initialize HTTP connector")]
Connector(#[source] Box<dyn std::error::Error + Send + Sync + 'static>),
}
cfg_if::cfg_if! {
if #[cfg(feature="openssl")] {
pub type DefaultConnector =
hyper_openssl::HttpsConnector<hyper::client::connect::HttpConnector>;
impl ClientBuilder {
#[cfg_attr(docsrs, doc(cfg(any(feature="rustls", feature="openssl"))))]
pub async fn new(config: ClientBuilderConfig) -> Result<Self, CreateBuilderError> {
let connector = hyper_openssl::HttpsConnector::new()
.map_err(|e| CreateBuilderError::Connector(e.into()))?;
Self::with_connector(config, connector).await
}
}
}
else if #[cfg(feature="rustls")] {
pub type DefaultConnector =
hyper_rustls::HttpsConnector<hyper::client::connect::HttpConnector>;
impl ClientBuilder {
#[cfg_attr(docsrs, doc(cfg(any(feature="rustls", feature="openssl"))))]
pub async fn new(config: ClientBuilderConfig) -> Result<Self, CreateBuilderError> {
let connector = hyper_rustls::HttpsConnector::with_native_roots();
Self::with_connector(config, connector).await
}
}
}
else {
#[doc(hidden)]
pub struct NoConnectorFeaturesEnabled;
pub type DefaultConnector = NoConnectorFeaturesEnabled;
}
}
pub struct ClientBuilder<Connector = DefaultConnector> {
#[allow(unused)]
pub(crate) connector: Connector,
#[allow(unused)]
pub(crate) auth: Option<Auth<Connector>>,
#[allow(unused)]
pub(crate) client: Client<Connector>,
}
impl<C> ClientBuilder<C> {
pub async fn with_connector(
config: ClientBuilderConfig,
connector: C,
) -> Result<Self, CreateBuilderError>
where
C: crate::Connect + Clone + Send + Sync + 'static,
{
use AuthFlow::{NoAuth, ServiceAccount};
let client = hyper::client::Client::builder().build(connector.clone());
let auth = match config.auth_flow {
NoAuth => None,
ServiceAccount(service_config) => Some(
create_service_auth(
match service_config {
ServiceAccountAuth::Path(path) => path.into_os_string(),
ServiceAccountAuth::EnvVar => std::env::var_os(SERVICE_ACCOUNT_ENV_VAR)
.ok_or(CreateBuilderError::CredentialsVarMissing)?,
},
client.clone(),
)
.await?,
),
};
Ok(Self {
connector,
client,
auth,
})
}
}
async fn create_service_auth<C>(
service_account_key_path: impl AsRef<std::path::Path>,
client: Client<C>,
) -> Result<Auth<C>, CreateBuilderError>
where
C: hyper::client::connect::Connect + Clone + Send + Sync + 'static,
{
let service_account_key =
yup_oauth2::read_service_account_key(service_account_key_path.as_ref())
.await
.map_err(|e| {
CreateBuilderError::ReadServiceAccountKey(
e,
service_account_key_path.as_ref().to_owned(),
)
})?;
Ok(
yup_oauth2::ServiceAccountAuthenticator::builder(service_account_key)
.hyper_client(client)
.build()
.await
.map_err(CreateBuilderError::Authenticator)?,
)
}