use dicom_json::DicomJson;
use dicom_object::{FileDicomObject, InMemDicomObject};
use bytes::Bytes;
use futures_util::{Stream, StreamExt, stream::BoxStream};
use multipart_rs::MultipartStreamWriter;
use reqwest::Body;
use snafu::ResultExt;
use crate::{
DeserializationFailedSnafu, DicomWebClient, DicomWebError, RequestFailedSnafu,
apply_auth_and_headers, validate_dicom_json_content_type,
};
type InstanceStream = BoxStream<'static, Result<Bytes, std::io::Error>>;
pub struct StowRequest {
client: DicomWebClient,
url: String,
instances: BoxStream<'static, Result<InstanceStream, std::io::Error>>,
}
impl StowRequest {
fn new(client: DicomWebClient, url: String) -> Self {
StowRequest {
client,
url,
instances: futures_util::stream::empty().boxed(),
}
}
pub fn with_data_streams<S, B>(mut self, instances: S) -> Self
where
S: Stream<Item = B> + Send + 'static,
B: Stream<Item = Result<Bytes, std::io::Error>> + Send + 'static,
{
self.instances = instances.map(|body| Ok(body.boxed())).boxed();
self
}
pub fn with_data(mut self, data: impl Stream<Item = Vec<u8>> + Send + 'static) -> Self {
self.instances = data
.map(|buffer| {
Ok(futures_util::stream::once(async move { Ok(Bytes::from(buffer)) }).boxed())
})
.boxed();
self
}
pub fn with_instances(
mut self,
instances: impl Stream<Item = FileDicomObject<InMemDicomObject>> + Send + 'static,
) -> Self {
self.instances = instances
.map(|instance| {
let mut buffer = Vec::new();
instance.write_all(&mut buffer).map_err(|e| {
std::io::Error::other(format!("Failed to serialize DICOM instance: {}", e))
})?;
Ok(
futures_util::stream::once(async move { Ok(Bytes::from(buffer)) }).boxed()
as InstanceStream,
)
})
.boxed();
self
}
pub async fn run(self) -> Result<InMemDicomObject, DicomWebError> {
let mut request = self.client.client.post(&self.url);
request = apply_auth_and_headers(request, &self.client);
let writer = MultipartStreamWriter::new();
let request = request.header(
"Content-Type",
format!(
"multipart/related; type=\"application/dicom\"; boundary={}",
writer.boundary
),
);
let parts = self.instances.map(|body| {
Ok::<_, std::io::Error>(("Content-Type: application/dicom".to_string(), body?))
});
let multipart_stream = writer.stream(parts);
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(),
});
}
let ct = response
.headers()
.get("Content-Type")
.ok_or(DicomWebError::MissingContentTypeHeader)?;
validate_dicom_json_content_type(ct.to_str().unwrap_or_default())?;
Ok(response
.json::<DicomJson<InMemDicomObject>>()
.await
.context(DeserializationFailedSnafu {})?
.into_inner())
}
}
impl DicomWebClient {
pub fn store_instances(&self) -> StowRequest {
let url = format!("{}/studies", self.stow_url);
StowRequest::new(self.clone(), url)
}
pub fn store_instances_in_study(&self, study_instance_uid: &str) -> StowRequest {
let url = format!("{}/studies/{}", self.stow_url, study_instance_uid);
StowRequest::new(self.clone(), url)
}
}