payjp_client_core/
request.rs

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