Skip to content

Commit

Permalink
Found a way around Rust compiler bugs rust-lang/rust#100013 and rust-…
Browse files Browse the repository at this point in the history
  • Loading branch information
jarimayenburg committed Sep 28, 2024
1 parent d649ec8 commit f5e7192
Show file tree
Hide file tree
Showing 4 changed files with 141 additions and 32 deletions.
39 changes: 37 additions & 2 deletions crates/fhir-sdk/src/client/r4b/search/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
//! Client search implementation.

use crate::client::search::NextPageCursor;
use crate::client::{search::SearchExecutor, Client, Error, FhirR4B, SearchParameters};
use crate::client::{
search::PagedSearchExecutor, search::UnpagedSearchExecutor, Client, Error, FhirR4B,
SearchParameters,
};
use async_trait::async_trait;
use fhir_model::r4b::resources::{Bundle, DomainResource, NamedResource, Resource};
use paging::{Page, Unpaged};
Expand All @@ -15,10 +18,12 @@ mod paging;
mod params;

#[async_trait]
impl<R> SearchExecutor<R> for Client<FhirR4B>
impl<R> PagedSearchExecutor<R> for Client<FhirR4B>
where
R: NamedResource + DomainResource + TryFrom<Resource> + 'static,
{
type Stream = Page<R>;

#[allow(refining_impl_trait)]
async fn search_paged(
self,
Expand Down Expand Up @@ -56,6 +61,14 @@ where

Ok((page, cursor))
}
}

#[async_trait]
impl<R> UnpagedSearchExecutor<R> for Client<FhirR4B>
where
R: NamedResource + DomainResource + TryFrom<Resource> + 'static,
{
type Stream = Unpaged<R>;

#[allow(refining_impl_trait)]
async fn search_unpaged(self, params: SearchParameters) -> Result<Unpaged<R>, Error> {
Expand All @@ -75,3 +88,25 @@ pub(self) fn find_next_page_url(bundle: &Bundle) -> Option<Result<Url, Error>> {

Some(Url::parse(url_str).map_err(|_| Error::UrlParse(url_str.to_string())))
}

#[cfg(test)]
mod tests {
use fhir_model::r4b::resources::Observation;

use crate::client::{Client, FhirR4B};

/// The search code is prone to run into rustc bugs [rust-lang/rust#100013](https://github.com/rust-lang/rust/issues/100013) and
/// [rust-lang/rust#102211](https://github.com/rust-lang/rust/issues/102211). We implemented a workaround for it.
/// This test is just there to prevent regressions. It doesn't actually test anything, we just need to make sure this compiles
#[allow(dead_code)]
async fn rustc_bug_workaround_inner() {
let client: Client<FhirR4B> = Client::builder().build().unwrap();

fn assert_send<T: Send>(v: T) -> T {
v
}

// We don't actually test anything here, we just need to make sure this compiles
let _ = assert_send(client.search::<Observation>().send());
}
}
43 changes: 39 additions & 4 deletions crates/fhir-sdk/src/client/r5/search/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
//! Client search implementation.

use crate::client::search::NextPageCursor;
use crate::client::{search::SearchExecutor, Client, Error, FhirR5, SearchParameters};
use crate::client::{
search::PagedSearchExecutor, search::UnpagedSearchExecutor, Client, Error, FhirR5,
SearchParameters,
};
use async_trait::async_trait;
use fhir_model::r5::codes::LinkRelationTypes;
use fhir_model::r5::resources::{Bundle, DomainResource, NamedResource, Resource};
Expand All @@ -16,16 +19,18 @@ mod paging;
mod params;

#[async_trait]
impl<R> SearchExecutor<R> for Client<FhirR5>
impl<R> PagedSearchExecutor<R> for Client<FhirR5>
where
R: NamedResource + DomainResource + TryFrom<Resource> + 'static,
{
type Stream = Page<R>;

#[allow(refining_impl_trait)]
async fn search_paged(
self,
params: SearchParameters,
page_size: Option<u32>,
) -> Result<(Page<R>, Option<NextPageCursor<Self, R>>), Error> {
) -> Result<(Self::Stream, Option<NextPageCursor<Self, R>>), Error> {
let mut url = self.url(&[R::TYPE.as_str()]);
url.query_pairs_mut().extend_pairs(params.into_queries()).finish();

Expand All @@ -40,7 +45,7 @@ where
async fn fetch_next_page(
self,
url: Url,
) -> Result<(Page<R>, Option<NextPageCursor<Self, R>>), Error> {
) -> Result<(Self::Stream, Option<NextPageCursor<Self, R>>), Error> {
let searchset: Bundle = self.fetch_resource(url).await?;

let cursor = match find_next_page_url(&searchset) {
Expand All @@ -57,6 +62,14 @@ where

Ok((page, cursor))
}
}

#[async_trait]
impl<R> UnpagedSearchExecutor<R> for Client<FhirR5>
where
R: NamedResource + DomainResource + TryFrom<Resource> + 'static,
{
type Stream = Unpaged<R>;

#[allow(refining_impl_trait)]
async fn search_unpaged(self, params: SearchParameters) -> Result<Unpaged<R>, Error> {
Expand All @@ -80,3 +93,25 @@ pub(self) fn find_next_page_url(bundle: &Bundle) -> Option<Result<Url, Error>> {

Some(Url::parse(url_str).map_err(|_| Error::UrlParse(url_str.to_string())))
}

#[cfg(test)]
mod tests {
use fhir_model::r5::resources::Observation;

use crate::client::{Client, FhirR5};

/// The search code is prone to run into rustc bugs [rust-lang/rust#100013](https://github.com/rust-lang/rust/issues/100013) and
/// [rust-lang/rust#102211](https://github.com/rust-lang/rust/issues/102211). We implemented a workaround for it.
/// This test is just there to prevent regressions. It doesn't actually test anything, we just need to make sure this compiles
#[allow(dead_code)]
async fn rustc_bug_workaround_inner() {
let client: Client<FhirR5> = Client::builder().build().unwrap();

fn assert_send<T: Send>(v: T) -> T {
v
}

// We don't actually test anything here, we just need to make sure this compiles
let _ = assert_send(client.search::<Observation>().send());
}
}
52 changes: 28 additions & 24 deletions crates/fhir-sdk/src/client/search.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ pub struct UnpagedSearch<E, R> {

impl<E, R> UnpagedSearch<E, R>
where
E: SearchExecutor<R> + 'static,
E: UnpagedSearchExecutor<R>,
{
pub fn new(executor: E) -> Self {
Self { executor, params: SearchParameters::empty(), resource_type: PhantomData }
Expand Down Expand Up @@ -65,18 +65,21 @@ where
self.with_raw(key, value)
}

/// Make this a paged search. Next pages can be fetched using
/// [SearchResponse::next_page].
/// Execute the search
pub async fn send(self) -> Result<E::Stream, Error> {
self.executor.search_unpaged(self.params).await
}
}

impl<E, R> UnpagedSearch<E, R>
where
E: UnpagedSearchExecutor<R> + PagedSearchExecutor<R>,
{
pub fn paged(self, page_size: Option<u32>) -> PagedSearch<E, R> {
let Self { executor, params, resource_type } = self;

PagedSearch { executor, params, resource_type, page_size }
}

/// Execute the search
pub async fn send(self) -> Result<impl Stream<Item = Result<R, Error>>, Error> {
self.executor.search_unpaged(self.params).await
}
}

#[derive(Debug)]
Expand All @@ -95,12 +98,10 @@ pub struct PagedSearch<E, R> {

impl<E, R> PagedSearch<E, R>
where
E: SearchExecutor<R> + 'static,
E: PagedSearchExecutor<R>,
{
/// Execute the search
pub async fn send(
self,
) -> Result<(impl Stream<Item = Result<R, Error>>, Option<NextPageCursor<E, R>>), Error> {
pub async fn send(self) -> Result<(E::Stream, Option<NextPageCursor<E, R>>), Error> {
self.executor.search_paged(self.params, self.page_size).await
}
}
Expand All @@ -121,36 +122,38 @@ pub struct NextPageCursor<E, R> {

impl<E, R> NextPageCursor<E, R>
where
E: SearchExecutor<R> + 'static,
E: PagedSearchExecutor<R> + 'static,
{
pub fn new(executor: E, next_page_url: Url) -> Self {
Self { executor, next_page_url, resource_type: PhantomData }
}

pub async fn next_page(
self,
) -> Result<(impl Stream<Item = Result<R, Error>>, Option<Self>), Error> {
pub async fn next_page(self) -> Result<(E::Stream, Option<Self>), Error> {
self.executor.fetch_next_page(self.next_page_url).await
}
}

#[async_trait]
pub trait SearchExecutor<R>: Sized {
async fn search_unpaged(
self,
params: SearchParameters,
) -> Result<impl Stream<Item = Result<R, Error>>, Error>;
pub trait UnpagedSearchExecutor<R>: Sized {
type Stream: Stream<Item = Result<R, Error>>;

async fn search_unpaged(self, params: SearchParameters) -> Result<Self::Stream, Error>;
}

#[async_trait]
pub trait PagedSearchExecutor<R>: Sized {
type Stream: Stream<Item = Result<R, Error>>;

async fn search_paged(
self,
params: SearchParameters,
page_size: Option<u32>,
) -> Result<(impl Stream<Item = Result<R, Error>>, Option<NextPageCursor<Self, R>>), Error>;
) -> Result<(Self::Stream, Option<NextPageCursor<Self, R>>), Error>;

async fn fetch_next_page(
self,
next_page_url: Url,
) -> Result<(impl Stream<Item = Result<R, Error>>, Option<NextPageCursor<Self, R>>), Error>;
) -> Result<(Self::Stream, Option<NextPageCursor<Self, R>>), Error>;
}

impl<V: 'static> Client<V> {
Expand All @@ -159,7 +162,8 @@ impl<V: 'static> Client<V> {
/// matching included resources.
pub fn search<R>(&self) -> UnpagedSearch<Self, R>
where
Self: SearchExecutor<R>,
Self: UnpagedSearchExecutor<R>,
R: Send,
{
UnpagedSearch::new(self.clone())
}
Expand Down
39 changes: 37 additions & 2 deletions crates/fhir-sdk/src/client/stu3/search/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
//! Client search implementation.

use crate::client::search::NextPageCursor;
use crate::client::{search::SearchExecutor, Client, Error, FhirStu3, SearchParameters};
use crate::client::{
search::PagedSearchExecutor, search::UnpagedSearchExecutor, Client, Error, FhirStu3,
SearchParameters,
};
use async_trait::async_trait;
use fhir_model::stu3::resources::{Bundle, DomainResource, NamedResource, Resource};
use paging::{Page, Unpaged};
Expand All @@ -15,10 +18,12 @@ mod paging;
mod params;

#[async_trait]
impl<R> SearchExecutor<R> for Client<FhirStu3>
impl<R> PagedSearchExecutor<R> for Client<FhirStu3>
where
R: NamedResource + DomainResource + TryFrom<Resource> + 'static,
{
type Stream = Page<R>;

#[allow(refining_impl_trait)]
async fn search_paged(
self,
Expand Down Expand Up @@ -56,6 +61,14 @@ where

Ok((page, cursor))
}
}

#[async_trait]
impl<R> UnpagedSearchExecutor<R> for Client<FhirStu3>
where
R: NamedResource + DomainResource + TryFrom<Resource> + 'static,
{
type Stream = Unpaged<R>;

#[allow(refining_impl_trait)]
async fn search_unpaged(self, params: SearchParameters) -> Result<Unpaged<R>, Error> {
Expand All @@ -75,3 +88,25 @@ pub(self) fn find_next_page_url(bundle: &Bundle) -> Option<Result<Url, Error>> {

Some(Url::parse(url_str).map_err(|_| Error::UrlParse(url_str.to_string())))
}

#[cfg(test)]
mod tests {
use fhir_model::stu3::resources::Observation;

use crate::client::{Client, FhirStu3};

/// The search code is prone to run into rustc bugs [rust-lang/rust#100013](https://github.com/rust-lang/rust/issues/100013) and
/// [rust-lang/rust#102211](https://github.com/rust-lang/rust/issues/102211). We implemented a workaround for it.
/// This test is just there to prevent regressions. It doesn't actually test anything, we just need to make sure this compiles
#[allow(dead_code)]
async fn rustc_bug_workaround_inner() {
let client: Client<FhirStu3> = Client::builder().build().unwrap();

fn assert_send<T: Send>(v: T) -> T {
v
}

// We don't actually test anything here, we just need to make sure this compiles
let _ = assert_send(client.search::<Observation>().send());
}
}

0 comments on commit f5e7192

Please sign in to comment.