-
Notifications
You must be signed in to change notification settings - Fork 24
Description
I'm working on an API client that's implemented (essentially) as a newtype wrapper around a reqwest::Client. For convenience, I'd like to mirror reqwest's API as much as possible (no point in reinventing the wheel, right?) so I've also included a newtype wrapper for reqwest::ClientBuilder as well.
The client and builder wrappers basically look like this:
ClientBuilder
#[derive(Debug, Default)]
pub struct APIClientBuilder {
api_key: Option<reqwest::header::HeaderValue>,
builder: reqwest::ClientBuilder,
}
impl Deref for APIClientBuilder {
type Target = reqwest::ClientBuilder;
fn deref(&self) -> &Self::Target {
&self.builder
}
}
impl APIClientBuilder {
pub fn new() -> Self {
// ...
}
pub fn build(self) -> Result<APIClient, crate::errors::ClientError> {
// ...
}
/// Set the API key to use for authenticating requests
pub fn api_key<ApiKey: ToString>(mut self, api_key: ApiKey) -> Self {
// ...
}
}APIClient
#[derive(Debug)]
pub struct APIClient(reqwest::Client);
impl Deref for APIClient {
type Target = reqwest::Client;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl APIClient {
/// Create a new [`APIClient`][APIClient] instance
pub fn new<ApiKey: ToString>(api_key: ApiKey) -> Result<Self, crate::errors::APIClientError> {
Self::builder() // -> APIClientBuilder // this is fine
.api_key(api_key) // -> APIClientBuilder // this is *also* fine
.cookie_store(true) // -> reqwest::ClientBuilder // problems start here
.timeout(Duration::from_secs(360)) // -> reqwest::ClientBuilder
.connect_timeout(Duration::from_secs(360)) // -> reqwest::ClientBuilder
.build() // -> reqwest::Client // this *should* be crate::client::APIClient
}
}I've annotated the code block above to illustrate the issue - the return type of the builder methods changes depending on whether or not they're "overridden" by the wrapper type.
My question is - would it be possible to instruct delegate to "capture" (re-assign) the return value of delegated calls back to the delegated field? At the moment, doing:
// ...
delegate! {
to self.builder {
pub fn timeout(mut self, timeout: Duration) -> APIClientBuilder;
pub fn cookie_store(mut self, enable: bool) -> APIClientBuilder;
pub fn connect_timeout(mut self, timeout: Duration) -> APIClientBuilder;
}
}
// ...causes delegate to generate:
// ...
#[inline(always)]
pub fn timeout(mut self, timeout: Duration) -> APIClientBuilder { // incorrect return type
self.builder.timeout(timeout)
}
#[inline(always)]
pub fn cookie_store(mut self, enable: bool) -> APIClientBuilder { // incorrect return type
self.builder.cookie_store(enable)
}
#[inline(always)]
pub fn connect_timeout(mut self, timeout: Duration) -> APIClientBuilder { // incorrect return type
self.builder.connect_timeout(timeout)
}
// ...Ideally (and with the correct attribute / annotation presumably) though, it would generate:
// ...
#[inline(always)]
pub fn timeout(mut self, timeout: Duration) -> APIClientBuilder {
self.builder = self.builder.timeout(timeout);
self
}
#[inline(always)]
pub fn cookie_store(mut self, enable: bool) -> APIClientBuilder {
self.builder = self.builder.cookie_store(enable);
self
}
#[inline(always)]
pub fn connect_timeout(mut self, timeout: Duration) -> APIClientBuilder {
self.builder = self.builder.connect_timeout(timeout);
self
}
// ...