use crate::backoff_policy::{BackoffPolicy, BackoffPolicyArg};
use crate::polling_backoff_policy::{PollingBackoffPolicy, PollingBackoffPolicyArg};
use crate::polling_error_policy::{PollingErrorPolicy, PollingErrorPolicyArg};
use crate::retry_policy::{RetryPolicy, RetryPolicyArg};
use crate::retry_throttler::{RetryThrottlerArg, SharedRetryThrottler};
use std::sync::Arc;
pub type Result<T> = std::result::Result<T, Error>;
#[derive(thiserror::Error, Debug)]
#[error(transparent)]
pub struct Error(ErrorKind);
impl Error {
pub fn is_default_credentials(&self) -> bool {
matches!(&self.0, ErrorKind::DefaultCredentials(_))
}
pub fn is_transport(&self) -> bool {
matches!(&self.0, ErrorKind::Transport(_))
}
#[cfg_attr(not(feature = "_internal-semver"), doc(hidden))]
pub fn cred<T: Into<BoxError>>(source: T) -> Self {
Self(ErrorKind::DefaultCredentials(source.into()))
}
#[cfg_attr(not(feature = "_internal-semver"), doc(hidden))]
pub fn transport<T: Into<BoxError>>(source: T) -> Self {
Self(ErrorKind::Transport(source.into()))
}
}
#[derive(thiserror::Error, Debug)]
enum ErrorKind {
#[error("could not create default credentials")]
DefaultCredentials(#[source] BoxError),
#[error("could not initialize transport client")]
Transport(#[source] BoxError),
}
type BoxError = Box<dyn std::error::Error + Send + Sync + 'static>;
#[derive(Clone, Debug)]
pub struct ClientBuilder<F, Cr> {
config: internal::ClientConfig<Cr>,
factory: F,
}
impl<F, Cr> ClientBuilder<F, Cr> {
pub async fn build<C>(self) -> Result<C>
where
F: internal::ClientFactory<Client = C, Credentials = Cr>,
{
self.factory.build(self.config).await
}
pub fn with_endpoint<V: Into<String>>(mut self, v: V) -> Self {
self.config.endpoint = Some(v.into());
self
}
pub fn with_tracing(mut self) -> Self {
self.config.tracing = true;
self
}
pub fn with_credentials<T: Into<Cr>>(mut self, v: T) -> Self {
self.config.cred = Some(v.into());
self
}
#[allow(dead_code)]
pub(crate) fn with_universe_domain<V: Into<String>>(mut self, v: V) -> Self {
self.config.universe_domain = Some(v.into());
self
}
pub fn with_retry_policy<V: Into<RetryPolicyArg>>(mut self, v: V) -> Self {
self.config.retry_policy = Some(v.into().into());
self
}
pub fn with_backoff_policy<V: Into<BackoffPolicyArg>>(mut self, v: V) -> Self {
self.config.backoff_policy = Some(v.into().into());
self
}
pub fn with_retry_throttler<V: Into<RetryThrottlerArg>>(mut self, v: V) -> Self {
self.config.retry_throttler = v.into().into();
self
}
pub fn with_polling_error_policy<V: Into<PollingErrorPolicyArg>>(mut self, v: V) -> Self {
self.config.polling_error_policy = Some(v.into().0);
self
}
pub fn with_polling_backoff_policy<V: Into<PollingBackoffPolicyArg>>(mut self, v: V) -> Self {
self.config.polling_backoff_policy = Some(v.into().0);
self
}
}
#[cfg_attr(not(feature = "_internal-semver"), doc(hidden))]
pub mod internal {
use super::*;
pub trait ClientFactory {
type Client;
type Credentials;
fn build(
self,
config: internal::ClientConfig<Self::Credentials>,
) -> impl Future<Output = Result<Self::Client>>;
}
pub fn new_builder<F, Cr, C>(factory: F) -> super::ClientBuilder<F, Cr>
where
F: ClientFactory<Client = C, Credentials = Cr>,
{
super::ClientBuilder {
factory,
config: ClientConfig::default(),
}
}
#[derive(Clone, Debug)]
#[non_exhaustive]
pub struct ClientConfig<Cr> {
pub endpoint: Option<String>,
pub universe_domain: Option<String>,
pub cred: Option<Cr>,
pub tracing: bool,
pub retry_policy: Option<Arc<dyn RetryPolicy>>,
pub backoff_policy: Option<Arc<dyn BackoffPolicy>>,
pub retry_throttler: SharedRetryThrottler,
pub polling_error_policy: Option<Arc<dyn PollingErrorPolicy>>,
pub polling_backoff_policy: Option<Arc<dyn PollingBackoffPolicy>>,
pub disable_automatic_decompression: bool,
pub disable_follow_redirects: bool,
pub grpc_subchannel_count: Option<usize>,
pub grpc_request_buffer_capacity: Option<usize>,
pub grpc_max_header_list_size: Option<u32>,
}
impl<Cr> std::default::Default for ClientConfig<Cr> {
fn default() -> Self {
use crate::retry_throttler::AdaptiveThrottler;
use std::sync::{Arc, Mutex};
Self {
endpoint: None,
universe_domain: None,
cred: None,
tracing: false,
retry_policy: None,
backoff_policy: None,
retry_throttler: Arc::new(Mutex::new(AdaptiveThrottler::default())),
polling_error_policy: None,
polling_backoff_policy: None,
disable_automatic_decompression: false,
disable_follow_redirects: false,
grpc_subchannel_count: None,
grpc_request_buffer_capacity: None,
grpc_max_header_list_size: None,
}
}
}
pub fn with_automatic_decompression<F, Cr>(
mut builder: super::ClientBuilder<F, Cr>,
v: bool,
) -> super::ClientBuilder<F, Cr> {
builder.config.disable_automatic_decompression = !v;
builder
}
pub fn with_follow_redirects<F, Cr>(
mut builder: super::ClientBuilder<F, Cr>,
v: bool,
) -> super::ClientBuilder<F, Cr> {
builder.config.disable_follow_redirects = !v;
builder
}
}
#[doc(hidden)]
pub mod examples {
type Config = super::internal::ClientConfig<Credentials>;
use super::Result;
#[allow(dead_code)]
pub struct Client(Config);
impl Client {
pub fn builder() -> client::Builder {
super::internal::new_builder(client::Factory)
}
async fn new(config: super::internal::ClientConfig<Credentials>) -> Result<Self> {
Ok(Self(config))
}
}
mod client {
pub type Builder = super::super::ClientBuilder<Factory, super::Credentials>;
pub struct Factory;
impl super::super::internal::ClientFactory for Factory {
type Credentials = super::Credentials;
type Client = super::Client;
async fn build(
self,
config: crate::client_builder::internal::ClientConfig<Self::Credentials>,
) -> super::Result<Self::Client> {
Self::Client::new(config).await
}
}
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct Credentials {
pub scopes: Vec<String>,
}
pub mod credentials {
pub mod mds {
#[derive(Clone, Default)]
pub struct Builder(super::super::Credentials);
impl Builder {
pub fn new() -> Self {
Self(super::super::Credentials::default())
}
pub fn build(self) -> super::super::Credentials {
self.0
}
pub fn scopes<I, V>(mut self, iter: I) -> Self
where
I: IntoIterator<Item = V>,
V: Into<String>,
{
self.0.scopes = iter.into_iter().map(|v| v.into()).collect();
self
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn build_default() {
let client = Client::builder().build().await.unwrap();
let config = client.0;
assert_eq!(config.endpoint, None);
assert_eq!(config.cred, None);
assert!(!config.tracing);
assert!(
format!("{:?}", &config).contains("AdaptiveThrottler"),
"{config:?}"
);
assert!(config.retry_policy.is_none(), "{config:?}");
assert!(config.backoff_policy.is_none(), "{config:?}");
assert!(config.polling_error_policy.is_none(), "{config:?}");
assert!(config.polling_backoff_policy.is_none(), "{config:?}");
assert!(!config.disable_automatic_decompression, "{config:?}");
assert!(!config.disable_follow_redirects, "{config:?}");
}
#[tokio::test]
async fn endpoint() {
let client = Client::builder()
.with_endpoint("http://example.com")
.build()
.await
.unwrap();
let config = client.0;
assert_eq!(config.endpoint.as_deref(), Some("http://example.com"));
}
#[tokio::test]
async fn tracing() {
let client = Client::builder().with_tracing().build().await.unwrap();
let config = client.0;
assert!(config.tracing);
}
#[tokio::test]
async fn automatic_decompression() {
let client = Client::builder();
let client = super::super::internal::with_automatic_decompression(client, false)
.build()
.await
.unwrap();
let config = client.0;
assert!(config.disable_automatic_decompression);
let client = Client::builder();
let client = super::super::internal::with_automatic_decompression(client, true)
.build()
.await
.unwrap();
let config = client.0;
assert!(!config.disable_automatic_decompression);
}
#[tokio::test]
async fn follow_redirects() {
let client = Client::builder();
let client = super::super::internal::with_follow_redirects(client, false)
.build()
.await
.unwrap();
let config = client.0;
assert!(config.disable_follow_redirects);
let client = Client::builder();
let client = super::super::internal::with_follow_redirects(client, true)
.build()
.await
.unwrap();
let config = client.0;
assert!(!config.disable_follow_redirects);
}
#[tokio::test]
async fn credentials() {
let client = Client::builder()
.with_credentials(
credentials::mds::Builder::new()
.scopes(["test-scope"])
.build(),
)
.build()
.await
.unwrap();
let config = client.0;
let cred = config.cred.unwrap();
assert_eq!(cred.scopes, vec!["test-scope".to_string()]);
}
#[tokio::test]
async fn universe_domain() {
let client = Client::builder()
.with_universe_domain("some-universe-domain.com")
.build()
.await
.unwrap();
let config = client.0;
assert_eq!(
config.universe_domain,
Some("some-universe-domain.com".to_string())
);
}
#[tokio::test]
async fn retry_policy() {
use crate::retry_policy::RetryPolicyExt;
let client = Client::builder()
.with_retry_policy(crate::retry_policy::AlwaysRetry.with_attempt_limit(3))
.build()
.await
.unwrap();
let config = client.0;
assert!(config.retry_policy.is_some(), "{config:?}");
}
#[tokio::test]
async fn backoff_policy() {
let client = Client::builder()
.with_backoff_policy(crate::exponential_backoff::ExponentialBackoff::default())
.build()
.await
.unwrap();
let config = client.0;
assert!(config.backoff_policy.is_some(), "{config:?}");
}
#[tokio::test]
async fn retry_throttler() {
use crate::retry_throttler::CircuitBreaker;
let client = Client::builder()
.with_retry_throttler(CircuitBreaker::default())
.build()
.await
.unwrap();
let config = client.0;
assert!(
format!("{:?}", &config).contains("CircuitBreaker"),
"{config:?}"
);
}
#[tokio::test]
async fn polling_error_policy() {
use crate::polling_error_policy::PollingErrorPolicyExt;
let client = Client::builder()
.with_polling_error_policy(
crate::polling_error_policy::AlwaysContinue.with_attempt_limit(3),
)
.build()
.await
.unwrap();
let config = client.0;
assert!(config.polling_error_policy.is_some(), "{config:?}");
}
#[tokio::test]
async fn polling_backoff_policy() {
let client = Client::builder()
.with_polling_backoff_policy(
crate::exponential_backoff::ExponentialBackoff::default(),
)
.build()
.await
.unwrap();
let config = client.0;
assert!(config.polling_backoff_policy.is_some(), "{config:?}");
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::error::Error as _;
#[test]
fn error_credentials() {
let source = wkt::TimestampError::OutOfRange;
let error = Error::cred(source);
assert!(error.is_default_credentials(), "{error:?}");
assert!(error.to_string().contains("default credentials"), "{error}");
let got = error
.source()
.and_then(|e| e.downcast_ref::<wkt::TimestampError>());
assert!(
matches!(got, Some(wkt::TimestampError::OutOfRange)),
"{error:?}"
);
}
#[test]
fn transport() {
let source = wkt::TimestampError::OutOfRange;
let error = Error::transport(source);
assert!(error.is_transport(), "{error:?}");
assert!(error.to_string().contains("transport client"), "{error}");
let got = error
.source()
.and_then(|e| e.downcast_ref::<wkt::TimestampError>());
assert!(
matches!(got, Some(wkt::TimestampError::OutOfRange)),
"{error:?}"
);
}
}