Skip to content

Instantly share code, notes, and snippets.

@feliwir
Created September 23, 2025 07:10
Show Gist options
  • Select an option

  • Save feliwir/dcc16029240daa1d1a6844a5b62fed7d to your computer and use it in GitHub Desktop.

Select an option

Save feliwir/dcc16029240daa1d1a6844a5b62fed7d to your computer and use it in GitHub Desktop.
//! 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)
}
}
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
//! 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