Use case: I've got a paginated Graphql API where many different entities return an opaque cursor and a boolean hasNext. I would like to make these entities available as a TryStream to allow computations to happen while all pages are being fetched.
I've defined a trait to abstract that. get_data fetches a single page:
trait PaginatedEntityQuery {
type ResponseData;
fn get_cursor(data: &Self::ResponseData) -> Option<String>;
fn has_next_page(data: &Self::ResponseData) -> bool;
fn get_data<'a>(
&self, // access to query variables
backend: &'a Backend, // access to a backend to get data from
cursor: Option<String>, // the cursor in the pagination
) -> Pin<Box<dyn Future<Output = Result<Self::ResponseData>> + 'a>>;
}
I'm using try_unfold in my Backend impl:
fn get_paginated_entities<'a, T>(
&'a self,
query: &'a TryStream<Ok = T::ResponseData, Error = anyhow::Error> + 'a
where
T: PaginatedEntity
{
try_unfold(StreamState::Start, async move |state| {
// stream state handling code that defines `cursor`
let data = query.get_data(self, cursor).await?
// more data + state computations + return result of `get_data`.
}
}
Now I'd like to define something like
struct SomeEntityQuery { filter: String }
impl PaginatedEntityQuery for SomeEntityQuery { /* … */ }
Finally, I can define a function that uses get_paginated_entities to do the heavy lifting:
fn get_some_entities(filter: String, /* … */) -> impl TryStream<Ok = Vec<SomeOtherType>, Error = anyhow::Error> + 'a {
backend.get_paginated_entities(&SomeEntityQuery { filter }).map_ok /* … */
}
Of course, this doesn't work. I've defined an instance of SomeEntityQuery in get_some_entities but I'm returning owned values. Rust can't know that no parts of SomeEntityQuery actually show up in the return value.
I'd make query: &'a T in get_some_entities owned instead of a ref, but then I move t out in the async move that I give to try_unfold and since it's an FnMut that closure can't own it multiple times anyway. It has query has to be shared. Also, removing the ref form T gets Rust to complain about the type parameter potentially not living long enough.
Is there a way to make sure that values for the query itself aren't required to live as long as the values the query returns? Or possibly to extend the lifetimes of the query? I'm fine with copying the query a lot, if need be. It's lightweight, and performance is network bound.