Skip to main content

stripe_client_core/
stripe_request.rs

1use std::fmt::Display;
2use std::marker::PhantomData;
3use std::time::Duration;
4
5use bytes::Bytes;
6use miniserde::json::from_str;
7use serde::Serialize;
8use stripe_shared::AccountId;
9
10use crate::ConfigOverride;
11use crate::request_strategy::RequestStrategy;
12
13/// REST API methods used by Stripe.
14#[derive(Debug, Copy, Clone)]
15pub enum StripeMethod {
16    /// GET
17    Get,
18    /// POST
19    Post,
20    /// DELETE
21    Delete,
22}
23
24/// This trait allows clients implementing `StripeClient` to define their own error type,
25/// while ensuring they can receive deserialization errors.
26pub trait StripeClientErr {
27    /// Raised when we cannot deserialize the bytes received from a request into the
28    /// specified type.
29    fn deserialize_err(msg: impl Display) -> Self;
30}
31
32/// An abstraction for defining HTTP clients capable of making Stripe API requests compatible
33/// with request information generated in the stripe request crates.
34pub trait StripeClient {
35    /// The error returned, either if the request failed due to an issue with client
36    /// communicating with Stripe, or a client error returned by the Stripe API.
37    type Err: StripeClientErr;
38
39    /// Make the API call.
40    ///
41    /// # Errors
42    /// If the request failed due to an issue with client communication with Stripe,
43    /// or a client error returned by the Stripe API.
44    fn execute(
45        &self,
46        req: CustomizedStripeRequest,
47    ) -> impl std::future::Future<Output = Result<Bytes, Self::Err>> + Send;
48}
49
50/// An abstraction for defining HTTP clients capable of making blocking Stripe API requests compatible
51/// with request information generated in the stripe request crates.
52pub trait StripeBlockingClient {
53    /// The error returned, either if the request failed due to an issue with client
54    /// communicating with Stripe, or a client error returned by the Stripe API.
55    type Err: StripeClientErr;
56
57    /// Make a blocking API call.
58    ///
59    /// # Errors
60    /// If the request failed due to an issue with client communication with Stripe,
61    /// or a client error returned by the Stripe API.
62    fn execute(&self, req: CustomizedStripeRequest) -> Result<Bytes, Self::Err>;
63}
64
65/// Define how to convert structs into the data required to make a specific Stripe API call.
66pub trait StripeRequest {
67    /// The data returned from the eventual API call.
68    type Output;
69
70    /// Convert the struct into library-agnostic data that can be used by compatible
71    /// clients to make API calls.
72    fn build(&self) -> RequestBuilder;
73
74    /// Convert to a builder allowing per-request customization.
75    fn customize(&self) -> CustomizableStripeRequest<Self::Output> {
76        CustomizableStripeRequest::new(self.build())
77    }
78}
79
80/// A `CustomizableStripeRequest` allows for configuring per-request behavior that overrides
81/// default configuration values.
82#[derive(Debug)]
83pub struct CustomizableStripeRequest<T> {
84    inner: CustomizedStripeRequest,
85    _output: PhantomData<T>,
86}
87
88/// The request specification used by a compatible Stripe client to make a request.
89#[derive(Debug)]
90pub struct CustomizedStripeRequest {
91    request: RequestBuilder,
92    config_override: ConfigOverride,
93}
94
95impl CustomizedStripeRequest {
96    /// Split the request specification into the request itself and any configuration override.
97    pub fn into_pieces(self) -> (RequestBuilder, ConfigOverride) {
98        (self.request, self.config_override)
99    }
100}
101
102impl<T> CustomizableStripeRequest<T> {
103    fn new(req_builder: RequestBuilder) -> Self {
104        Self {
105            _output: PhantomData,
106            inner: CustomizedStripeRequest {
107                request: req_builder,
108                config_override: ConfigOverride::new(),
109            },
110        }
111    }
112
113    /// Set a strategy to use for this request, overriding the default set
114    /// during configuration.
115    pub fn request_strategy(mut self, strategy: RequestStrategy) -> Self {
116        self.inner.config_override.request_strategy = Some(strategy);
117        self
118    }
119
120    /// Set an account id to use for this request, overriding the default set
121    /// during configuration.
122    pub fn account_id(mut self, account_id: AccountId) -> Self {
123        self.inner.config_override.account_id = Some(account_id);
124        self
125    }
126
127    /// Set a per-attempt timeout to use for this request, overriding the
128    /// default set during configuration. The timeout applies to each
129    /// individual HTTP attempt; the request strategy may still retry a
130    /// timed-out attempt.
131    pub fn timeout(mut self, timeout: Duration) -> Self {
132        self.inner.config_override.timeout = Some(timeout);
133        self
134    }
135}
136
137impl<T: miniserde::Deserialize> CustomizableStripeRequest<T> {
138    /// Sends the request and returns the response.
139    ///
140    /// # Errors
141    /// If the request failed due to an issue with client communication with Stripe,
142    /// or a client error returned by the Stripe API.
143    pub async fn send<C: StripeClient>(self, client: &C) -> Result<T, C::Err> {
144        let bytes = client.execute(self.inner).await?;
145        deserialize_bytes(bytes)
146    }
147
148    /// Sends the request, blocking the main thread until the response is returned.
149    ///
150    /// # Errors
151    /// If the request failed due to an issue with client communication with Stripe,
152    /// or a client error returned by the Stripe API.
153    pub fn send_blocking<C: StripeBlockingClient>(self, client: &C) -> Result<T, C::Err> {
154        let bytes = client.execute(self.inner)?;
155        deserialize_bytes(bytes)
156    }
157}
158
159fn deserialize_bytes<T: miniserde::Deserialize, Err: StripeClientErr>(
160    bytes: Bytes,
161) -> Result<T, Err> {
162    let str = std::str::from_utf8(bytes.as_ref())
163        .map_err(|_| Err::deserialize_err("Response was not valid UTF-8"))?;
164    from_str(str).map_err(|_| Err::deserialize_err("error deserializing request data"))
165}
166
167/// A builder for specifying the possible pieces of a Stripe API request.
168#[derive(Debug)]
169pub struct RequestBuilder {
170    /// The current query string to use, if provided.
171    pub query: Option<String>,
172    /// The current form-encoded body to send, if provided.
173    pub body: Option<String>,
174    /// The API endpoint to send the request to.
175    pub path: String,
176    /// The method type.
177    pub method: StripeMethod,
178}
179
180impl RequestBuilder {
181    /// Construct a new `RequestBuilder`.
182    pub fn new(method: StripeMethod, path: impl Into<String>) -> Self {
183        Self { path: path.into(), method, query: None, body: None }
184    }
185
186    /// Set a query by serializing the params.
187    #[allow(clippy::missing_panics_doc)]
188    pub fn query<P: Serialize>(mut self, params: &P) -> Self {
189        self.query = Some(serde_qs::to_string(params).expect("valid serialization"));
190        self
191    }
192
193    /// Construct a serialized, form-encoded body.
194    #[allow(clippy::missing_panics_doc)]
195    pub fn form<F: Serialize>(mut self, form: &F) -> Self {
196        self.body = Some(serde_qs::to_string(form).expect("valid serialization"));
197        self
198    }
199
200    /// Convert this request into a `CustomizableStripeRequest` to allow per-request
201    /// customization.
202    pub fn customize<T>(self) -> CustomizableStripeRequest<T> {
203        CustomizableStripeRequest::new(self)
204    }
205}