stripe/hyper/
client_builder.rs

1use hyper::http::{HeaderValue, Uri};
2use stripe_client_core::{RequestStrategy, SharedConfigBuilder};
3use stripe_shared::{AccountId, ApplicationId};
4
5use crate::StripeError;
6use crate::hyper::client::Client;
7
8static DEFAULT_USER_AGENT: &str = concat!("Stripe/v1 RustBindings/", env!("CARGO_PKG_VERSION"));
9const DEFAULT_API_BASE: &str = "https://api.stripe.com/";
10
11/// Configuration for a Stripe client.
12///
13/// This builder allows customizing request behavior, headers sent to Stripe, and endpoint
14/// targeted. Some defaults can be overridden on a per-request basis.
15#[derive(Clone, Debug)]
16pub struct ClientBuilder {
17    inner: SharedConfigBuilder,
18}
19
20impl ClientBuilder {
21    /// Create a new `ClientBuilder` with the given secret key.
22    pub fn new(secret: impl Into<String>) -> Self {
23        Self { inner: SharedConfigBuilder::new(secret) }
24    }
25
26    /// Set the Stripe client id for the client. This will be sent to stripe
27    /// with the "client-id" header.
28    pub fn client_id(mut self, client_id: ApplicationId) -> Self {
29        self.inner = self.inner.client_id(client_id);
30        self
31    }
32
33    /// Set the default Stripe account id used for the client. This will be sent to stripe
34    /// with the "stripe-account" header, unless it is overridden when customizing
35    /// a request.
36    pub fn account_id(mut self, account_id: AccountId) -> Self {
37        self.inner = self.inner.account_id(account_id);
38        self
39    }
40
41    /// Set the default `RequestStrategy` used when making requests.
42    pub fn request_strategy(mut self, strategy: RequestStrategy) -> Self {
43        self.inner = self.inner.request_strategy(strategy);
44        self
45    }
46
47    /// Create a new client pointed at a specific URL. This is useful for testing.
48    pub fn url(mut self, url: impl Into<String>) -> Self {
49        self.inner = self.inner.url(url);
50        self
51    }
52
53    /// Set the application info for the client.
54    ///
55    /// It is recommended that applications set this so that
56    /// Stripe is able to understand usage patterns from your
57    /// user agent.
58    pub fn app_info(
59        mut self,
60        name: impl Into<String>,
61        version: Option<String>,
62        url: Option<String>,
63    ) -> Self {
64        self.inner = self.inner.app_info(name, version, url);
65        self
66    }
67
68    fn try_into_config(self) -> Result<ClientConfig, StripeError> {
69        let api_base = if let Some(url) = self.inner.api_base {
70            Uri::try_from(url).map_err(|err| {
71                StripeError::ConfigError(format!("user-provided Stripe url is invalid: {err}"))
72            })?
73        } else {
74            Uri::from_static(DEFAULT_API_BASE)
75        };
76
77        let user_agent_header = if let Some(app_info_str) = self.inner.app_info_str {
78            HeaderValue::try_from(format!("{DEFAULT_USER_AGENT} {app_info_str}"))
79                .map_err(|_| cons_header_err("app_info"))?
80        } else {
81            HeaderValue::from_static(DEFAULT_USER_AGENT)
82        };
83
84        let mut secret = HeaderValue::try_from(format!("Bearer {}", self.inner.secret))
85            .map_err(|_| cons_header_err("secret"))?;
86        secret.set_sensitive(true);
87        Ok(ClientConfig {
88            stripe_version: HeaderValue::from_str(self.inner.stripe_version.as_str())
89                .expect("all stripe versions produce valid header values"),
90            user_agent: user_agent_header,
91            client_id: self
92                .inner
93                .client_id
94                .map(|id| HeaderValue::try_from(id.to_string()))
95                .transpose()
96                .map_err(|_| cons_header_err("client_id"))?,
97            account_id: self
98                .inner
99                .account_id
100                .map(|id| HeaderValue::try_from(id.to_string()))
101                .transpose()
102                .map_err(|_| cons_header_err("account_id"))?,
103            request_strategy: self.inner.request_strategy.unwrap_or(RequestStrategy::Once),
104            secret,
105            api_base,
106        })
107    }
108
109    /// Builds a Stripe `client`.
110    ///
111    /// # Errors
112    /// This method errors if any of the specified configuration is invalid.
113    pub fn build(self) -> Result<Client, StripeError> {
114        Ok(Client::from_config(self.try_into_config()?))
115    }
116
117    /// Builds a Stripe `client` for making blocking API calls.
118    ///
119    /// # Errors
120    /// This method errors if any of the specified configuration is invalid.
121    ///
122    /// # Panics
123    /// This method panics if called from within an async runtime.
124    #[cfg(feature = "blocking")]
125    pub fn build_sync(self) -> Result<crate::hyper::blocking::Client, StripeError> {
126        Ok(crate::hyper::blocking::Client::from_async(self.build()?))
127    }
128}
129
130fn cons_header_err(config_name: &'static str) -> StripeError {
131    StripeError::ClientError(format!("`{config_name}` can only include visible ASCII characters"))
132}
133
134/// A validated client configuration. All configuration types are carefully chosen to be
135/// cheaply clonable so that the client can be cheaply cloned.
136#[derive(Clone, Debug)]
137pub struct ClientConfig {
138    pub stripe_version: HeaderValue,
139    pub user_agent: HeaderValue,
140    pub client_id: Option<HeaderValue>,
141    pub account_id: Option<HeaderValue>,
142    pub request_strategy: RequestStrategy,
143    // NB: This `HeaderValue` is marked as sensitive, so it won't be debug printed.
144    pub secret: HeaderValue,
145    pub api_base: Uri,
146}