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 for (key, value) in &self.client.extra_headers {
45 request = request.header(key, value);
46 }
47
48 let response = request
49 .send()
50 .await
51 .context(RequestFailedSnafu { url: &self.url })?;
52
53 if !response.status().is_success() {
54 return Err(DicomWebError::HttpStatusFailure {
55 status_code: response.status(),
56 });
57 }
58
59 let ct = response
61 .headers()
62 .get("Content-Type")
63 .ok_or(DicomWebError::MissingContentTypeHeader)?;
64 let media_type = MediaType::parse(ct.to_str().unwrap_or_default())
65 .map_err(|e| DicomWebError::ContentTypeParseFailed { source: e })?;
66
67 if media_type.essence() != MediaType::new(APPLICATION, JSON)
69 && media_type.essence()
70 != MediaType::from_parts(APPLICATION, Name::new_unchecked("dicom"), Some(JSON), &[])
71 {
72 return Err(DicomWebError::UnexpectedContentType {
73 content_type: ct.to_str().unwrap_or_default().to_string(),
74 });
75 }
76
77 Ok(response
78 .json::<Vec<DicomJson<InMemDicomObject>>>()
79 .await
80 .context(DeserializationFailedSnafu {})?
81 .into_iter()
82 .map(|dj| dj.into_inner())
83 .collect())
84 }
85}
86
87#[derive(Debug, Clone)]
89pub struct WadoFileRequest {
90 client: DicomWebClient,
91 url: String,
92}
93
94impl WadoFileRequest {
95 pub fn new(client: DicomWebClient, url: String) -> Self {
96 WadoFileRequest { client, url }
97 }
98
99 pub async fn run(
100 &self,
101 ) -> Result<
102 impl Stream<Item = Result<FileDicomObject<InMemDicomObject>, DicomWebError>>,
103 DicomWebError,
104 > {
105 let mut request = self.client.client.get(&self.url);
106
107 if let Some(username) = &self.client.username {
109 request = request.basic_auth(username, self.client.password.as_ref());
110 }
111 else if let Some(bearer_token) = &self.client.bearer_token {
113 request = request.bearer_auth(bearer_token);
114 }
115
116 let response = request
117 .send()
118 .await
119 .context(RequestFailedSnafu { url: &self.url })?;
120
121 if !response.status().is_success() {
122 return Err(DicomWebError::HttpStatusFailure {
123 status_code: response.status(),
124 });
125 }
126
127 let headers: Vec<(String, String)> = response
129 .headers()
130 .iter()
131 .map(|(k, v)| (k.to_string(), String::from(v.to_str().unwrap_or_default())))
132 .collect();
133
134 let stream = response.bytes_stream();
135 let reader = MultipartReader::from_stream_with_headers(stream, &headers)
136 .map_err(|source| DicomWebError::MultipartReaderFailed { source })?;
137
138 if reader.multipart_type != MultipartType::Related {
139 return Err(DicomWebError::UnexpectedMultipartType {
140 multipart_type: (reader.multipart_type),
141 });
142 }
143
144 Ok(reader.map(|item| {
145 let item = item.context(MultipartReaderFailedSnafu)?;
146 let ct = item
148 .headers
149 .iter()
150 .find(|(k, _)| k.to_lowercase() == "content-type")
151 .map(|(_, v)| v.as_str())
152 .context(MissingContentTypeHeaderSnafu)?;
153 let media_type = MediaType::parse(ct)
154 .map_err(|e| DicomWebError::ContentTypeParseFailed { source: e })?;
155
156 if media_type.essence() != MediaType::new(APPLICATION, JSON)
158 && media_type.essence()
159 != MediaType::from_parts(
160 APPLICATION,
161 Name::new_unchecked("dicom"),
162 Some(JSON),
163 &[],
164 )
165 {
166 return Err(DicomWebError::UnexpectedContentType {
167 content_type: ct.to_owned(),
168 });
169 }
170 from_reader(&*item.data).context(DicomReaderFailedSnafu)
171 }))
172 }
173}
174
175pub struct WadoSingleFileRequest {
177 request: WadoFileRequest,
178}
179
180impl WadoSingleFileRequest {
181 pub async fn run(&self) -> Result<FileDicomObject<InMemDicomObject>, DicomWebError> {
182 let mut stream = self.request.run().await?;
184 stream.next().await.context(EmptyResponseSnafu)?
185 }
186}
187
188pub struct WadoFramesRequest {
190 client: DicomWebClient,
191 url: String,
192}
193
194impl WadoFramesRequest {
195 pub fn new(client: DicomWebClient, url: String) -> Self {
196 WadoFramesRequest { client, url }
197 }
198
199 pub async fn run(&self) -> Result<Vec<MultipartItem>, DicomWebError> {
200 let mut request = self.client.client.get(&self.url);
201
202 if let Some(username) = &self.client.username {
204 request = request.basic_auth(username, self.client.password.as_ref());
205 }
206 else if let Some(bearer_token) = &self.client.bearer_token {
208 request = request.bearer_auth(bearer_token);
209 }
210
211 let response = request
212 .send()
213 .await
214 .context(RequestFailedSnafu { url: &self.url })?;
215
216 if !response.status().is_success() {
217 return Err(DicomWebError::HttpStatusFailure {
218 status_code: response.status(),
219 });
220 }
221
222 let headers: Vec<(String, String)> = response
224 .headers()
225 .iter()
226 .map(|(k, v)| (k.to_string(), String::from(v.to_str().unwrap_or_default())))
227 .collect();
228 let stream = response.bytes_stream();
229 let mut reader = MultipartReader::from_stream_with_headers(stream, &headers)
230 .map_err(|source| DicomWebError::MultipartReaderFailed { source })?;
231
232 if reader.multipart_type != MultipartType::Related {
233 return Err(DicomWebError::UnexpectedMultipartType {
234 multipart_type: (reader.multipart_type),
235 });
236 }
237
238 let mut item_list = vec![];
239
240 while let Some(item) = reader.next().await {
241 let item = item.context(MultipartReaderFailedSnafu)?;
242 item_list.push(item);
243 }
244
245 Ok(item_list)
246 }
247}
248
249impl DicomWebClient {
250 pub fn retrieve_study(&self, study_instance_uid: &str) -> WadoFileRequest {
252 let url = format!("{}/studies/{}", self.wado_url, study_instance_uid);
253 WadoFileRequest::new(self.clone(), url)
254 }
255
256 pub fn retrieve_study_metadata(&self, study_instance_uid: &str) -> WadoMetadataRequest {
258 let url = format!("{}/studies/{}/metadata", self.wado_url, study_instance_uid);
259 WadoMetadataRequest::new(self.clone(), url)
260 }
261
262 pub fn retrieve_series(
264 &self,
265 study_instance_uid: &str,
266 series_instance_uid: &str,
267 ) -> WadoFileRequest {
268 let base_url = &self.wado_url;
269 let url = format!("{base_url}/studies/{study_instance_uid}/series/{series_instance_uid}",);
270 WadoFileRequest::new(self.clone(), url)
271 }
272
273 pub fn retrieve_series_metadata(
275 &self,
276 study_instance_uid: &str,
277 series_instance_uid: &str,
278 ) -> WadoMetadataRequest {
279 let base_url = &self.wado_url;
280 let url = format!(
281 "{base_url}/studies/{study_instance_uid}/series/{series_instance_uid}/metadata"
282 );
283 WadoMetadataRequest::new(self.clone(), url)
284 }
285
286 pub fn retrieve_instance(
288 &self,
289 study_instance_uid: &str,
290 series_instance_uid: &str,
291 sop_instance_uid: &str,
292 ) -> WadoSingleFileRequest {
293 let base_url = &self.wado_url;
294 let url = format!(
295 "{base_url}/studies/{study_instance_uid}/series/{series_instance_uid}/instances/{sop_instance_uid}",
296 );
297 WadoSingleFileRequest {
298 request: WadoFileRequest::new(self.clone(), url),
299 }
300 }
301
302 pub fn retrieve_instance_metadata(
304 &self,
305 study_instance_uid: &str,
306 series_instance_uid: &str,
307 sop_instance_uid: &str,
308 ) -> WadoMetadataRequest {
309 let base_url = &self.wado_url;
310 let url = format!(
311 "{base_url}/studies/{study_instance_uid}/series/{series_instance_uid}/instances/{sop_instance_uid}/metadata",
312 );
313 WadoMetadataRequest::new(self.clone(), url)
314 }
315
316 pub fn retrieve_frames(
318 &self,
319 study_instance_uid: &str,
320 series_instance_uid: &str,
321 sop_instance_uid: &str,
322 framelist: &[u32],
323 ) -> WadoFramesRequest {
324 let framelist = framelist
325 .iter()
326 .map(|f| f.to_string())
327 .collect::<Vec<String>>()
328 .join(",");
329 let base_url = &self.wado_url;
330 let url = format!(
331 "{base_url}/studies/{study_instance_uid}/series/{series_instance_uid}/instances/{sop_instance_uid}/frames/{framelist}",
332 );
333 WadoFramesRequest::new(self.clone(), url)
334 }
335}