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}