Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 18 additions & 17 deletions src/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@

use crate::AppState;
use axum::Router;
use dicom::core::dictionary::{DataDictionaryEntry, DataDictionaryEntryRef};
use dicom::core::dictionary::DataDictionaryEntry;
use dicom::core::ops::AttributeSelector;
use dicom::core::{DataDictionary, PrimitiveValue, Tag, VR};
use dicom::object::StandardDataDictionary;
use serde::de::{Error, SeqAccess, Visitor};
Expand Down Expand Up @@ -39,10 +40,10 @@
/// Match Query Parameters for QIDO and MWL requests.
#[derive(Debug, Deserialize, PartialEq)]
#[serde(try_from = "HashMap<String, String>")]
pub struct MatchCriteria(Vec<(Tag, PrimitiveValue)>);
pub struct MatchCriteria(Vec<(AttributeSelector, PrimitiveValue)>);

impl MatchCriteria {
pub fn into_inner(self) -> Vec<(Tag, PrimitiveValue)> {
pub fn into_inner(self) -> Vec<(AttributeSelector, PrimitiveValue)> {
self.0
}
}
Expand All @@ -51,15 +52,15 @@
type Error = String;

fn try_from(value: HashMap<String, String>) -> Result<Self, Self::Error> {
let criteria: Vec<(Tag, PrimitiveValue)> = value
let criteria: Vec<(AttributeSelector, PrimitiveValue)> = value
.into_iter()
.map(|(key, value)| {
StandardDataDictionary
.by_expr(&key)
.ok_or(format!("Cannot use unknown attribute {key} for matching."))
.and_then(|entry| {
to_primitive_value(entry, &value)
.map(|primitive| (entry.tag.inner(), primitive))
.parse_selector(&key)
.map_err(|err| format!("invalid attribute selector {key}: {err}"))
.and_then(|selector| {
to_primitive_value(selector.last_tag(), &value)
.map(|primitive| (selector, primitive))
})
})
.collect::<Result<_, Self::Error>>()?;
Expand All @@ -68,14 +69,15 @@
}

/// helper function to convert a query parameter value to a PrimitiveValue
fn to_primitive_value(
entry: &DataDictionaryEntryRef,
raw_value: &str,
) -> Result<PrimitiveValue, String> {
fn to_primitive_value(tag: Tag, raw_value: &str) -> Result<PrimitiveValue, String> {
if raw_value.is_empty() {
return Ok(PrimitiveValue::Empty);
}
match entry.vr.relaxed() {
let vr = StandardDataDictionary
.by_tag(tag)
.ok_or_else(|| format!("unknown tag {tag}"))?
.vr();
match vr.relaxed() {
// String-like VRs, no parsing required
VR::AE
| VR::AS
Expand Down Expand Up @@ -132,11 +134,10 @@
let value = raw_value.parse::<f64>().map_err(|err| err.to_string())?;
Ok(PrimitiveValue::from(value))
}
_ => Err(format!(
"Attribute {} cannot be used for matching due to unsupported VR {}",
entry.tag(),
entry.vr.relaxed()
"Attribute {} cannot be used for matching due to unsupported VR {:?}",
tag, vr
)),

Check warning

Code scanning / clippy

variables can be used directly in the format! string Warning

variables can be used directly in the format! string

Check warning

Code scanning / clippy

variables can be used directly in the format! string Warning

variables can be used directly in the format! string
}
}

Expand Down
26 changes: 25 additions & 1 deletion src/api/mwl/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ pub enum MwlSearchError {
mod tests {
use axum::extract::Query;
use axum::http::Uri;
use dicom::core::ops::AttributeSelector;
use dicom::core::PrimitiveValue;
use dicom::dictionary_std::tags;

Expand All @@ -100,14 +101,37 @@ mod tests {
limit: 42,
include_field: IncludeField::List(vec![tags::PATIENT_WEIGHT]),
match_criteria: MatchCriteria(vec![(
tags::PATIENT_NAME,
AttributeSelector::from(tags::PATIENT_NAME),
PrimitiveValue::from("MUSTERMANN^MAX")
)]),
fuzzy_matching: false,
}
);
}

#[test]
fn parse_query_params_nested() {
let uri = Uri::from_static("http://test?00400100.00400010=CTSCANNER");
let Query(params) = Query::<MwlQueryParameters>::try_from_uri(&uri).unwrap();

assert_eq!(
params,
MwlQueryParameters {
offset: 0,
limit: 200,
include_field: IncludeField::List(vec![]),
match_criteria: MatchCriteria(vec![(
AttributeSelector::from((
tags::SCHEDULED_PROCEDURE_STEP_SEQUENCE,
tags::SCHEDULED_STATION_NAME
)),
PrimitiveValue::from("CTSCANNER")
)]),
fuzzy_matching: false,
}
);
}

#[test]
fn parse_query_params_multiple_includefield() {
let uri =
Expand Down
Loading
Loading