1use dicom_core::{ops::AttributeSelector, Tag};
4use dicom_json::DicomJson;
5use dicom_object::InMemDicomObject;
6
7use snafu::ResultExt;
8
9use crate::{
10 apply_auth_and_headers, selector_to_string, validate_dicom_json_content_type,
11 DeserializationFailedSnafu, DicomWebClient, DicomWebError, RequestFailedSnafu,
12};
13
14#[derive(Debug, Clone)]
17pub struct QidoRequest {
18 client: DicomWebClient,
19 url: String,
20
21 limit: Option<u32>,
22 offset: Option<u32>,
23 includefields: Vec<Tag>,
24 fuzzymatching: Option<bool>,
25 filters: Vec<(AttributeSelector, String)>,
26}
27
28impl QidoRequest {
29 fn new(client: DicomWebClient, url: String) -> Self {
30 QidoRequest {
31 client,
32 url,
33 limit: None,
34 offset: None,
35 includefields: vec![],
36 fuzzymatching: None,
37 filters: vec![],
38 }
39 }
40
41 pub async fn run(&self) -> Result<Vec<InMemDicomObject>, DicomWebError> {
43 let mut query: Vec<(String, String)> = vec![];
44 if let Some(limit) = self.limit {
45 query.push((String::from("limit"), limit.to_string()));
46 }
47 if let Some(offset) = self.offset {
48 query.push((String::from("offset"), offset.to_string()));
49 }
50 if let Some(fuzzymatching) = self.fuzzymatching {
51 query.push((String::from("fuzzymatching"), fuzzymatching.to_string()));
52 }
53 for include_field in self.includefields.iter() {
54 let radix_string = format!(
56 "{:04x}{:04x}",
57 include_field.group(),
58 include_field.element()
59 );
60
61 query.push((String::from("includefield"), radix_string));
62 }
63 for (selector, value) in self.filters.iter() {
64 query.push((selector_to_string(&selector), value.clone()));
65 }
66
67 let mut request = self.client.client.get(&self.url).query(&query);
68 request = apply_auth_and_headers(request, &self.client);
69
70 let response = request
71 .send()
72 .await
73 .context(RequestFailedSnafu { url: &self.url })?;
74
75 if !response.status().is_success() {
76 return Err(DicomWebError::HttpStatusFailure {
77 status_code: response.status(),
78 });
79 }
80
81 let ct = response
83 .headers()
84 .get("Content-Type")
85 .ok_or(DicomWebError::MissingContentTypeHeader)?;
86 validate_dicom_json_content_type(ct.to_str().unwrap_or_default())?;
87
88 Ok(response
89 .json::<Vec<DicomJson<InMemDicomObject>>>()
90 .await
91 .context(DeserializationFailedSnafu {})?
92 .into_iter()
93 .map(|dicomjson| dicomjson.into_inner())
94 .collect())
95 }
96
97 pub fn with_limit(&mut self, limit: u32) -> &mut Self {
100 self.limit = Some(limit);
101 self
102 }
103
104 pub fn with_offset(&mut self, offset: u32) -> &mut Self {
107 self.offset = Some(offset);
108 self
109 }
110
111 pub fn with_includefields(&mut self, includefields: Vec<Tag>) -> &mut Self {
113 self.includefields = includefields;
114 self
115 }
116
117 pub fn with_fuzzymatching(&mut self, fuzzymatching: bool) -> &mut Self {
119 self.fuzzymatching = Some(fuzzymatching);
120 self
121 }
122
123 pub fn with_filter(&mut self, selector: AttributeSelector, value: String) -> &mut Self {
125 self.filters.push((selector, value));
126 self
127 }
128}
129
130impl DicomWebClient {
131 pub fn query_studies(&self) -> QidoRequest {
133 let base_url = &self.qido_url;
134 let url = format!("{base_url}/studies");
135
136 QidoRequest::new(self.clone(), url)
137 }
138
139 pub fn query_series(&self) -> QidoRequest {
141 let base_url = &self.qido_url;
142 let url = format!("{base_url}/series");
143
144 QidoRequest::new(self.clone(), url)
145 }
146
147 pub fn query_series_in_study(&self, study_instance_uid: &str) -> QidoRequest {
149 let base_url = &self.qido_url;
150 let url = format!("{base_url}/studies/{study_instance_uid}/series");
151
152 QidoRequest::new(self.clone(), url)
153 }
154
155 pub fn query_instances(&self) -> QidoRequest {
157 let base_url = &self.qido_url;
158 let url = format!("{base_url}/instances");
159
160 QidoRequest::new(self.clone(), url)
161 }
162
163 pub fn query_instances_in_series(
165 &self,
166 study_instance_uid: &str,
167 series_instance_uid: &str,
168 ) -> QidoRequest {
169 let base_url = &self.qido_url;
170 let url = format!(
171 "{base_url}/studies/{study_instance_uid}/series/{series_instance_uid}/instances",
172 );
173
174 QidoRequest::new(self.clone(), url)
175 }
176}