1use dicom_json::DicomJson;
4use dicom_object::{from_reader, FileDicomObject, InMemDicomObject};
5
6use futures_util::{Stream, StreamExt};
7use multipart_rs::{MultipartItem, MultipartReader, MultipartType};
8use snafu::{OptionExt, ResultExt};
9
10use crate::{
11 apply_auth_and_headers, validate_dicom_json_content_type, validate_multipart_item_content_type,
12 DeserializationFailedSnafu, DicomReaderFailedSnafu, DicomWebClient, DicomWebError,
13 EmptyResponseSnafu, MissingContentTypeHeaderSnafu, MultipartReaderFailedSnafu,
14 RequestFailedSnafu,
15};
16
17#[derive(Debug, Clone)]
19pub struct WadoMetadataRequest {
20 client: DicomWebClient,
21 url: String,
22}
23
24impl WadoMetadataRequest {
25 fn new(client: DicomWebClient, url: String) -> Self {
26 WadoMetadataRequest { client, url }
27 }
28
29 pub async fn run(&self) -> Result<Vec<InMemDicomObject>, DicomWebError> {
30 let mut request = self.client.client.get(&self.url);
31 request = apply_auth_and_headers(request, &self.client);
32
33 let response = request
34 .send()
35 .await
36 .context(RequestFailedSnafu { url: &self.url })?;
37
38 if !response.status().is_success() {
39 return Err(DicomWebError::HttpStatusFailure {
40 status_code: response.status(),
41 });
42 }
43
44 let ct = response
46 .headers()
47 .get("Content-Type")
48 .ok_or(DicomWebError::MissingContentTypeHeader)?;
49 validate_dicom_json_content_type(ct.to_str().unwrap_or_default())?;
50
51 Ok(response
52 .json::<Vec<DicomJson<InMemDicomObject>>>()
53 .await
54 .context(DeserializationFailedSnafu {})?
55 .into_iter()
56 .map(|dj| dj.into_inner())
57 .collect())
58 }
59}
60
61#[derive(Debug, Clone)]
63pub struct WadoFileRequest {
64 client: DicomWebClient,
65 url: String,
66}
67
68impl WadoFileRequest {
69 fn new(client: DicomWebClient, url: String) -> Self {
70 WadoFileRequest { client, url }
71 }
72
73 pub async fn run(
74 self,
75 ) -> Result<
76 impl Stream<Item = Result<FileDicomObject<InMemDicomObject>, DicomWebError>>,
77 DicomWebError,
78 > {
79 let mut request = self.client.client.get(&self.url);
80 request = apply_auth_and_headers(request, &self.client);
81
82 let response = request
83 .send()
84 .await
85 .context(RequestFailedSnafu { url: &self.url })?;
86
87 if !response.status().is_success() {
88 return Err(DicomWebError::HttpStatusFailure {
89 status_code: response.status(),
90 });
91 }
92
93 let headers: Vec<(String, String)> = response
95 .headers()
96 .iter()
97 .map(|(k, v)| (k.to_string(), String::from(v.to_str().unwrap_or_default())))
98 .collect();
99
100 let stream = response.bytes_stream();
101 let reader = MultipartReader::from_stream_with_headers(stream, &headers)
102 .map_err(|source| DicomWebError::MultipartReaderFailed { source })?;
103
104 if reader.multipart_type != MultipartType::Related {
105 return Err(DicomWebError::UnexpectedMultipartType {
106 multipart_type: reader.multipart_type,
107 });
108 }
109
110 Ok(reader.map(|item| {
111 let item = item.context(MultipartReaderFailedSnafu)?;
112 let ct = item
114 .headers
115 .iter()
116 .find(|(k, _)| k.to_lowercase() == "content-type")
117 .map(|(_, v)| v.as_str())
118 .context(MissingContentTypeHeaderSnafu)?;
119 validate_multipart_item_content_type(ct)?;
120 from_reader(&*item.data).context(DicomReaderFailedSnafu)
121 }))
122 }
123}
124
125pub struct WadoSingleFileRequest {
127 request: WadoFileRequest,
128}
129
130impl WadoSingleFileRequest {
131 pub async fn run(self) -> Result<FileDicomObject<InMemDicomObject>, DicomWebError> {
132 let mut stream = self.request.run().await?;
134 stream.next().await.context(EmptyResponseSnafu)?
135 }
136}
137
138pub struct WadoFramesRequest {
140 client: DicomWebClient,
141 url: String,
142}
143
144impl WadoFramesRequest {
145 fn new(client: DicomWebClient, url: String) -> Self {
146 WadoFramesRequest { client, url }
147 }
148
149 pub async fn run(self) -> Result<Vec<MultipartItem>, DicomWebError> {
150 let mut request = self.client.client.get(&self.url);
151 request = apply_auth_and_headers(request, &self.client);
152
153 let response = request
154 .send()
155 .await
156 .context(RequestFailedSnafu { url: &self.url })?;
157
158 if !response.status().is_success() {
159 return Err(DicomWebError::HttpStatusFailure {
160 status_code: response.status(),
161 });
162 }
163
164 let headers: Vec<(String, String)> = response
166 .headers()
167 .iter()
168 .map(|(k, v)| (k.to_string(), String::from(v.to_str().unwrap_or_default())))
169 .collect();
170 let stream = response.bytes_stream();
171 let mut reader = MultipartReader::from_stream_with_headers(stream, &headers)
172 .map_err(|source| DicomWebError::MultipartReaderFailed { source })?;
173
174 if reader.multipart_type != MultipartType::Related {
175 return Err(DicomWebError::UnexpectedMultipartType {
176 multipart_type: reader.multipart_type,
177 });
178 }
179
180 let mut item_list = vec![];
181
182 while let Some(item) = reader.next().await {
183 let item = item.context(MultipartReaderFailedSnafu)?;
184 item_list.push(item);
185 }
186
187 Ok(item_list)
188 }
189}
190
191impl DicomWebClient {
192 pub fn retrieve_study(&self, study_instance_uid: &str) -> WadoFileRequest {
194 let url = format!("{}/studies/{}", self.wado_url, study_instance_uid);
195 WadoFileRequest::new(self.clone(), url)
196 }
197
198 pub fn retrieve_study_metadata(&self, study_instance_uid: &str) -> WadoMetadataRequest {
200 let url = format!("{}/studies/{}/metadata", self.wado_url, study_instance_uid);
201 WadoMetadataRequest::new(self.clone(), url)
202 }
203
204 pub fn retrieve_series(
206 &self,
207 study_instance_uid: &str,
208 series_instance_uid: &str,
209 ) -> WadoFileRequest {
210 let base_url = &self.wado_url;
211 let url = format!("{base_url}/studies/{study_instance_uid}/series/{series_instance_uid}",);
212 WadoFileRequest::new(self.clone(), url)
213 }
214
215 pub fn retrieve_series_metadata(
217 &self,
218 study_instance_uid: &str,
219 series_instance_uid: &str,
220 ) -> WadoMetadataRequest {
221 let base_url = &self.wado_url;
222 let url = format!(
223 "{base_url}/studies/{study_instance_uid}/series/{series_instance_uid}/metadata"
224 );
225 WadoMetadataRequest::new(self.clone(), url)
226 }
227
228 pub fn retrieve_instance(
230 &self,
231 study_instance_uid: &str,
232 series_instance_uid: &str,
233 sop_instance_uid: &str,
234 ) -> WadoSingleFileRequest {
235 let base_url = &self.wado_url;
236 let url = format!(
237 "{base_url}/studies/{study_instance_uid}/series/{series_instance_uid}/instances/{sop_instance_uid}",
238 );
239 WadoSingleFileRequest {
240 request: WadoFileRequest::new(self.clone(), url),
241 }
242 }
243
244 pub fn retrieve_instance_metadata(
246 &self,
247 study_instance_uid: &str,
248 series_instance_uid: &str,
249 sop_instance_uid: &str,
250 ) -> WadoMetadataRequest {
251 let base_url = &self.wado_url;
252 let url = format!(
253 "{base_url}/studies/{study_instance_uid}/series/{series_instance_uid}/instances/{sop_instance_uid}/metadata",
254 );
255 WadoMetadataRequest::new(self.clone(), url)
256 }
257
258 pub fn retrieve_frames(
260 &self,
261 study_instance_uid: &str,
262 series_instance_uid: &str,
263 sop_instance_uid: &str,
264 framelist: &[u32],
265 ) -> WadoFramesRequest {
266 let framelist = framelist
267 .iter()
268 .map(|f| f.to_string())
269 .collect::<Vec<String>>()
270 .join(",");
271 let base_url = &self.wado_url;
272 let url = format!(
273 "{base_url}/studies/{study_instance_uid}/series/{series_instance_uid}/instances/{sop_instance_uid}/frames/{framelist}",
274 );
275 WadoFramesRequest::new(self.clone(), url)
276 }
277}