1use dicom_core::Tag;
5use dicom_json::DicomJson;
6use dicom_object::InMemDicomObject;
7
8use mediatype::{
9 names::{APPLICATION, JSON},
10 MediaType, Name,
11};
12use snafu::ResultExt;
13
14use crate::{DeserializationFailedSnafu, DicomWebClient, DicomWebError, RequestFailedSnafu};
15
16#[derive(Debug, Clone)]
19pub struct MwlRequest {
20 client: DicomWebClient,
21 url: String,
22
23 limit: Option<u32>,
24 offset: Option<u32>,
25 includefields: Vec<Tag>,
26 fuzzymatching: Option<bool>,
27 filters: Vec<(Tag, String)>,
28}
29
30impl MwlRequest {
31 pub fn new(client: DicomWebClient, url: String) -> Self {
32 MwlRequest {
33 client,
34 url,
35 limit: None,
36 offset: None,
37 includefields: vec![],
38 fuzzymatching: None,
39 filters: vec![],
40 }
41 }
42
43 pub async fn run(&self) -> Result<Vec<InMemDicomObject>, DicomWebError> {
45 let mut query: Vec<(String, String)> = vec![];
46 if let Some(limit) = self.limit {
47 query.push((String::from("limit"), limit.to_string()));
48 }
49 if let Some(offset) = self.offset {
50 query.push((String::from("offset"), offset.to_string()));
51 }
52 for include_field in self.includefields.iter() {
53 let radix_string = format!(
55 "{:04x}{:04x}",
56 include_field.group(),
57 include_field.element()
58 );
59
60 query.push((String::from("includefield"), radix_string));
61 }
62 for filter in self.filters.iter() {
63 query.push((filter.0.to_string(), filter.1.clone()));
64 }
65
66 let mut request = self.client.client.get(&self.url).query(&query);
67
68 if let Some(username) = &self.client.username {
70 request = request.basic_auth(username, self.client.password.as_ref());
71 }
72 else if let Some(bearer_token) = &self.client.bearer_token {
74 request = request.bearer_auth(bearer_token);
75 }
76
77 for (key, value) in &self.client.extra_headers {
79 request = request.header(key, value);
80 }
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 ct = response
95 .headers()
96 .get("Content-Type")
97 .ok_or(DicomWebError::MissingContentTypeHeader)?;
98 let media_type = MediaType::parse(ct.to_str().unwrap_or_default())
99 .map_err(|e| DicomWebError::ContentTypeParseFailed { source: e })?;
100
101 if media_type.essence() != MediaType::new(APPLICATION, JSON)
103 && media_type.essence()
104 != MediaType::from_parts(APPLICATION, Name::new_unchecked("dicom"), Some(JSON), &[])
105 {
106 return Err(DicomWebError::UnexpectedContentType {
107 content_type: ct.to_str().unwrap_or_default().to_string(),
108 });
109 }
110
111 Ok(response
112 .json::<Vec<DicomJson<InMemDicomObject>>>()
113 .await
114 .context(DeserializationFailedSnafu {})?
115 .into_iter()
116 .map(|dj| dj.into_inner())
117 .collect())
118 }
119
120 pub fn with_limit(&mut self, limit: u32) -> &mut Self {
123 self.limit = Some(limit);
124 self
125 }
126
127 pub fn with_offset(&mut self, offset: u32) -> &mut Self {
130 self.offset = Some(offset);
131 self
132 }
133
134 pub fn with_includefields(&mut self, includefields: Vec<Tag>) -> &mut Self {
136 self.includefields = includefields;
137 self
138 }
139
140 pub fn with_fuzzymatching(&mut self, fuzzymatching: bool) -> &mut Self {
142 self.fuzzymatching = Some(fuzzymatching);
143 self
144 }
145
146 pub fn with_filter(&mut self, tag: Tag, value: String) -> &mut Self {
148 self.filters.push((tag, value));
149 self
150 }
151}
152
153impl DicomWebClient {
154 pub fn query_modality_scheduled_procedure_steps(&self) -> MwlRequest {
156 let base_url = &self.qido_url;
157 let url = format!("{base_url}/modality-scheduled-procedure-steps");
158
159 MwlRequest::new(self.clone(), url)
160 }
161}