1use dicom_core::ops::AttributeSelector;
4use dicom_json::DicomJson;
5use dicom_object::InMemDicomObject;
6
7use serde::{Deserialize, Serialize};
8use snafu::ResultExt;
9
10use crate::{
11 apply_auth_and_headers, selector_to_string, validate_dicom_json_content_type,
12 DeserializationFailedSnafu, DicomWebClient, DicomWebError, RequestFailedSnafu,
13};
14
15#[derive(Debug, Clone)]
19pub struct AsdoSendRequest {
20 client: DicomWebClient,
21 url: String,
22 destination: String,
23 username: Option<String>,
26 password: Option<String>,
27 token: Option<String>,
28
29 filters: Vec<(AttributeSelector, String)>,
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize)]
33struct AuthInfo {
34 username: Option<String>,
35 password: Option<String>,
36 token: Option<String>,
37}
38
39impl AsdoSendRequest {
40 fn new(client: DicomWebClient, url: String) -> Self {
41 AsdoSendRequest {
42 client,
43 url,
44 filters: vec![],
45 destination: String::new(),
46 username: None,
47 password: None,
48 token: None,
49 }
50 }
51
52 pub async fn run(self) -> Result<InMemDicomObject, DicomWebError> {
54 let mut query: Vec<(String, String)> = vec![];
55 for (selector, value) in self.filters.iter() {
56 query.push((selector_to_string(&selector), value.clone()));
57 }
58
59 if self.destination.is_empty() {
60 return Err(DicomWebError::Other {
61 message: "Destination must be set for ASDO-RS request".to_string(),
62 });
63 }
64
65 query.push((String::from("destination"), self.destination.clone()));
66
67 let mut request = self.client.client.post(&self.url).query(&query);
68 if let (Some(username), Some(password)) = (&self.username, &self.password) {
71 request = request.json(&AuthInfo {
72 username: Some(username.clone()),
73 password: Some(password.clone()),
74 token: None,
75 });
76 } else if let Some(token) = &self.token {
77 request = request.json(&AuthInfo {
78 username: None,
79 password: None,
80 token: Some(token.clone()),
81 });
82 }
83
84 request = apply_auth_and_headers(request, &self.client);
85
86 let response = request
87 .send()
88 .await
89 .context(RequestFailedSnafu { url: &self.url })?;
90
91 if !response.status().is_success() {
92 return Err(DicomWebError::HttpStatusFailure {
93 status_code: response.status(),
94 });
95 }
96
97 let ct = response
99 .headers()
100 .get("Content-Type")
101 .ok_or(DicomWebError::MissingContentTypeHeader)?;
102 validate_dicom_json_content_type(ct.to_str().unwrap_or_default())?;
103
104 Ok(response
105 .json::<DicomJson<InMemDicomObject>>()
106 .await
107 .context(DeserializationFailedSnafu {})?
108 .into_inner())
109 }
110
111 pub fn with_filter(mut self, selector: AttributeSelector, value: String) -> Self {
113 self.filters.push((selector, value));
114 self
115 }
116
117 pub fn with_destination(mut self, destination: String) -> Self {
119 self.destination = destination;
120 self
121 }
122
123 pub fn with_basic_auth(mut self, username: String, password: String) -> Self {
124 self.username = Some(username);
125 self.password = Some(password);
126 self
127 }
128
129 pub fn with_bearer_token(mut self, token: String) -> Self {
130 self.token = Some(token);
131 self
132 }
133}
134
135#[derive(Debug, Clone)]
136pub struct AsdoStatusRequest {
137 client: DicomWebClient,
138 url: String,
139}
140
141impl AsdoStatusRequest {
142 fn new(client: DicomWebClient, url: String) -> Self {
143 AsdoStatusRequest { client, url }
144 }
145
146 pub async fn run(&self) -> Result<InMemDicomObject, DicomWebError> {
147 let request = self.client.client.get(&self.url);
148 let request = apply_auth_and_headers(request, &self.client);
149
150 let response = request
151 .send()
152 .await
153 .context(RequestFailedSnafu { url: &self.url })?;
154
155 if !response.status().is_success() {
156 return Err(DicomWebError::HttpStatusFailure {
157 status_code: response.status(),
158 });
159 }
160
161 let ct = response
163 .headers()
164 .get("Content-Type")
165 .ok_or(DicomWebError::MissingContentTypeHeader)?;
166 validate_dicom_json_content_type(ct.to_str().unwrap_or_default())?;
167
168 Ok(response
169 .json::<DicomJson<InMemDicomObject>>()
170 .await
171 .context(DeserializationFailedSnafu {})?
172 .into_inner())
173 }
174}
175
176impl DicomWebClient {
177 pub fn send_studies(&self, transaction_uid: &str) -> AsdoSendRequest {
179 let base_url = &self.qido_url;
180 let url = format!("{base_url}/studies/send-requests/{transaction_uid}");
181
182 AsdoSendRequest::new(self.clone(), url)
183 }
184
185 pub fn send_studies_status(&self, transaction_uid: &str) -> AsdoStatusRequest {
187 let base_url = &self.qido_url;
188 let url = format!("{base_url}/studies/send-requests/{transaction_uid}");
189
190 AsdoStatusRequest::new(self.clone(), url)
191 }
192
193 pub fn send_series_in_study(
195 &self,
196 study_instance_uid: &str,
197 transaction_uid: &str,
198 ) -> AsdoSendRequest {
199 let base_url = &self.qido_url;
200 let url = format!(
201 "{base_url}/studies/{study_instance_uid}/series/send-requests/{transaction_uid}"
202 );
203
204 AsdoSendRequest::new(self.clone(), url)
205 }
206
207 pub fn send_instances_in_study(
209 &self,
210 study_instance_uid: &str,
211 transaction_uid: &str,
212 ) -> AsdoSendRequest {
213 let base_url = &self.qido_url;
214 let url = format!(
215 "{base_url}/studies/{study_instance_uid}/instances/send-requests/{transaction_uid}"
216 );
217
218 AsdoSendRequest::new(self.clone(), url)
219 }
220
221 pub fn send_instances_in_series(
223 &self,
224 study_instance_uid: &str,
225 series_instance_uid: &str,
226 transaction_uid: &str,
227 ) -> AsdoSendRequest {
228 let base_url = &self.qido_url;
229 let url = format!(
230 "{base_url}/studies/{study_instance_uid}/series/{series_instance_uid}/instances/send-requests/{transaction_uid}",
231 );
232
233 AsdoSendRequest::new(self.clone(), url)
234 }
235}