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}