Hi,
I'm trying to build a client crate, using a builder pattern. The API I'm targeting supports multiple types of authentication so I thought I'd make this a trait. Also not all requests will require authentication so adding it as part of the builder makes sense.
I'd like to use it something like this:
let client = Client::new(...).with_authentication(...);
let result = client.perform_request().await;
I also want the trait method authenticate
to be async, as I understand it this can be done by returning a Pinned Future.
I get a chain of errors regarding lifetimes, I tried following the (friendly) compiler messages to resolve the issue but I can't seem to get it right. Partly because I don't fully understand the Pinned Future result. Can anyone explain why this happening and suggestions on how to fix it? (This is a mini/dummy version of the real crate so it may have syntax errors hidden behind the lifetime error)
use core::pin::Pin;
use core::future::Future;
/// Example Client
/// Client should own the authentication
struct Client<'a> {
url: String,
authentication: Option<Box<dyn Authentication + 'a>>
}
/// Client can be constructed with `Client::new(...)` and,
/// if authenticated requests are required, `.with_authentication(...)`
impl<'a> Client<'a> {
/// Create new Client
pub fn new(url: &str) -> Self {
Client {
url: url.into(),
authentication: None,
}
}
// Add authentication to existing client
pub fn with_authentication(mut self, authentication: impl Authentication + 'a) -> Self {
self.authentication = Some(Box::new(authentication));
self
}
/// Dummy method that requires authentication
pub async fn dummy_request(&self) -> String {
let auth_value = self.authentication.as_ref().unwrap().authenticate(self).await;
// Perform the actual request using auth from above
"Success".into()
}
}
trait Authentication {
// Use a Pinned Future to allow async trait
fn authenticate(&self, client: &Client) -> Pin<Box<dyn Future<Output = String> + '_>>;
}
// Dummy Authentication implementation
struct DummyAuthentication;
impl DummyAuthentication {
async fn one_way_to_authenticate(&self, client: &Client<'_>) -> Result<String, ()> {
println!("DummyAuthentication.one_way_to_authenticate() - this would do something async (with .await)");
Ok("Bearer alksdjalsjdao4234saijoj".into())
}
}
/// Implement the trait
impl Authentication for DummyAuthentication {
fn authenticate(&self, client: &Client) -> Pin<Box<dyn Future<Output = String> + '_>> {
Box::pin(async move {
let response = self.one_way_to_authenticate(client).await.unwrap();
response
})
}
}
fn main() {
let dummy_auth = DummyAuthentication;
let c = Client::new("www.example.com").with_authentication(dummy_auth);
let res = c.dummy_request();
}
Errors:
Compiling playground v0.0.1 (/playground)
error[E0623]: lifetime mismatch
--> src/main.rs:55:9
|
54 | fn authenticate(&self, client: &Client) -> Pin<Box<dyn Future<Output = String> + '_>> {
| ------- ------------------------------------------
| |
| this parameter and the return type are declared with different lifetimes...
55 | / Box::pin(async move {
56 | | let response = self.one_way_to_authenticate(client).await.unwrap();
57 | | response
58 | | })
| |__________^ ...but data from `client` is returned here
error: aborting due to previous error
error: could not compile `playground`.
To learn more, run the command again with --verbose.