Skip to content

Assign Return Value Of Delegated Calls To Delegated Field #50

@the-wondersmith

Description

@the-wondersmith

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
}

// ...

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions