Skip to content

Commit

Permalink
docs, docs, docs, configuration, clean up generated builders
Browse files Browse the repository at this point in the history
  • Loading branch information
mzeitlin11 committed Apr 13, 2024
1 parent d875b23 commit 3bc8f49
Show file tree
Hide file tree
Showing 636 changed files with 39,242 additions and 14,882 deletions.
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,5 @@ edition = "2021"
serde = { version = ">=1.0.79", features = ["derive"] } # we use `serde(other)` which was introduced in 1.0.79
smol_str = { version = "0.2.0", features = ["serde"] }
miniserde = "0.1.34"
serde_json = "1.0.115"
serde_qs = "0.12.0"
7 changes: 2 additions & 5 deletions async-stripe/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,10 @@ name = "stripe"
[dependencies]
hyper = { version = "0.14.28", default-features = false, features = ["http1", "http2", "client"] }
hyper-tls = { version = "0.5.0" }
stripe_types = { path = "../stripe_types" }
stripe_shared = { path = "../generated/stripe_shared" }
serde = "1.0.197"
serde.workspace = true
thiserror = "1.0.58"
serde_qs = "0.12.0"
serde_path_to_error = "0.1.16"
serde_json = "1.0.115"
miniserde.workspace = true
stripe_client_core = { path = "../stripe_client_core" }
http = "1.1.0"
tokio = { version = "1.24.1", features = ["rt", "macros"] }
Expand Down
13 changes: 3 additions & 10 deletions async-stripe/src/blocking.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
use std::fmt::Display;
use std::{sync::Arc, time::Duration};

use serde::de::DeserializeOwned;
use stripe_client_core::{CustomizedStripeRequest, StripeBlockingClient, StripeClient};

use crate::error::StripeError;
Expand All @@ -16,16 +14,11 @@ pub struct Client {

impl Client {
/// Creates a new client pointed to `https://api.stripe.com/`
pub fn new(secret_key: impl Display) -> Client {
pub fn new(secret_key: impl Into<String>) -> Client {
Client::from_async(crate::Client::new(secret_key))
}

/// Create a new account pointed at a specific URL. This is useful for testing.
pub fn from_url<'a>(url: impl Into<&'a str>, secret_key: impl Display) -> Self {
Client::from_async(crate::Client::from_url(url, secret_key))
}

fn from_async(inner: crate::Client) -> Client {
pub(crate) fn from_async(inner: crate::Client) -> Client {
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_io()
.enable_time() // use separate `io/time` instead of `all` to ensure `tokio/time` is enabled
Expand All @@ -38,7 +31,7 @@ impl Client {
impl StripeBlockingClient for Client {
type Err = StripeError;

fn execute<T: DeserializeOwned>(
fn execute<T: miniserde::Deserialize>(
&self,
req: CustomizedStripeRequest<T>,
) -> Result<T, Self::Err> {
Expand Down
203 changes: 85 additions & 118 deletions async-stripe/src/client.rs
Original file line number Diff line number Diff line change
@@ -1,26 +1,24 @@
use std::fmt::Display;
use std::str::FromStr;
use std::fmt::Write as _;

use bytes::Bytes;
use http::Uri;
use hyper::client::HttpConnector;
use hyper::header::{AUTHORIZATION, CONTENT_TYPE, USER_AGENT};
use hyper::http::request::Builder;
use hyper::http::HeaderValue;
use hyper::http::{HeaderName, HeaderValue};
use hyper::{Body, Request, StatusCode};
use hyper_tls::HttpsConnector;
use serde::de::DeserializeOwned;
use stripe_client_core::headers::Headers;
use miniserde::json::from_str;
use stripe_client_core::request_strategy::{Outcome, RequestStrategy, StripeStatusCode};
use stripe_client_core::{CustomizedStripeRequest, StripeMethod};
use stripe_shared::ApiErrors;
use stripe_client_core::{ConfigOverride, CustomizedStripeRequest, RequestBuilder, StripeMethod};
use stripe_shared::AccountId;

use crate::config_builder::{ClientConfig, ConfigBuilder};
use crate::StripeError;

/// A client for making Stripe API requests.
pub struct Client {
client: hyper::Client<HttpsConnector<HttpConnector>, Body>,
secret_header_val: HeaderValue,
headers: Headers,
api_base: Uri,
config: ClientConfig,
}

fn connector() -> hyper::Client<HttpsConnector<HttpConnector>, Body> {
Expand All @@ -39,27 +37,61 @@ fn clone_builder(builder: &Builder) -> Builder {
}

impl Client {
fn from_uri_and_key(uri: Uri, secret_key: impl Display) -> Self {
let mut secret_header =
HeaderValue::try_from(format!("Bearer {secret_key}")).expect("invalid secret key");
secret_header.set_sensitive(true);
Self {
client: connector(),
secret_header_val: secret_header,
headers: Headers::default(),
api_base: uri,
}
pub(crate) fn from_config(config: ClientConfig) -> Self {
Self { client: connector(), config }
}

/// Construct a `client` with the given secret key and a default configuration.
pub fn new(secret_key: impl Into<String>) -> Self {
ConfigBuilder::new(secret_key).build().expect("invalid secret provided")
}

pub fn new(secret_key: impl Display) -> Self {
let base = Uri::from_static("https://api.stripe.com/");
Self::from_uri_and_key(base, secret_key)
fn get_account_id_header(
&self,
account_id_override: Option<&AccountId>,
) -> Result<Option<HeaderValue>, StripeError> {
if let Some(overridden) = account_id_override {
return Ok(Some(HeaderValue::from_str(overridden.as_str()).map_err(|_| {
StripeError::ConfigError("invalid account id set in customizations".into())
})?));
}
Ok(self.config.account_id.clone())
}

/// Create a new account pointed at a specific URL. This is useful for testing.
pub fn from_url<'a>(url: impl Into<&'a str>, secret_key: impl Display) -> Self {
let uri = Uri::from_str(url.into()).expect("invalid url provided");
Self::from_uri_and_key(uri, secret_key)
fn construct_request(
&self,
req: RequestBuilder,
config_override: &ConfigOverride,
) -> Result<(Builder, Option<Bytes>), StripeError> {
let mut uri = format!("{}v1/{}", self.config.api_base, req.path.trim_start_matches('/'));
if let Some(query) = req.query {
let _ = write!(uri, "?{query}");
}

let mut builder = Request::builder()
.method(conv_stripe_method(req.method))
.uri(uri)
.header(AUTHORIZATION, self.config.secret.clone())
.header(USER_AGENT, self.config.user_agent.clone())
.header(HeaderName::from_static("stripe-version"), self.config.stripe_version.clone());

if let Some(client_id) = &self.config.client_id {
builder = builder.header(HeaderName::from_static("stripe-account"), client_id.clone());
}
if let Some(account_id) = self.get_account_id_header(config_override.account_id.as_ref())? {
builder = builder.header(HeaderName::from_static("client-id"), account_id);
}

let body = if let Some(body) = req.body {
builder = builder.header(
CONTENT_TYPE,
HeaderValue::from_static("application/x-www-form-urlencoded"),
);
Some(Bytes::from(body))
} else {
None
};
Ok((builder, body))
}

async fn send_inner(
Expand All @@ -74,7 +106,7 @@ impl Client {
let mut last_error = StripeError::ClientError("invalid strategy".into());

if let Some(key) = strategy.get_key() {
req_builder = req_builder.header("Idempotency-Key", key);
req_builder = req_builder.header(HeaderName::from_static("Idempotency-Key"), key);
}

loop {
Expand Down Expand Up @@ -113,10 +145,19 @@ impl Client {
let bytes = hyper::body::to_bytes(response.into_body()).await?;
if !status.is_success() {
tries += 1;
let json_deserializer = &mut serde_json::Deserializer::from_slice(&bytes);
last_error = serde_path_to_error::deserialize(json_deserializer).map(
|e: ApiErrors| StripeError::Stripe(Box::new(e), status.as_u16()),
)?;

let str = std::str::from_utf8(bytes.as_ref()).map_err(|_| {
StripeError::JSONDeserialize("Response was not valid UTF-8".into())
})?;
last_error = from_str(str)
.map(|e: stripe_shared::ApiErrors| {
StripeError::Stripe(Box::new(e), status.as_u16())
})
.map_err(|_| {
StripeError::JSONDeserialize(
"error deserializing request data".into(),
)
})?;
last_status = Some(status);
last_retry_header = retry;
continue;
Expand All @@ -141,91 +182,17 @@ impl stripe_client_core::StripeClient for Client {

async fn execute<T>(&self, req_full: CustomizedStripeRequest<T>) -> Result<T, Self::Err>
where
T: DeserializeOwned,
T: miniserde::Deserialize,
{
let req = req_full.inner;
let mut uri = format!("{}v1/{}", self.api_base, req.path.trim_start_matches('/'));
if let Some(query) = req.query {
uri = format!("{uri}?{query}");
}

let mut builder = Request::builder()
.method(conv_stripe_method(req.method))
.uri(uri)
.header("authorization", self.secret_header_val.clone());

for (key, value) in self.headers.to_array().iter().filter_map(|(k, v)| v.map(|v| (*k, v))) {
builder = builder.header(key, value);
}
let body = if let Some(body) = req.body {
builder = builder.header(
"content-type",
HeaderValue::from_static("application/x-www-form-urlencoded"),
);
Some(Bytes::from(body))
} else {
None
};

let bytes = self.send_inner(body, builder, req_full.request_strategy).await?;
let json_deserializer = &mut serde_json::Deserializer::from_slice(&bytes);
Ok(serde_path_to_error::deserialize(json_deserializer)?)
}
}

#[cfg(test)]
mod tests {
use serde::{Deserialize, Serialize};
use stripe_client_core::{RequestBuilder, StripeClient, StripeMethod, StripeRequest};

use crate::Client;

#[derive(Serialize)]
struct Inner<'a> {
pub param: &'a str,
pub opt: Option<&'a str>,
}

struct RetrieveAccount<'a> {
inner: Inner<'a>,
id: String,
}

#[derive(Deserialize, Debug)]
struct Account {
pub acct: String,
}

impl<'a> RetrieveAccount<'a> {
pub fn new(id: String, param: &'a str) -> Self {
Self { id, inner: Inner { param, opt: None } }
}
}

impl StripeRequest for RetrieveAccount<'_> {
type Output = Account;

fn build(&self) -> RequestBuilder {
RequestBuilder::new(StripeMethod::Get, format!("/hi/{}", self.id)).query(&self.inner)
}
}

impl RetrieveAccount<'_> {
pub async fn send<C: StripeClient>(
&self,
client: &C,
) -> Result<<Self as StripeRequest>::Output, C::Err> {
client.execute(self.customize()).await
}
}

#[tokio::test]
async fn simple_req() {
let client = Client::new("secret");
let ret = RetrieveAccount::new("id".into(), "param").send(&client).await.unwrap();
println!("{ret:?}");

let ret2 =
RetrieveAccount::new("id".into(), "param").customize().send(&client).await.unwrap();
let (builder, body) = self.construct_request(req_full.inner, &req_full.config_override)?;
let request_strategy = req_full
.config_override
.request_strategy
.unwrap_or_else(|| self.config.request_strategy.clone());
let bytes = self.send_inner(body, builder, request_strategy).await?;
let str = std::str::from_utf8(bytes.as_ref())
.map_err(|_| StripeError::JSONDeserialize("Response was not valid UTF-8".into()))?;
from_str(str)
.map_err(|_| StripeError::JSONDeserialize("error deserializing request data".into()))
}
}
Loading

0 comments on commit 3bc8f49

Please sign in to comment.