dicom_web/
stow.rs

1//! Module for WADO-RS requests
2use dicom_object::{FileDicomObject, InMemDicomObject};
3
4use rand::{distr::Alphanumeric, Rng};
5use snafu::ResultExt;
6
7use crate::{DicomWebClient, DicomWebError, RequestFailedSnafu};
8
9/// A builder type for STOW-RS requests
10#[derive(Debug, Clone)]
11pub struct WadoStowRequest {
12    client: DicomWebClient,
13    url: String,
14    instances: Vec<FileDicomObject<InMemDicomObject>>,
15}
16
17impl WadoStowRequest {
18    fn new(client: DicomWebClient, url: String) -> Self {
19        WadoStowRequest {
20            client,
21            url,
22            instances: Vec::new(),
23        }
24    }
25
26    pub fn with_instances(mut self, instances: Vec<FileDicomObject<InMemDicomObject>>) -> Self {
27        self.instances = instances;
28        self
29    }
30
31    pub async fn run(&self) -> Result<(), DicomWebError> {
32        let mut request = self.client.client.post(&self.url);
33
34        // Basic authentication
35        if let Some(username) = &self.client.username {
36            request = request.basic_auth(username, self.client.password.as_ref());
37        }
38        // Bearer token
39        else if let Some(bearer_token) = &self.client.bearer_token {
40            request = request.bearer_auth(bearer_token);
41        }
42
43        let boundary: String = rand::rng()
44            .sample_iter(&Alphanumeric)
45            .take(8)
46            .map(char::from)
47            .collect();
48
49        let mut multipart_buffer = vec![];
50        for instance in &self.instances {
51            let mut buffer = Vec::new();
52            instance.write_all(&mut buffer).unwrap();
53            multipart_buffer.extend_from_slice(b"--");
54            multipart_buffer.extend_from_slice(boundary.as_bytes());
55            multipart_buffer.extend_from_slice(b"\r\n");
56            multipart_buffer.extend_from_slice(b"Content-Type: application/dicom\r\n\r\n");
57
58            multipart_buffer.extend_from_slice(&buffer);
59            multipart_buffer.extend_from_slice(b"\r\n");
60        }
61        // Write the final boundary
62        multipart_buffer.extend_from_slice(b"--");
63        multipart_buffer.extend_from_slice(boundary.as_bytes());
64        multipart_buffer.extend_from_slice(b"--\r\n");
65
66        let response = request
67            .header(
68                "Content-Type",
69                format!("multipart/related; boundary={}", boundary),
70            )
71            .body(multipart_buffer)
72            .send()
73            .await
74            .context(RequestFailedSnafu { url: &self.url })?;
75
76        if !response.status().is_success() {
77            return Err(DicomWebError::HttpStatusFailure {
78                status_code: response.status(),
79            });
80        }
81
82        Ok(())
83    }
84}
85
86impl DicomWebClient {
87    /// Create a STOW-RS request to store instances
88    pub fn store_instances(&self) -> WadoStowRequest {
89        let url = format!("{}/studies", self.stow_url);
90        WadoStowRequest::new(self.clone(), url)
91    }
92
93    /// Create a WADO-RS request to retrieve the metadata of a specific study
94    pub fn store_instances_in_study(&self, study_instance_uid: &str) -> WadoStowRequest {
95        let url = format!("{}/studies/{}", self.stow_url, study_instance_uid);
96        WadoStowRequest::new(self.clone(), url)
97    }
98}