diff --git a/crates/fhir-sdk/src/client/r4b/search/mod.rs b/crates/fhir-sdk/src/client/r4b/search/mod.rs index 141705b8..752ee991 100644 --- a/crates/fhir-sdk/src/client/r4b/search/mod.rs +++ b/crates/fhir-sdk/src/client/r4b/search/mod.rs @@ -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}; @@ -15,10 +18,12 @@ mod paging; mod params; #[async_trait] -impl SearchExecutor for Client +impl PagedSearchExecutor for Client where R: NamedResource + DomainResource + TryFrom + 'static, { + type Stream = Page; + #[allow(refining_impl_trait)] async fn search_paged( self, @@ -56,6 +61,14 @@ where Ok((page, cursor)) } +} + +#[async_trait] +impl UnpagedSearchExecutor for Client +where + R: NamedResource + DomainResource + TryFrom + 'static, +{ + type Stream = Unpaged; #[allow(refining_impl_trait)] async fn search_unpaged(self, params: SearchParameters) -> Result, Error> { @@ -75,3 +88,25 @@ pub(self) fn find_next_page_url(bundle: &Bundle) -> Option> { 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 = Client::builder().build().unwrap(); + + fn assert_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::().send()); + } +} diff --git a/crates/fhir-sdk/src/client/r5/search/mod.rs b/crates/fhir-sdk/src/client/r5/search/mod.rs index a36a2893..fb46c39f 100644 --- a/crates/fhir-sdk/src/client/r5/search/mod.rs +++ b/crates/fhir-sdk/src/client/r5/search/mod.rs @@ -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}; @@ -16,16 +19,18 @@ mod paging; mod params; #[async_trait] -impl SearchExecutor for Client +impl PagedSearchExecutor for Client where R: NamedResource + DomainResource + TryFrom + 'static, { + type Stream = Page; + #[allow(refining_impl_trait)] async fn search_paged( self, params: SearchParameters, page_size: Option, - ) -> Result<(Page, Option>), Error> { + ) -> Result<(Self::Stream, Option>), Error> { let mut url = self.url(&[R::TYPE.as_str()]); url.query_pairs_mut().extend_pairs(params.into_queries()).finish(); @@ -40,7 +45,7 @@ where async fn fetch_next_page( self, url: Url, - ) -> Result<(Page, Option>), Error> { + ) -> Result<(Self::Stream, Option>), Error> { let searchset: Bundle = self.fetch_resource(url).await?; let cursor = match find_next_page_url(&searchset) { @@ -57,6 +62,14 @@ where Ok((page, cursor)) } +} + +#[async_trait] +impl UnpagedSearchExecutor for Client +where + R: NamedResource + DomainResource + TryFrom + 'static, +{ + type Stream = Unpaged; #[allow(refining_impl_trait)] async fn search_unpaged(self, params: SearchParameters) -> Result, Error> { @@ -80,3 +93,25 @@ pub(self) fn find_next_page_url(bundle: &Bundle) -> Option> { 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 = Client::builder().build().unwrap(); + + fn assert_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::().send()); + } +} diff --git a/crates/fhir-sdk/src/client/search.rs b/crates/fhir-sdk/src/client/search.rs index ff6bed41..59512aaf 100644 --- a/crates/fhir-sdk/src/client/search.rs +++ b/crates/fhir-sdk/src/client/search.rs @@ -21,7 +21,7 @@ pub struct UnpagedSearch { impl UnpagedSearch where - E: SearchExecutor + 'static, + E: UnpagedSearchExecutor, { pub fn new(executor: E) -> Self { Self { executor, params: SearchParameters::empty(), resource_type: PhantomData } @@ -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 { + self.executor.search_unpaged(self.params).await + } +} + +impl UnpagedSearch +where + E: UnpagedSearchExecutor + PagedSearchExecutor, +{ pub fn paged(self, page_size: Option) -> PagedSearch { let Self { executor, params, resource_type } = self; PagedSearch { executor, params, resource_type, page_size } } - - /// Execute the search - pub async fn send(self) -> Result>, Error> { - self.executor.search_unpaged(self.params).await - } } #[derive(Debug)] @@ -95,12 +98,10 @@ pub struct PagedSearch { impl PagedSearch where - E: SearchExecutor + 'static, + E: PagedSearchExecutor, { /// Execute the search - pub async fn send( - self, - ) -> Result<(impl Stream>, Option>), Error> { + pub async fn send(self) -> Result<(E::Stream, Option>), Error> { self.executor.search_paged(self.params, self.page_size).await } } @@ -121,36 +122,38 @@ pub struct NextPageCursor { impl NextPageCursor where - E: SearchExecutor + 'static, + E: PagedSearchExecutor + '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>, Option), Error> { + pub async fn next_page(self) -> Result<(E::Stream, Option), Error> { self.executor.fetch_next_page(self.next_page_url).await } } #[async_trait] -pub trait SearchExecutor: Sized { - async fn search_unpaged( - self, - params: SearchParameters, - ) -> Result>, Error>; +pub trait UnpagedSearchExecutor: Sized { + type Stream: Stream>; + + async fn search_unpaged(self, params: SearchParameters) -> Result; +} + +#[async_trait] +pub trait PagedSearchExecutor: Sized { + type Stream: Stream>; async fn search_paged( self, params: SearchParameters, page_size: Option, - ) -> Result<(impl Stream>, Option>), Error>; + ) -> Result<(Self::Stream, Option>), Error>; async fn fetch_next_page( self, next_page_url: Url, - ) -> Result<(impl Stream>, Option>), Error>; + ) -> Result<(Self::Stream, Option>), Error>; } impl Client { @@ -159,7 +162,8 @@ impl Client { /// matching included resources. pub fn search(&self) -> UnpagedSearch where - Self: SearchExecutor, + Self: UnpagedSearchExecutor, + R: Send, { UnpagedSearch::new(self.clone()) } diff --git a/crates/fhir-sdk/src/client/stu3/search/mod.rs b/crates/fhir-sdk/src/client/stu3/search/mod.rs index 361bea18..86835a50 100644 --- a/crates/fhir-sdk/src/client/stu3/search/mod.rs +++ b/crates/fhir-sdk/src/client/stu3/search/mod.rs @@ -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}; @@ -15,10 +18,12 @@ mod paging; mod params; #[async_trait] -impl SearchExecutor for Client +impl PagedSearchExecutor for Client where R: NamedResource + DomainResource + TryFrom + 'static, { + type Stream = Page; + #[allow(refining_impl_trait)] async fn search_paged( self, @@ -56,6 +61,14 @@ where Ok((page, cursor)) } +} + +#[async_trait] +impl UnpagedSearchExecutor for Client +where + R: NamedResource + DomainResource + TryFrom + 'static, +{ + type Stream = Unpaged; #[allow(refining_impl_trait)] async fn search_unpaged(self, params: SearchParameters) -> Result, Error> { @@ -75,3 +88,25 @@ pub(self) fn find_next_page_url(bundle: &Bundle) -> Option> { 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 = Client::builder().build().unwrap(); + + fn assert_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::().send()); + } +}