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 let response = request
78 .send()
79 .await
80 .context(RequestFailedSnafu { url: &self.url })?;
81
82 if !response.status().is_success() {
83 return Err(DicomWebError::HttpStatusFailure {
84 status_code: response.status(),
85 });
86 }
87
88 let ct = response
90 .headers()
91 .get("Content-Type")
92 .ok_or(DicomWebError::MissingContentTypeHeader)?;
93 let media_type = MediaType::parse(ct.to_str().unwrap_or_default())
94 .map_err(|e| DicomWebError::ContentTypeParseFailed { source: e })?;
95
96 if media_type.essence() != MediaType::new(APPLICATION, JSON)
98 && media_type.essence()
99 != MediaType::from_parts(APPLICATION, Name::new_unchecked("dicom"), Some(JSON), &[])
100 {
101 return Err(DicomWebError::UnexpectedContentType {
102 content_type: ct.to_str().unwrap_or_default().to_string(),
103 });
104 }
105
106 Ok(response
107 .json::<Vec<DicomJson<InMemDicomObject>>>()
108 .await
109 .context(DeserializationFailedSnafu {})?
110 .into_iter()
111 .map(|dj| dj.into_inner())
112 .collect())
113 }
114
115 pub fn with_limit(&mut self, limit: u32) -> &mut Self {
118 self.limit = Some(limit);
119 self
120 }
121
122 pub fn with_offset(&mut self, offset: u32) -> &mut Self {
125 self.offset = Some(offset);
126 self
127 }
128
129 pub fn with_includefields(&mut self, includefields: Vec<Tag>) -> &mut Self {
131 self.includefields = includefields;
132 self
133 }
134
135 pub fn with_fuzzymatching(&mut self, fuzzymatching: bool) -> &mut Self {
137 self.fuzzymatching = Some(fuzzymatching);
138 self
139 }
140
141 pub fn with_filter(&mut self, tag: Tag, value: String) -> &mut Self {
143 self.filters.push((tag, value));
144 self
145 }
146}
147
148impl DicomWebClient {
149 pub fn query_modality_scheduled_procedure_steps(&self) -> MwlRequest {
151 let base_url = &self.qido_url;
152 let url = format!("{base_url}/modality-scheduled-procedure-steps");
153
154 MwlRequest::new(self.clone(), url)
155 }
156}