1use dicom_json::DicomJson;
3use dicom_object::{from_reader, FileDicomObject, InMemDicomObject};
4
5use futures_util::{Stream, StreamExt};
6use mediatype::{
7 names::{APPLICATION, JSON},
8 MediaType, Name,
9};
10use multipart_rs::{MultipartItem, MultipartReader, MultipartType};
11use snafu::{OptionExt, ResultExt};
12
13use crate::{
14 DeserializationFailedSnafu, DicomReaderFailedSnafu, DicomWebClient, DicomWebError,
15 EmptyResponseSnafu, MissingContentTypeHeaderSnafu, MultipartReaderFailedSnafu,
16 RequestFailedSnafu,
17};
18
19#[derive(Debug, Clone)]
21pub struct WadoMetadataRequest {
22 client: DicomWebClient,
23 url: String,
24}
25
26impl WadoMetadataRequest {
27 fn new(client: DicomWebClient, url: String) -> Self {
28 WadoMetadataRequest { client, url }
29 }
30
31 pub async fn run(&self) -> Result<Vec<InMemDicomObject>, DicomWebError> {
32 let mut request = self.client.client.get(&self.url);
33
34 if let Some(username) = &self.client.username {
36 request = request.basic_auth(username, self.client.password.as_ref());
37 }
38 else if let Some(bearer_token) = &self.client.bearer_token {
40 request = request.bearer_auth(bearer_token);
41 }
42
43 let response = request
44 .send()
45 .await
46 .context(RequestFailedSnafu { url: &self.url })?;
47
48 if !response.status().is_success() {
49 return Err(DicomWebError::HttpStatusFailure {
50 status_code: response.status(),
51 });
52 }
53
54 let ct = response
56 .headers()
57 .get("Content-Type")
58 .ok_or(DicomWebError::MissingContentTypeHeader)?;
59 let media_type = MediaType::parse(ct.to_str().unwrap_or_default())
60 .map_err(|e| DicomWebError::ContentTypeParseFailed { source: e })?;
61
62 if media_type.essence() != MediaType::new(APPLICATION, JSON)
64 && media_type.essence()
65 != MediaType::from_parts(APPLICATION, Name::new_unchecked("dicom"), Some(JSON), &[])
66 {
67 return Err(DicomWebError::UnexpectedContentType {
68 content_type: ct.to_str().unwrap_or_default().to_string(),
69 });
70 }
71
72 Ok(response
73 .json::<Vec<DicomJson<InMemDicomObject>>>()
74 .await
75 .context(DeserializationFailedSnafu {})?
76 .into_iter()
77 .map(|dj| dj.into_inner())
78 .collect())
79 }
80}
81
82#[derive(Debug, Clone)]
84pub struct WadoFileRequest {
85 client: DicomWebClient,
86 url: String,
87}
88
89impl WadoFileRequest {
90 pub fn new(client: DicomWebClient, url: String) -> Self {
91 WadoFileRequest { client, url }
92 }
93
94 pub async fn run(
95 &self,
96 ) -> Result<
97 impl Stream<Item = Result<FileDicomObject<InMemDicomObject>, DicomWebError>>,
98 DicomWebError,
99 > {
100 let mut request = self.client.client.get(&self.url);
101
102 if let Some(username) = &self.client.username {
104 request = request.basic_auth(username, self.client.password.as_ref());
105 }
106 else if let Some(bearer_token) = &self.client.bearer_token {
108 request = request.bearer_auth(bearer_token);
109 }
110
111 let response = request
112 .send()
113 .await
114 .context(RequestFailedSnafu { url: &self.url })?;
115
116 if !response.status().is_success() {
117 return Err(DicomWebError::HttpStatusFailure {
118 status_code: response.status(),
119 });
120 }
121
122 let headers: Vec<(String, String)> = response
124 .headers()
125 .iter()
126 .map(|(k, v)| (k.to_string(), String::from(v.to_str().unwrap_or_default())))
127 .collect();
128
129 let stream = response.bytes_stream();
130 let reader = MultipartReader::from_stream_with_headers(stream, &headers)
131 .map_err(|source| DicomWebError::MultipartReaderFailed { source })?;
132
133 if reader.multipart_type != MultipartType::Related {
134 return Err(DicomWebError::UnexpectedMultipartType {
135 multipart_type: (reader.multipart_type),
136 });
137 }
138
139 Ok(reader.map(|item| {
140 let item = item.context(MultipartReaderFailedSnafu)?;
141 let ct = item
143 .headers
144 .iter()
145 .find(|(k, _)| k.to_lowercase() == "content-type")
146 .map(|(_, v)| v.as_str())
147 .context(MissingContentTypeHeaderSnafu)?;
148 let media_type = MediaType::parse(ct)
149 .map_err(|e| DicomWebError::ContentTypeParseFailed { source: e })?;
150
151 if media_type.essence() != MediaType::new(APPLICATION, JSON)
153 && media_type.essence()
154 != MediaType::from_parts(
155 APPLICATION,
156 Name::new_unchecked("dicom"),
157 Some(JSON),
158 &[],
159 )
160 {
161 return Err(DicomWebError::UnexpectedContentType {
162 content_type: ct.to_owned(),
163 });
164 }
165 from_reader(&*item.data).context(DicomReaderFailedSnafu)
166 }))
167 }
168}
169
170pub struct WadoSingleFileRequest {
172 request: WadoFileRequest,
173}
174
175impl WadoSingleFileRequest {
176 pub async fn run(&self) -> Result<FileDicomObject<InMemDicomObject>, DicomWebError> {
177 let mut stream = self.request.run().await?;
179 stream.next().await.context(EmptyResponseSnafu)?
180 }
181}
182
183pub struct WadoFramesRequest {
185 client: DicomWebClient,
186 url: String,
187}
188
189impl WadoFramesRequest {
190 pub fn new(client: DicomWebClient, url: String) -> Self {
191 WadoFramesRequest { client, url }
192 }
193
194 pub async fn run(&self) -> Result<Vec<MultipartItem>, DicomWebError> {
195 let mut request = self.client.client.get(&self.url);
196
197 if let Some(username) = &self.client.username {
199 request = request.basic_auth(username, self.client.password.as_ref());
200 }
201 else if let Some(bearer_token) = &self.client.bearer_token {
203 request = request.bearer_auth(bearer_token);
204 }
205
206 let response = request
207 .send()
208 .await
209 .context(RequestFailedSnafu { url: &self.url })?;
210
211 if !response.status().is_success() {
212 return Err(DicomWebError::HttpStatusFailure {
213 status_code: response.status(),
214 });
215 }
216
217 let headers: Vec<(String, String)> = response
219 .headers()
220 .iter()
221 .map(|(k, v)| (k.to_string(), String::from(v.to_str().unwrap_or_default())))
222 .collect();
223 let stream = response.bytes_stream();
224 let mut reader = MultipartReader::from_stream_with_headers(stream, &headers)
225 .map_err(|source| DicomWebError::MultipartReaderFailed { source })?;
226
227 if reader.multipart_type != MultipartType::Related {
228 return Err(DicomWebError::UnexpectedMultipartType {
229 multipart_type: (reader.multipart_type),
230 });
231 }
232
233 let mut item_list = vec![];
234
235 while let Some(item) = reader.next().await {
236 let item = item.context(MultipartReaderFailedSnafu)?;
237 item_list.push(item);
238 }
239
240 Ok(item_list)
241 }
242}
243
244impl DicomWebClient {
245 pub fn retrieve_study(&self, study_instance_uid: &str) -> WadoFileRequest {
247 let url = format!("{}/studies/{}", self.wado_url, study_instance_uid);
248 WadoFileRequest::new(self.clone(), url)
249 }
250
251 pub fn retrieve_study_metadata(&self, study_instance_uid: &str) -> WadoMetadataRequest {
253 let url = format!("{}/studies/{}/metadata", self.wado_url, study_instance_uid);
254 WadoMetadataRequest::new(self.clone(), url)
255 }
256
257 pub fn retrieve_series(
259 &self,
260 study_instance_uid: &str,
261 series_instance_uid: &str,
262 ) -> WadoFileRequest {
263 let base_url = &self.wado_url;
264 let url = format!("{base_url}/studies/{study_instance_uid}/series/{series_instance_uid}",);
265 WadoFileRequest::new(self.clone(), url)
266 }
267
268 pub fn retrieve_series_metadata(
270 &self,
271 study_instance_uid: &str,
272 series_instance_uid: &str,
273 ) -> WadoMetadataRequest {
274 let base_url = &self.wado_url;
275 let url = format!(
276 "{base_url}/studies/{study_instance_uid}/series/{series_instance_uid}/metadata"
277 );
278 WadoMetadataRequest::new(self.clone(), url)
279 }
280
281 pub fn retrieve_instance(
283 &self,
284 study_instance_uid: &str,
285 series_instance_uid: &str,
286 sop_instance_uid: &str,
287 ) -> WadoSingleFileRequest {
288 let base_url = &self.wado_url;
289 let url = format!(
290 "{base_url}/studies/{study_instance_uid}/series/{series_instance_uid}/instances/{sop_instance_uid}",
291 );
292 WadoSingleFileRequest {
293 request: WadoFileRequest::new(self.clone(), url),
294 }
295 }
296
297 pub fn retrieve_instance_metadata(
299 &self,
300 study_instance_uid: &str,
301 series_instance_uid: &str,
302 sop_instance_uid: &str,
303 ) -> WadoMetadataRequest {
304 let base_url = &self.wado_url;
305 let url = format!(
306 "{base_url}/studies/{study_instance_uid}/series/{series_instance_uid}/instances/{sop_instance_uid}/metadata",
307 );
308 WadoMetadataRequest::new(self.clone(), url)
309 }
310
311 pub fn retrieve_frames(
313 &self,
314 study_instance_uid: &str,
315 series_instance_uid: &str,
316 sop_instance_uid: &str,
317 framelist: &[u32],
318 ) -> WadoFramesRequest {
319 let framelist = framelist
320 .iter()
321 .map(|f| f.to_string())
322 .collect::<Vec<String>>()
323 .join(",");
324 let base_url = &self.wado_url;
325 let url = format!(
326 "{base_url}/studies/{study_instance_uid}/series/{series_instance_uid}/instances/{sop_instance_uid}/frames/{framelist}",
327 );
328 WadoFramesRequest::new(self.clone(), url)
329 }
330}