use std::fmt::Display;
use std::marker::PhantomData;
use bytes::Bytes;
use miniserde::json::from_str;
use serde::Serialize;
use crate::request_strategy::RequestStrategy;
use crate::ConfigOverride;
#[derive(Debug, Copy, Clone)]
pub enum PayjpMethod {
Get,
Post,
Delete,
}
pub trait PayjpClientErr {
fn deserialize_err(msg: impl Display) -> Self;
}
pub trait PayjpClient {
type Err: PayjpClientErr;
fn execute(
&self,
req: CustomizedPayjpRequest,
) -> impl std::future::Future<Output = Result<Bytes, Self::Err>>;
}
pub trait BlockingClient {
type Err: PayjpClientErr;
fn execute(&self, req: CustomizedPayjpRequest) -> Result<Bytes, Self::Err>;
}
pub trait PayjpRequest {
type Output;
fn build(&self) -> RequestBuilder;
fn customize(&self) -> CustomizablePayjpRequest<Self::Output> {
CustomizablePayjpRequest::new(self.build())
}
}
#[derive(Debug)]
pub struct CustomizablePayjpRequest<T> {
inner: CustomizedPayjpRequest,
_output: PhantomData<T>,
}
#[derive(Debug)]
pub struct CustomizedPayjpRequest {
request: RequestBuilder,
config_override: ConfigOverride,
}
impl CustomizedPayjpRequest {
pub fn into_pieces(self) -> (RequestBuilder, ConfigOverride) {
(self.request, self.config_override)
}
}
impl<T> CustomizablePayjpRequest<T> {
fn new(req_builder: RequestBuilder) -> Self {
Self {
_output: PhantomData,
inner: CustomizedPayjpRequest {
request: req_builder,
config_override: ConfigOverride::new(),
},
}
}
pub fn request_strategy(mut self, strategy: RequestStrategy) -> Self {
self.inner.config_override.request_strategy = Some(strategy);
self
}
}
impl<T: miniserde::Deserialize> CustomizablePayjpRequest<T> {
pub async fn send<C: PayjpClient>(self, client: &C) -> Result<T, C::Err> {
let bytes = client.execute(self.inner).await?;
deserialize_bytes(bytes)
}
pub fn send_blocking<C: BlockingClient>(self, client: &C) -> Result<T, C::Err> {
let bytes = client.execute(self.inner)?;
deserialize_bytes(bytes)
}
}
fn deserialize_bytes<T: miniserde::Deserialize, Err: PayjpClientErr>(
bytes: Bytes,
) -> Result<T, Err> {
let str = std::str::from_utf8(bytes.as_ref())
.map_err(|_| Err::deserialize_err("Response was not valid UTF-8"))?;
from_str(str).map_err(|_| Err::deserialize_err("error deserializing request data"))
}
#[derive(Debug)]
pub struct RequestBuilder {
pub query: Option<String>,
pub body: Option<String>,
pub path: String,
pub method: PayjpMethod,
}
impl RequestBuilder {
pub fn new(method: PayjpMethod, path: impl Into<String>) -> Self {
Self { path: path.into(), method, query: None, body: None }
}
#[allow(clippy::missing_panics_doc)]
pub fn query<P: Serialize>(mut self, params: &P) -> Self {
self.query = Some(serde_qs::to_string(params).expect("valid serialization"));
self
}
#[allow(clippy::missing_panics_doc)]
pub fn form<F: Serialize>(mut self, form: &F) -> Self {
self.body = Some(serde_qs::to_string(form).expect("valid serialization"));
self
}
pub fn customize<T>(self) -> CustomizablePayjpRequest<T> {
CustomizablePayjpRequest::new(self)
}
}