Skip to main content

dicom_web/
mwl.rs

1//! Module for MWL-RS requests
2//! See https://dicom.nema.org/medical/dicom/current/output/html/part18.html#chapter_14
3use 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/// A builder type for MWL-RS requests
15/// By default, the request is built with no filters, no limit, and no offset.
16#[derive(Debug, Clone)]
17pub struct MwlRequest {
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 MwlRequest {
29    fn new(client: DicomWebClient, url: String) -> Self {
30        MwlRequest {
31            client,
32            url,
33            limit: None,
34            offset: None,
35            includefields: vec![],
36            fuzzymatching: None,
37            filters: vec![],
38        }
39    }
40
41    /// Execute the MWL-RS request
42    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            // Convert the tag to a radix string
55            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        // Check if the response is a DICOM-JSON
82        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(|dj| dj.into_inner())
94            .collect())
95    }
96
97    /// Set the maximum number of results to return. Will be passed as a query parameter.
98    /// This is useful for pagination.
99    pub fn with_limit(&mut self, limit: u32) -> &mut Self {
100        self.limit = Some(limit);
101        self
102    }
103
104    /// Set the offset of the results to return. Will be passed as a query parameter.
105    /// This is useful for pagination.
106    pub fn with_offset(&mut self, offset: u32) -> &mut Self {
107        self.offset = Some(offset);
108        self
109    }
110
111    /// Set the tags that should be queried. Will be passed as a query parameter.
112    pub fn with_includefields(&mut self, includefields: Vec<Tag>) -> &mut Self {
113        self.includefields = includefields;
114        self
115    }
116
117    /// Set whether fuzzy matching should be used. Will be passed as a query parameter.
118    pub fn with_fuzzymatching(&mut self, fuzzymatching: bool) -> &mut Self {
119        self.fuzzymatching = Some(fuzzymatching);
120        self
121    }
122
123    /// Add a filter to the query. Will be passed as a query parameter.
124    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    /// Create a DMWL-RS request to query all studies
132    pub fn query_modality_scheduled_procedure_steps(&self) -> MwlRequest {
133        let base_url = &self.qido_url;
134        let url = format!("{base_url}/modality-scheduled-procedure-steps");
135
136        MwlRequest::new(self.clone(), url)
137    }
138}