stripe_client_core/
config.rs

1use std::fmt;
2use std::fmt::{Display, Formatter};
3
4use stripe_shared::version::VERSION;
5use stripe_shared::{AccountId, ApiVersion, ApplicationId};
6
7use crate::RequestStrategy;
8
9/// Shared configuration utilities for implementing a Stripe client.
10///
11/// This is meant for internal use when implementing compatible clients,
12/// so it may be more unstable with respect to semver.
13
14// The disclaimer above was written to justify the semver hazard of keeping all the fields here public.
15// This is not necessary, but writing accessors is tricky because configs using this
16// internally want to take ownership of each field to avoid unnecessary clones.
17#[derive(Clone)]
18pub struct SharedConfigBuilder {
19    /// The stripe version being used.
20    pub stripe_version: ApiVersion,
21    /// The user-provided part of the user-agent we'll use.
22    pub app_info_str: Option<String>,
23    /// The client id to send requests with.
24    pub client_id: Option<ApplicationId>,
25    /// The account id to send requests with.
26    pub account_id: Option<AccountId>,
27    /// The default request strategy to use.,
28    pub request_strategy: Option<RequestStrategy>,
29    /// The secret key for authorizing requests.
30    pub secret: String,
31    /// The base URL to send requests to.
32    pub api_base: Option<String>,
33}
34
35impl SharedConfigBuilder {
36    /// Create a new `SharedConfigBuilder` with the given secret key.
37    pub fn new(secret: impl Into<String>) -> Self {
38        let secret = secret.into();
39
40        // some basic sanity checks
41        // TODO: maybe a full-blown type here rather than a warning?
42        if secret.trim() != secret || !secret.starts_with("sk_") {
43            tracing::warn!("suspiciously formatted secret key")
44        }
45
46        Self {
47            stripe_version: VERSION,
48            app_info_str: None,
49            client_id: None,
50            account_id: None,
51            request_strategy: None,
52            secret,
53            api_base: None,
54        }
55    }
56
57    /// Set the Stripe client id for the client. This will be sent to stripe
58    /// with the "client-id" header.
59    pub fn client_id(mut self, client_id: ApplicationId) -> Self {
60        self.client_id = Some(client_id);
61        self
62    }
63
64    /// Set the default Stripe account id used for the client. This will be sent to stripe
65    /// with the "stripe-account" header, unless it is overridden when customizing
66    /// a request.
67    pub fn account_id(mut self, account_id: AccountId) -> Self {
68        self.account_id = Some(account_id);
69        self
70    }
71
72    /// Sets the default `RequestStrategy` used when making requests.
73    pub fn request_strategy(mut self, strategy: RequestStrategy) -> Self {
74        self.request_strategy = Some(strategy);
75        self
76    }
77
78    /// Create a new client pointed at a specific URL. This is useful for testing.
79    pub fn url(mut self, url: impl Into<String>) -> Self {
80        self.api_base = Some(url.into());
81        self
82    }
83
84    /// Set the application info for the client.
85    pub fn app_info(
86        mut self,
87        name: impl Into<String>,
88        version: Option<String>,
89        url: Option<String>,
90    ) -> Self {
91        self.app_info_str = Some(AppInfo { name: name.into(), url, version }.to_string());
92        self
93    }
94}
95
96struct AppInfo {
97    name: String,
98    url: Option<String>,
99    version: Option<String>,
100}
101
102impl Display for AppInfo {
103    /// Formats a plugin's 'App Info' into a string that can be added to the end of a User-Agent string.
104    ///
105    /// This formatting matches that of other libraries, and if changed then it should be changed everywhere.
106    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
107        let name = &self.name;
108        match (&self.version, &self.url) {
109            (Some(a), Some(b)) => write!(f, "{name}/{a} ({b})"),
110            (Some(a), None) => write!(f, "{name}/{a}"),
111            (None, Some(b)) => write!(f, "{name} ({b})"),
112            _ => f.write_str(name),
113        }
114    }
115}
116
117// Manual implementation so we don't print the secret!
118impl fmt::Debug for SharedConfigBuilder {
119    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
120        let mut builder = f.debug_struct("SharedConfigBuilder");
121        builder.field("request_strategy", &self.request_strategy);
122        builder.field("client_id", &self.client_id);
123        builder.field("account_id", &self.account_id);
124        builder.field("app_info_str", &self.app_info_str);
125        if let Some(api_base) = &self.api_base {
126            builder.field("api_base", api_base);
127        }
128        builder.field("stripe_version", &self.stripe_version);
129        builder.finish()
130    }
131}
132
133/// Per-request configuration overrides.
134#[derive(Debug)]
135#[non_exhaustive]
136pub struct ConfigOverride {
137    /// Use a particular account id, instead of the client default.
138    pub account_id: Option<AccountId>,
139    /// Use a particular `RequestStrategy`, instead of the client default.
140    pub request_strategy: Option<RequestStrategy>,
141}
142
143impl ConfigOverride {
144    pub(crate) fn new() -> Self {
145        Self { account_id: None, request_strategy: None }
146    }
147}