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