Skip to content

Commit ed2056c

Browse files
committed
feat(qido-rs,mwl-rs): support sequence attribute filtering
1 parent 93e6426 commit ed2056c

5 files changed

Lines changed: 283 additions & 253 deletions

File tree

src/api/mod.rs

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ use std::fmt::Formatter;
33

44
use crate::AppState;
55
use axum::Router;
6-
use dicom::core::dictionary::{DataDictionaryEntry, DataDictionaryEntryRef};
6+
use dicom::core::dictionary::DataDictionaryEntry;
7+
use dicom::core::ops::AttributeSelector;
78
use dicom::core::{DataDictionary, PrimitiveValue, Tag, VR};
89
use dicom::object::StandardDataDictionary;
910
use serde::de::{Error, SeqAccess, Visitor};
@@ -39,10 +40,10 @@ pub fn routes(base_path: &str) -> Router<AppState> {
3940
/// Match Query Parameters for QIDO and MWL requests.
4041
#[derive(Debug, Deserialize, PartialEq)]
4142
#[serde(try_from = "HashMap<String, String>")]
42-
pub struct MatchCriteria(Vec<(Tag, PrimitiveValue)>);
43+
pub struct MatchCriteria(Vec<(AttributeSelector, PrimitiveValue)>);
4344

4445
impl MatchCriteria {
45-
pub fn into_inner(self) -> Vec<(Tag, PrimitiveValue)> {
46+
pub fn into_inner(self) -> Vec<(AttributeSelector, PrimitiveValue)> {
4647
self.0
4748
}
4849
}
@@ -51,15 +52,15 @@ impl TryFrom<HashMap<String, String>> for MatchCriteria {
5152
type Error = String;
5253

5354
fn try_from(value: HashMap<String, String>) -> Result<Self, Self::Error> {
54-
let criteria: Vec<(Tag, PrimitiveValue)> = value
55+
let criteria: Vec<(AttributeSelector, PrimitiveValue)> = value
5556
.into_iter()
5657
.map(|(key, value)| {
5758
StandardDataDictionary
58-
.by_expr(&key)
59-
.ok_or(format!("Cannot use unknown attribute {key} for matching."))
60-
.and_then(|entry| {
61-
to_primitive_value(entry, &value)
62-
.map(|primitive| (entry.tag.inner(), primitive))
59+
.parse_selector(&key)
60+
.map_err(|err| format!("invalid attribute selector {key}: {err}"))
61+
.and_then(|selector| {
62+
to_primitive_value(selector.last_tag(), &value)
63+
.map(|primitive| (selector, primitive))
6364
})
6465
})
6566
.collect::<Result<_, Self::Error>>()?;
@@ -68,14 +69,15 @@ impl TryFrom<HashMap<String, String>> for MatchCriteria {
6869
}
6970

7071
/// helper function to convert a query parameter value to a PrimitiveValue
71-
fn to_primitive_value(
72-
entry: &DataDictionaryEntryRef,
73-
raw_value: &str,
74-
) -> Result<PrimitiveValue, String> {
72+
fn to_primitive_value(tag: Tag, raw_value: &str) -> Result<PrimitiveValue, String> {
7573
if raw_value.is_empty() {
7674
return Ok(PrimitiveValue::Empty);
7775
}
78-
match entry.vr.relaxed() {
76+
let vr = StandardDataDictionary
77+
.by_tag(tag)
78+
.ok_or_else(|| format!("unknown tag {tag}"))?
79+
.vr();
80+
match vr.relaxed() {
7981
// String-like VRs, no parsing required
8082
VR::AE
8183
| VR::AS
@@ -133,9 +135,8 @@ fn to_primitive_value(
133135
Ok(PrimitiveValue::from(value))
134136
}
135137
_ => Err(format!(
136-
"Attribute {} cannot be used for matching due to unsupported VR {}",
137-
entry.tag(),
138-
entry.vr.relaxed()
138+
"Attribute {} cannot be used for matching due to unsupported VR {:?}",
139+
tag, vr
139140
)),
140141
}
141142
}

src/api/mwl/service.rs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ pub enum MwlSearchError {
8181
mod tests {
8282
use axum::extract::Query;
8383
use axum::http::Uri;
84+
use dicom::core::ops::AttributeSelector;
8485
use dicom::core::PrimitiveValue;
8586
use dicom::dictionary_std::tags;
8687

@@ -100,14 +101,37 @@ mod tests {
100101
limit: 42,
101102
include_field: IncludeField::List(vec![tags::PATIENT_WEIGHT]),
102103
match_criteria: MatchCriteria(vec![(
103-
tags::PATIENT_NAME,
104+
AttributeSelector::from(tags::PATIENT_NAME),
104105
PrimitiveValue::from("MUSTERMANN^MAX")
105106
)]),
106107
fuzzy_matching: false,
107108
}
108109
);
109110
}
110111

112+
#[test]
113+
fn parse_query_params_nested() {
114+
let uri = Uri::from_static("http://test?00400100.00400010=CTSCANNER");
115+
let Query(params) = Query::<MwlQueryParameters>::try_from_uri(&uri).unwrap();
116+
117+
assert_eq!(
118+
params,
119+
MwlQueryParameters {
120+
offset: 0,
121+
limit: 200,
122+
include_field: IncludeField::List(vec![]),
123+
match_criteria: MatchCriteria(vec![(
124+
AttributeSelector::from((
125+
tags::SCHEDULED_PROCEDURE_STEP_SEQUENCE,
126+
tags::SCHEDULED_STATION_NAME
127+
)),
128+
PrimitiveValue::from("CTSCANNER")
129+
)]),
130+
fuzzy_matching: false,
131+
}
132+
);
133+
}
134+
111135
#[test]
112136
fn parse_query_params_multiple_includefield() {
113137
let uri =

0 commit comments

Comments
 (0)