Created
September 23, 2025 07:10
-
-
Save feliwir/dcc16029240daa1d1a6844a5b62fed7d to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| //! Module for WADO-RS requests | |
| use dicom_object::{FileDicomObject, InMemDicomObject}; | |
| use futures_util::{stream::BoxStream, Stream, StreamExt}; | |
| use rand::{distr::Alphanumeric, Rng}; | |
| use reqwest::Body; | |
| use snafu::ResultExt; | |
| use crate::{DicomWebClient, DicomWebError, RequestFailedSnafu}; | |
| /// A builder type for STOW-RS requests | |
| pub struct WadoStowRequest { | |
| client: DicomWebClient, | |
| url: String, | |
| instances: BoxStream<'static, FileDicomObject<InMemDicomObject>>, | |
| } | |
| impl WadoStowRequest { | |
| fn new(client: DicomWebClient, url: String) -> Self { | |
| WadoStowRequest { | |
| client, | |
| url, | |
| instances: futures_util::stream::empty().boxed(), | |
| } | |
| } | |
| pub fn with_instances( | |
| mut self, | |
| instances: impl Stream<Item = FileDicomObject<InMemDicomObject>> + Send + 'static, | |
| ) -> Self { | |
| self.instances = instances.boxed(); | |
| self | |
| } | |
| pub async fn run(self) -> Result<(), DicomWebError> { | |
| let mut request = self.client.client.post(&self.url); | |
| // Basic authentication | |
| if let Some(username) = &self.client.username { | |
| request = request.basic_auth(username, self.client.password.as_ref()); | |
| } | |
| // Bearer token | |
| else if let Some(bearer_token) = &self.client.bearer_token { | |
| request = request.bearer_auth(bearer_token); | |
| } | |
| let boundary: String = rand::rng() | |
| .sample_iter(&Alphanumeric) | |
| .take(8) | |
| .map(char::from) | |
| .collect(); | |
| let request = request.header( | |
| "Content-Type", | |
| format!( | |
| "multipart/related; type=\"application/dicom\"; boundary={}", | |
| boundary | |
| ), | |
| ); | |
| let boundary_clone = boundary.clone(); | |
| // Convert each instance to a multipart item | |
| let multipart_stream = self.instances.map(move |instance| { | |
| let mut multipart_item = Vec::new(); | |
| let mut buffer = Vec::new(); | |
| instance.clone().write_all(&mut buffer).unwrap(); | |
| multipart_item.extend_from_slice(b"--"); | |
| multipart_item.extend_from_slice(boundary.as_bytes()); | |
| multipart_item.extend_from_slice(b"\r\n"); | |
| multipart_item.extend_from_slice(b"Content-Type: application/dicom\r\n\r\n"); | |
| multipart_item.extend_from_slice(&buffer); | |
| multipart_item.extend_from_slice(b"\r\n"); | |
| Ok::<_, std::io::Error>(multipart_item) | |
| }); | |
| // Write the final boundary | |
| let multipart_stream = multipart_stream.chain(futures_util::stream::once(async move { | |
| Ok(format!("--{}--\r\n", boundary_clone).into_bytes()) | |
| })); | |
| let response = request | |
| .body(Body::wrap_stream(multipart_stream)) | |
| .send() | |
| .await | |
| .context(RequestFailedSnafu { url: &self.url })?; | |
| if !response.status().is_success() { | |
| return Err(DicomWebError::HttpStatusFailure { | |
| status_code: response.status(), | |
| }); | |
| } | |
| Ok(()) | |
| } | |
| } | |
| impl DicomWebClient { | |
| /// Create a STOW-RS request to store instances | |
| pub fn store_instances(&self) -> WadoStowRequest { | |
| let url = format!("{}/studies", self.stow_url); | |
| WadoStowRequest::new(self.clone(), url) | |
| } | |
| /// Create a WADO-RS request to retrieve the metadata of a specific study | |
| pub fn store_instances_in_study(&self, study_instance_uid: &str) -> WadoStowRequest { | |
| let url = format!("{}/studies/{}", self.stow_url, study_instance_uid); | |
| WadoStowRequest::new(self.clone(), url) | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| error[E0521]: borrowed data escapes outside of method | |
| --> src/stow.rs:90:19 | |
| | | |
| 18 | impl<'a> WadoStowRequest<'a> { | |
| | -- lifetime `'a` defined here | |
| ... | |
| 44 | pub async fn run(self) -> Result<(), DicomWebError> { | |
| | ---- `self` is a reference that is only valid in the method body | |
| ... | |
| 90 | .body(Body::wrap_stream(multipart_stream)) | |
| | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | |
| | | | |
| | `self` escapes the method body here | |
| | argument requires that `'a` must outlive `'static` | |
| For more information about this error, try `rustc --explain E0521`. | |
| error: could not compile `dicom-web` (lib) due to 1 previous error |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| //! Module for WADO-RS requests | |
| use dicom_object::{FileDicomObject, InMemDicomObject}; | |
| use futures_util::{stream::BoxStream, Stream, StreamExt}; | |
| use rand::{distr::Alphanumeric, Rng}; | |
| use reqwest::Body; | |
| use snafu::ResultExt; | |
| use crate::{DicomWebClient, DicomWebError, RequestFailedSnafu}; | |
| /// A builder type for STOW-RS requests | |
| pub struct WadoStowRequest<'a> { | |
| client: DicomWebClient, | |
| url: String, | |
| instances: BoxStream<'a, Vec<u8>>, | |
| } | |
| impl<'a> WadoStowRequest<'a> { | |
| fn new(client: DicomWebClient, url: String) -> Self { | |
| WadoStowRequest { | |
| client, | |
| url, | |
| instances: futures_util::stream::empty().boxed(), | |
| } | |
| } | |
| pub fn with_data(mut self, data: impl Stream<Item = Vec<u8>> + Send + 'a) -> Self { | |
| self.instances = data.boxed(); | |
| self | |
| } | |
| pub fn with_instances( | |
| mut self, | |
| instances: impl Stream<Item = FileDicomObject<InMemDicomObject>> + Send + 'a | |
| ) -> Self { | |
| self.instances = instances.map(|dcm| { | |
| let mut buffer = Vec::new(); | |
| dcm.write_all(&mut buffer).unwrap(); | |
| buffer | |
| }).boxed(); | |
| self | |
| } | |
| pub async fn run(self) -> Result<(), DicomWebError> { | |
| let mut request = self.client.client.post(&self.url); | |
| // Basic authentication | |
| if let Some(username) = &self.client.username { | |
| request = request.basic_auth(username, self.client.password.as_ref()); | |
| } | |
| // Bearer token | |
| else if let Some(bearer_token) = &self.client.bearer_token { | |
| request = request.bearer_auth(bearer_token); | |
| } | |
| let boundary: String = rand::rng() | |
| .sample_iter(&Alphanumeric) | |
| .take(8) | |
| .map(char::from) | |
| .collect(); | |
| let request = request.header( | |
| "Content-Type", | |
| format!( | |
| "multipart/related; type=\"application/dicom\"; boundary={}", | |
| boundary | |
| ), | |
| ); | |
| let boundary_clone = boundary.clone(); | |
| // Convert each instance to a multipart item | |
| let multipart_stream = self.instances.map(move |buffer| { | |
| let mut multipart_item = Vec::new(); | |
| multipart_item.extend_from_slice(b"--"); | |
| multipart_item.extend_from_slice(boundary.as_bytes()); | |
| multipart_item.extend_from_slice(b"\r\n"); | |
| multipart_item.extend_from_slice(b"Content-Type: application/dicom\r\n\r\n"); | |
| multipart_item.extend_from_slice(&buffer); | |
| multipart_item.extend_from_slice(b"\r\n"); | |
| Ok::<_, std::io::Error>(multipart_item) | |
| }); | |
| // Write the final boundary | |
| let multipart_stream = multipart_stream.chain(futures_util::stream::once(async move { | |
| Ok(format!("--{}--\r\n", boundary_clone).into_bytes()) | |
| })); | |
| let response = request | |
| .body(Body::wrap_stream(multipart_stream)) | |
| .send() | |
| .await | |
| .context(RequestFailedSnafu { url: &self.url })?; | |
| if !response.status().is_success() { | |
| return Err(DicomWebError::HttpStatusFailure { | |
| status_code: response.status(), | |
| }); | |
| } | |
| Ok(()) | |
| } | |
| } | |
| impl DicomWebClient { | |
| /// Create a STOW-RS request to store instances | |
| pub fn store_instances(&self) -> WadoStowRequest { | |
| let url = format!("{}/studies", self.stow_url); | |
| WadoStowRequest::new(self.clone(), url) | |
| } | |
| /// Create a WADO-RS request to retrieve the metadata of a specific study | |
| pub fn store_instances_in_study(&self, study_instance_uid: &str) -> WadoStowRequest { | |
| let url = format!("{}/studies/{}", self.stow_url, study_instance_uid); | |
| WadoStowRequest::new(self.clone(), url) | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment