Skip to content

Commit

Permalink
Implement miniserde-based deserialization
Browse files Browse the repository at this point in the history
Add fixture testing and serialize object keys

Fix object keys in tests
  • Loading branch information
mzeitlin11 committed Apr 8, 2024
1 parent 562c493 commit 4cc2678
Show file tree
Hide file tree
Showing 1,047 changed files with 142,623 additions and 3,537 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/async-stripe.yml
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ jobs:
- uses: Swatinem/rust-cache@v2

- name: Run clippy
run: cargo clippy --features "runtime-${{ matrix.runtime }} full"
run: cargo clippy --features "runtime-${{ matrix.runtime }} full serde"

test:
runs-on: ubuntu-latest
Expand Down Expand Up @@ -131,7 +131,7 @@ jobs:

- uses: taiki-e/install-action@cargo-llvm-cov
- name: Test and gather coverage
run: cargo llvm-cov --lcov --output-path lcov.info --features runtime-${{ matrix.runtime }}
run: cargo llvm-cov --lcov --output-path lcov.info --features "runtime-${{ matrix.runtime }} serde"
- name: Upload to codecov.io
uses: codecov/[email protected]
with:
Expand All @@ -155,7 +155,7 @@ jobs:
- uses: Swatinem/rust-cache@v2

- name: Build Documentation
run: cargo doc --lib --no-deps --features "runtime-tokio-hyper full"
run: cargo doc --lib --no-deps --features "runtime-tokio-hyper full serde"

# Examples tested separately so that we can use crates which don't match our MSRV
examples:
Expand Down
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ edition = "2021"

[workspace.dependencies]
serde = { version = ">=1.0.79", features = ["derive"] } # we use `serde(other)` which was introduced in 1.0.79
serde_json = "1"
http-types = { version = "2.12.0", default-features = false }
smol_str = { version = "0.2.0", features = ["serde"] }
serde_json = "1.0"
miniserde = "0.1.34"
12 changes: 6 additions & 6 deletions async-stripe/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -78,24 +78,24 @@ hyper-rustls-native = ["hyper-rustls", "hyper-rustls/native-tokio"]
hyper-rustls-webpki = ["hyper-rustls", "hyper-rustls/webpki-tokio"]

[dependencies]
stripe_types = {path = "../stripe_types"}
stripe_shared = {path = "../generated/stripe_shared"}
async-std = {version = "1.8,<1.11", optional = true}
stripe_types = { path = "../stripe_types" }
stripe_shared = { path = "../generated/stripe_shared" }
async-std = { version = "1.8,<1.11", optional = true }

thiserror = "1.0.24"
hyper = { version = "0.14", default-features = false, features = ["http1", "http2", "client", "tcp"], optional = true }
hyper-tls = { version = "0.5", optional = true }
hyper-rustls = { version = "0.24", default-features = false, features = ["http1", "http2", "tls12", "logging"], optional = true }
serde_json.workspace = true
serde_qs = "0.12.0"
serde_path_to_error = "0.1.8"
surf = { version = "2.1", optional = true }
tokio = { version = "1.2", optional = true }
uuid = { version = "1.6.1", optional=true, features=["v4"] }
uuid = { version = "1.6.1", optional = true, features = ["v4"] }

serde.workspace = true
serde_json.workspace = true
http-types.workspace = true
smol_str.workspace = true
miniserde.workspace = true

# stream for lists
futures-util = { version = "0.3.21", optional = true }
Expand Down
23 changes: 16 additions & 7 deletions async-stripe/src/client/base/async_std.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::pin::Pin;

use async_std::task::sleep;
use http_types::{Request, StatusCode};
use serde::de::DeserializeOwned;
use miniserde::json::from_str;

use crate::client::request_strategy::{Outcome, RequestStrategy};
use crate::error::StripeError;
Expand All @@ -26,7 +26,7 @@ impl AsyncStdClient {
Self { client: surf::Client::new() }
}

pub fn execute<T: DeserializeOwned + Send + 'static>(
pub fn execute<T: miniserde::Deserialize + Send + 'static>(
&self,
request: Request,
strategy: &RequestStrategy,
Expand All @@ -38,8 +38,11 @@ impl AsyncStdClient {

Box::pin(async move {
let bytes = send_inner(&client, request, &strategy).await?;
let json_deserializer = &mut serde_json::Deserializer::from_slice(&bytes);
serde_path_to_error::deserialize(json_deserializer).map_err(StripeError::from)
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())
})
})
}
}
Expand Down Expand Up @@ -100,10 +103,16 @@ async fn send_inner(

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)
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::Error| StripeError::Stripe(*e.error, status.into()))
.unwrap_or_else(StripeError::from);
.unwrap_or_else(|_| {
StripeError::JSONDeserialize(
"Could not deserialize Stripe error".into(),
)
});
last_status = Some(status);
last_retry_header = retry;

Expand Down
29 changes: 18 additions & 11 deletions async-stripe/src/client/base/tokio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::pin::Pin;
use http_types::{Request, StatusCode};
use hyper::http;
use hyper::{client::HttpConnector, Body};
use serde::de::DeserializeOwned;
use miniserde::json::from_str;
use tokio::time::sleep;

use crate::client::request_strategy::{Outcome, RequestStrategy};
Expand Down Expand Up @@ -77,7 +77,7 @@ impl TokioClient {
}
}

pub fn execute<T: DeserializeOwned + Send + 'static>(
pub fn execute<T: miniserde::Deserialize + Send + 'static>(
&self,
request: Request,
strategy: &RequestStrategy,
Expand All @@ -89,8 +89,11 @@ impl TokioClient {

Box::pin(async move {
let bytes = send_inner(&client, request, &strategy).await?;
let json_deserializer = &mut serde_json::Deserializer::from_slice(&bytes);
serde_path_to_error::deserialize(json_deserializer).map_err(StripeError::from)
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())
})
})
}
}
Expand Down Expand Up @@ -153,12 +156,18 @@ async fn send_inner(

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)
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::Error| {
StripeError::Stripe(*e.error, status.as_u16())
})
.unwrap_or_else(StripeError::from);
.unwrap_or_else(|_| {
StripeError::JSONDeserialize(
"Could not deserialize Stripe error".into(),
)
});
last_status = Some(
// NOTE: StatusCode::from can panic here, so fall back to InternalServerError
// see https://github.com/http-rs/http-types/blob/ac5d645ce5294554b86ebd49233d3ec01665d1d7/src/hyperium_http.rs#L20-L24
Expand Down Expand Up @@ -302,7 +311,7 @@ mod tests {
async fn nice_serde_error() {
use serde::Deserialize;

#[derive(Debug, Deserialize)]
#[derive(Debug, Deserialize, miniserde::Deserialize)]
struct DataType {
// Allowing dead code since used for deserialization
#[allow(dead_code)]
Expand Down Expand Up @@ -333,9 +342,7 @@ mod tests {
mock.assert_hits_async(1).await;

match res {
Err(StripeError::JSONSerialize(err)) => {
println!("Error: {:?} Path: {:?}", err.inner(), err.path().to_string())
}
Err(StripeError::JSONDeserialize(_)) => {}
_ => panic!("Expected stripe error {:?}", res),
}
}
Expand Down
3 changes: 1 addition & 2 deletions async-stripe/src/client/base/tokio_blocking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
use std::{sync::Arc, time::Duration};

use http_types::Request;
use serde::de::DeserializeOwned;

use crate::client::base::tokio::TokioClient;
use crate::client::request_strategy::RequestStrategy;
Expand Down Expand Up @@ -39,7 +38,7 @@ impl TokioBlockingClient {
TokioBlockingClient { inner, runtime: Arc::new(runtime) }
}

pub fn execute<T: DeserializeOwned + Send + 'static>(
pub fn execute<T: miniserde::Deserialize + Send + 'static>(
&self,
request: Request,
strategy: &RequestStrategy,
Expand Down
44 changes: 14 additions & 30 deletions async-stripe/src/client/stripe.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Necessary under tokio-blocking since `Response` is a type alias to a `Result`
#![allow(clippy::missing_errors_doc)]
use http_types::{Body, Method, Request, Url};
use serde::de::DeserializeOwned;
use miniserde::Deserialize;
use serde::Serialize;
use stripe_shared::version::VERSION;

Expand Down Expand Up @@ -81,12 +81,12 @@ impl Client {
}

/// Make a `GET` http request with just a path
pub fn get<T: DeserializeOwned + Send + 'static>(&self, path: &str) -> Response<T> {
pub fn get<T: Deserialize + Send + 'static>(&self, path: &str) -> Response<T> {
self.send(path, Method::Get)
}

/// Make a `GET` http request with url query parameters
pub fn get_query<T: DeserializeOwned + Send + 'static, P: Serialize>(
pub fn get_query<T: Deserialize + Send + 'static, P: Serialize>(
&self,
path: &str,
params: P,
Expand All @@ -98,27 +98,23 @@ impl Client {
self.client.execute::<T>(self.create_request(Method::Get, url), &self.strategy)
}

pub fn send<T: DeserializeOwned + Send + 'static>(
&self,
path: &str,
method: Method,
) -> Response<T> {
pub fn send<T: Deserialize + Send + 'static>(&self, path: &str, method: Method) -> Response<T> {
let url = self.url(path);
self.client.execute::<T>(self.create_request(method, url), &self.strategy)
}

/// Make a `DELETE` http request with just a path
pub fn delete<T: DeserializeOwned + Send + 'static>(&self, path: &str) -> Response<T> {
pub fn delete<T: Deserialize + Send + 'static>(&self, path: &str) -> Response<T> {
self.send(path, Method::Delete)
}

/// Make a `POST` http request with just a path
pub fn post<T: DeserializeOwned + Send + 'static>(&self, path: &str) -> Response<T> {
pub fn post<T: Deserialize + Send + 'static>(&self, path: &str) -> Response<T> {
self.send(path, Method::Post)
}

/// Make a `POST` http request with urlencoded body
pub fn post_form<T: DeserializeOwned + Send + 'static, F: Serialize>(
pub fn post_form<T: Deserialize + Send + 'static, F: Serialize>(
&self,
path: &str,
form: F,
Expand All @@ -127,7 +123,7 @@ impl Client {
}

/// Make a `DELETE` http request with urlencoded body
pub fn delete_form<T: DeserializeOwned + Send + 'static, F: Serialize>(
pub fn delete_form<T: Deserialize + Send + 'static, F: Serialize>(
&self,
path: &str,
form: F,
Expand All @@ -136,7 +132,7 @@ impl Client {
}

/// Make an http request with urlencoded body
pub fn send_form<T: DeserializeOwned + Send + 'static, F: Serialize>(
pub fn send_form<T: Deserialize + Send + 'static, F: Serialize>(
&self,
path: &str,
form: F,
Expand All @@ -145,15 +141,8 @@ impl Client {
let url = self.url(path);
let mut req = self.create_request(method, url);

let mut params_buffer = Vec::new();
let qs_ser = &mut serde_qs::Serializer::new(&mut params_buffer);
if let Err(qs_ser_err) = serde_path_to_error::serialize(&form, qs_ser) {
return err(StripeError::QueryStringSerialize(qs_ser_err));
}

let body =
String::from_utf8(params_buffer).expect("Unable to extract string from params_buffer");

// We control the types here, so this should never fail
let body = serde_qs::to_string(&form).expect("serialization should work");
req.set_body(Body::from_string(body));

req.insert_header("content-type", "application/x-www-form-urlencoded");
Expand All @@ -169,14 +158,9 @@ impl Client {
fn url_with_params<P: Serialize>(&self, path: &str, params: P) -> Result<Url, StripeError> {
let mut url = self.url(path);

let mut params_buffer = Vec::new();
let qs_ser = &mut serde_qs::Serializer::new(&mut params_buffer);
serde_path_to_error::serialize(&params, qs_ser).map_err(StripeError::from)?;

let params =
String::from_utf8(params_buffer).expect("Unable to extract string from params_buffer");

url.set_query(Some(&params));
// We control the types here, so this should never fail
let query = serde_qs::to_string(&params).expect("serialization should work");
url.set_query(Some(&query));
Ok(url)
}

Expand Down
6 changes: 2 additions & 4 deletions async-stripe/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,8 @@ use thiserror::Error;
pub enum StripeError {
#[error("error reported by stripe: {0:#?}, status code: {1}")]
Stripe(ApiErrors, u16),
#[error("error serializing or deserializing a querystring: {0}")]
QueryStringSerialize(#[from] serde_path_to_error::Error<serde_qs::Error>),
#[error("error serializing or deserializing a request")]
JSONSerialize(#[from] serde_path_to_error::Error<serde_json::Error>),
#[error("error deserializing a request: {0}")]
JSONDeserialize(String),
#[error("attempted to access an unsupported version of the api")]
UnsupportedVersion,
#[error("error communicating with stripe: {0}")]
Expand Down
Loading

0 comments on commit 4cc2678

Please sign in to comment.