wyyerd / stripe-rs

Rust API bindings for the Stripe HTTP API.
Apache License 2.0
224 stars 88 forks source link

Any plans to expose an async interface? #24

Closed thedodd closed 5 years ago

thedodd commented 5 years ago

I would totally use this lib, except that I need an async interface. For now, I've just built a light wrapper around the reqwest::async::Client and implemented the logic I need. I would definitely love to get rid of that code and just use this though.

Anyway, I've had a lot of success so far with the reqwest async system. Works really well.

Thanks for all the work so far!

kestred commented 5 years ago

Yeah! I think there are definitely plans to expose an async interface (we'll be needing it ourselves in a month or two 😆 ). Originally I was waiting for the tokio/futures situation to stabilize, but since we've now switched to using reqwest instead of hyper, I think we can just begin providing the async client directly (like you've pointed out)-- perhaps via a stripe::async::Client?

There is a bit of design work to decide how we want to handle traits and apis to support both sync and async in the same crate (my ideal scenario).

It'd probably be nice if a library user could (for example) do both of:

stripe::Customer::create(my_blocking_client, ...)
stripe::Customer::create(my_async_client, ...)

That could be handled by parameterizing all Resource::action functions to look something like:

impl Customer {
    pub fn create<C>(client: &C, params: CustomerParams) -> C::Response<Customer>
        where C: RequestSender
    {
        client.post("/customers", params)
    }
}

It would rely on some trait implemented for both stripe::Client and stripe::async::Client:

trait RequestSender {
    type Response<T>;

    pub fn get<T>(&self, path: &str) -> Self::Response<T>
        where T: serde::de::DeserializeOwned;

    pub fn post<T, F>(&self, path: &str, form: F) -> Self::Response<T>
        where T: serde::de::DeserializeOwned,
              F: serde::Serialize;
}

impl RequestSender for Client {
    type Response<T> = Result<T, Error>;

    // ...
}

impl RequestSender for async::Client {
    type Response<T> = impl Future<Item=T, Error=Error>;  // This may need to be `Box<dyn Future<..>>` or some concrete future instead ¯\_(ツ)_/¯.

    // ...
}

A pull request implementing this would definitely be appreciated!

thedodd commented 5 years ago

@kestred thanks for the quick response. Yea, I would love to be able to hack on that. For better or for worse, I just implemented a lightweight interface on top of reqwest::async for the items that I needed. Worked out pretty well, but next time I need to touch that code, I'll do my best to take a look at the code here and see if I can put together a PR which satisfies your above description.

At a minimum, I'll ping you for feedback if get a PR up first. If you beat me too it, def ping me. I'm happy to do some code review as well.

agrinman commented 5 years ago

@thedodd did you happen to publish your changes with the light async wrapper somewhere? I started down the rabbit hole of converting this lib to use Futures everywhere but I also need just a few API calls at the moment.

thedodd commented 5 years ago

@agrinman negative. It is part of one of my closed source projects (part of the DocQL.io infrastructure).

If you have a PR, I’m happy to open a PR against yours with the bits that I need.

Just let me know. I’m pretty time crunched for the next few weeks.

kestred commented 5 years ago

@thedodd / @agrinman - I've implemented an async client in PR https://github.com/wyyerd/stripe-rs/pull/40; a new example in the examples folder checks that it compiles, but I haven't personally tested it. Should work fine since its just using reqwest's async behavior...

agrinman commented 5 years ago

@kestred awesome, I'll take a look at it when I can. For now I've also just converted the subset I need to be async.

kestred commented 5 years ago

These changes were released in v0.9.0.

To switch to async, in your Cargo.toml use:

stripe-rust = { version = "0.9.0", features = ["async"] }

When creating your client use:

// For older projects
let client = stripe::async::Client::new();  // instead of `stripe::Client`

// For Rust 2018
let client = stripe::r#async::Client::new();

As is the same with other async clients, futures normally need to be run inside a tokio runtime or driven to completion by spawn.

Happy billing :D!

daniel-abramov commented 5 years ago

Should this issue be closed? It seems that the async is indeed on crates.io already.

thedodd commented 5 years ago

Sounds good to me.