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
6 changes: 3 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ target/
# VSCode
.vscode/

# Fleet
.fleet/
# Zed editor
.zed/

# Avoid adding config files that overwrite the default config
config.toml
config.toml
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ nursery = "warn"
cargo = "warn"
module_name_repetitions = "allow"
must_use_candidate = "allow"
lint_groups_priority = "allow"

[profile.release]
codegen-units = 1
Expand Down
4 changes: 2 additions & 2 deletions src/api/aets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ async fn all_aets(state: State<AppState>) -> impl IntoResponse {
let aets = &state.config.aets;

Json(serde_json::Value::Array(
aets.into_iter()
.map(|ae| serde_json::Value::String(ae.aet.to_owned()))
aets.iter()
.map(|ae| serde_json::Value::String(ae.aet.clone()))
.collect::<Vec<serde_json::Value>>(),
))
}
Expand Down
5 changes: 2 additions & 3 deletions src/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ impl TryFrom<HashMap<String, String>> for MatchCriteria {
}
}

/// helper function to convert a query parameter value to a PrimitiveValue
/// helper function to convert a query parameter value to a `PrimitiveValue`
fn to_primitive_value(tag: Tag, raw_value: &str) -> Result<PrimitiveValue, String> {
if raw_value.is_empty() {
return Ok(PrimitiveValue::Empty);
Expand Down Expand Up @@ -135,8 +135,7 @@ fn to_primitive_value(tag: Tag, raw_value: &str) -> Result<PrimitiveValue, Strin
Ok(PrimitiveValue::from(value))
}
_ => Err(format!(
"Attribute {} cannot be used for matching due to unsupported VR {:?}",
tag, vr
"Attribute {tag} cannot be used for matching due to unsupported VR {vr:?}",
)),
}
}
Expand Down
7 changes: 2 additions & 5 deletions src/api/mwl/routes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use dicom_json::DicomJson;
use futures::TryStreamExt;
use tracing::instrument;

use super::{MwlQueryParameters, MwlRequestHeaderFields, MwlSearchError, MwlSearchRequest};
use super::{MwlQueryParameters, MwlSearchError, MwlSearchRequest};

/// HTTP Router for the Modality Worklist.
///
Expand Down Expand Up @@ -58,9 +58,6 @@ async fn all_workitems(
provider: ServiceProvider,
Query(parameters): Query<MwlQueryParameters>,
) -> impl IntoResponse {
let request = MwlSearchRequest {
parameters,
headers: MwlRequestHeaderFields::default(),
};
let request = MwlSearchRequest { parameters };
mwl_handler(provider, request).await
}
20 changes: 0 additions & 20 deletions src/api/mwl/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ pub trait MwlService: Send + Sync {

pub struct MwlSearchRequest {
pub parameters: MwlQueryParameters,
pub headers: MwlRequestHeaderFields,
}

/// Query parameters for a MWL-RS request.
Expand Down Expand Up @@ -48,25 +47,6 @@ impl Default for MwlQueryParameters {
}
}

#[derive(Debug, Default)]
pub struct MwlRequestHeaderFields {
pub accept: Option<String>,
pub accept_charset: Option<String>,
}

/// <https://dicom.nema.org/medical/dicom/current/output/chtml/part18/sect_10.6.3.2.html#table_10.6.3-2>
#[derive(Debug, Default)]
pub struct ResponseHeaderFields {
/// The DICOM Media Type of the response payload.
/// Shall be present if the response has a payload.
pub content_type: Option<String>,
/// Shall be present if no transfer coding has been applied to the payload.
pub content_length: Option<usize>,
/// Shall be present if a transfer encoding has been applied to the payload.
pub transfer_encoding: Option<String>,
pub warning: Vec<String>,
}

pub struct MwlSearchResponse<'a> {
pub stream: BoxStream<'a, Result<InMemDicomObject, MwlSearchError>>,
}
Expand Down
11 changes: 1 addition & 10 deletions src/api/qido/routes.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
use crate::api::qido::{
QueryParameters, RequestHeaderFields, ResourceQuery, SearchError, SearchRequest,
};
use crate::api::qido::{QueryParameters, ResourceQuery, SearchError, SearchRequest};
use crate::backend::ServiceProvider;
use crate::types::QueryRetrieveLevel;
use crate::AppState;
Expand All @@ -15,7 +13,6 @@ use axum_streams::StreamBodyAs;
use dicom::object::InMemDicomObject;
use dicom_json::DicomJson;
use futures::TryStreamExt;
use std::default::Default;
use tracing::instrument;

/// HTTP Router for the Search Transaction.
Expand Down Expand Up @@ -74,7 +71,6 @@ async fn all_studies(
series_instance_uid: None,
},
parameters,
headers: RequestHeaderFields::default(),
};
qido_handler(provider, request).await
}
Expand All @@ -92,7 +88,6 @@ async fn studys_series(
series_instance_uid: None,
},
parameters,
headers: RequestHeaderFields::default(),
};
qido_handler(provider, request).await
}
Expand All @@ -110,7 +105,6 @@ async fn studys_series_instances(
series_instance_uid: Some(series),
},
parameters,
headers: RequestHeaderFields::default(),
};
qido_handler(provider, request).await
}
Expand All @@ -128,7 +122,6 @@ async fn studys_instances(
series_instance_uid: None,
},
parameters,
headers: RequestHeaderFields::default(),
};
qido_handler(provider, request).await
}
Expand All @@ -145,7 +138,6 @@ async fn all_series(
series_instance_uid: None,
},
parameters,
headers: RequestHeaderFields::default(),
};
qido_handler(provider, request).await
}
Expand All @@ -162,7 +154,6 @@ async fn all_instances(
series_instance_uid: None,
},
parameters,
headers: RequestHeaderFields::default(),
};
qido_handler(provider, request).await
}
20 changes: 0 additions & 20 deletions src/api/qido/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ pub trait QidoService: Send + Sync {
pub struct SearchRequest {
pub query: ResourceQuery,
pub parameters: QueryParameters,
pub headers: RequestHeaderFields,
}

/// Query parameters for a QIDO-RS request.
Expand Down Expand Up @@ -51,25 +50,6 @@ impl Default for QueryParameters {
}
}

#[derive(Debug, Default)]
pub struct RequestHeaderFields {
pub accept: Option<String>,
pub accept_charset: Option<String>,
}

/// <https://dicom.nema.org/medical/dicom/current/output/chtml/part18/sect_10.6.3.2.html#table_10.6.3-2>
#[derive(Debug, Default)]
pub struct ResponseHeaderFields {
/// The DICOM Media Type of the response payload.
/// Shall be present if the response has a payload.
pub content_type: Option<String>,
/// Shall be present if no transfer coding has been applied to the payload.
pub content_length: Option<usize>,
/// Shall be present if a transfer encoding has been applied to the payload.
pub transfer_encoding: Option<String>,
pub warning: Vec<String>,
}

pub struct SearchResponse<'a> {
pub stream: BoxStream<'a, Result<InMemDicomObject, SearchError>>,
}
Expand Down
39 changes: 17 additions & 22 deletions src/api/stow/routes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use multer::Error;
use tracing::{error, instrument, warn};

/// HTTP Router for the Store Transaction
/// https://dicom.nema.org/medical/dicom/current/output/html/part18.html#sect_10.5
/// <https://dicom.nema.org/medical/dicom/current/output/html/part18.html#sect_10.5>
pub fn routes() -> Router<AppState> {
Router::new()
.route("/studies", post(studies))
Expand All @@ -37,38 +37,33 @@ async fn studies(
instances.push(file);
}
Err(err) => {
let err = match &err {
Error::StreamReadFailed(stream_error) => {
let is_limit_exceeded = stream_error
.downcast_ref::<axum::Error>()
.and_then(std::error::Error::source)
.and_then(|err| err.downcast_ref::<LengthLimitError>())
.is_some();
let err = if let Error::StreamReadFailed(stream_error) = &err {
let is_limit_exceeded = stream_error
.downcast_ref::<axum::Error>()
.and_then(std::error::Error::source)
.and_then(|err| err.downcast_ref::<LengthLimitError>())
.is_some();

if is_limit_exceeded {
warn!("Upload limit exceeded.");
StoreError::UploadLimitExceeded
} else {
error!("Failed to read multipart stream: {err:?}");
StoreError::Stream(err)
}
}
_ => {
error!("Failed to read multipart stream: {:?}", err);
if is_limit_exceeded {
warn!("Upload limit exceeded.");
StoreError::UploadLimitExceeded
} else {
error!("Failed to read multipart stream: {err:?}");
StoreError::Stream(err)
}
} else {
error!("Failed to read multipart stream: {:?}", err);
StoreError::Stream(err)
};
return (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()).into_response();
}
};
}

let request = StoreRequest {
instances,
study_instance_uid: None, // TODO
};
let request = StoreRequest { instances };

if let Some(stow) = provider.stow {
#[allow(clippy::option_if_let_else)]
if let Ok(response) = stow.store(request).await {
let json = DicomJson::from(InMemDicomObject::from(response));

Expand Down
5 changes: 0 additions & 5 deletions src/api/stow/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ use thiserror::Error;

pub struct StoreRequest {
pub instances: Vec<FileDicomObject<InMemDicomObject>>,
pub study_instance_uid: Option<UI>,
}

/// <https://dicom.nema.org/medical/dicom/current/output/html/part03.html#table_10-11>
Expand Down Expand Up @@ -89,10 +88,6 @@ pub trait StowService: Sync + Send {

#[derive(Debug, Error)]
pub enum StoreError {
#[error("Instance does not match the provided Study Instance UID")]
StudyInstanceUidMismatch { study_instance_uid: String },
#[error("The media type {media_type} is not supported")]
UnsupportedMediaType { media_type: String },
#[error("The file exceeds the configured upload size limit")]
UploadLimitExceeded,
#[error(transparent)]
Expand Down
28 changes: 8 additions & 20 deletions src/api/wado/routes.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
use crate::api::wado::{
MetadataRequest, RenderedResponse, RenderingRequest, RetrieveError, RetrieveInstanceRequest,
ThumbnailRequest,
MetadataRequest, RenderedResponse, RenderingRequest, RetrieveInstanceRequest, ThumbnailRequest,
};
use crate::backend::dimse::cmove::movescu::MoveError;
use crate::backend::dimse::wado::DicomMultipartStream;
use crate::backend::ServiceProvider;
use crate::types::UI;
use crate::AppState;
use axum::body::Body;
use axum::extract::State;
use axum::http::header::{CONTENT_DISPOSITION, CONTENT_TYPE};
use axum::http::{Response, StatusCode, Uri};
use axum::response::{IntoResponse, Redirect};
Expand All @@ -26,7 +24,7 @@ use std::sync::Arc;
use tracing::{error, instrument};

/// HTTP Router for the Retrieve Transaction
/// https://dicom.nema.org/medical/dicom/current/output/html/part18.html#sect_10.4
/// <https://dicom.nema.org/medical/dicom/current/output/html/part18.html#sect_10.4>
#[rustfmt::skip]
pub fn routes() -> Router<AppState> {
Router::new()
Expand Down Expand Up @@ -135,7 +133,6 @@ async fn rendered_resource(
async fn metadata_resource(
provider: ServiceProvider,
request: MetadataRequest,
state: &AppState,
) -> impl IntoResponse {
let Some(wado) = provider.wado else {
return Response::builder()
Expand Down Expand Up @@ -179,7 +176,7 @@ async fn metadata_resource(
}
}

#[derive(Debug, Clone, PartialEq)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BulkdataRemovalOptions {
pub max_length: u32,
}
Expand Down Expand Up @@ -255,28 +252,19 @@ async fn instance(
instance_resource(provider, request).await
}

async fn study_metadata(
provider: ServiceProvider,
request: MetadataRequest,
State(state): State<AppState>,
) -> impl IntoResponse {
metadata_resource(provider, request, &state).await
async fn study_metadata(provider: ServiceProvider, request: MetadataRequest) -> impl IntoResponse {
metadata_resource(provider, request).await
}

async fn series_metadata(
provider: ServiceProvider,
request: MetadataRequest,
State(state): State<AppState>,
) -> impl IntoResponse {
metadata_resource(provider, request, &state).await
async fn series_metadata(provider: ServiceProvider, request: MetadataRequest) -> impl IntoResponse {
metadata_resource(provider, request).await
}

async fn instance_metadata(
provider: ServiceProvider,
request: MetadataRequest,
State(state): State<AppState>,
) -> impl IntoResponse {
metadata_resource(provider, request, &state).await
metadata_resource(provider, request).await
}

#[instrument(skip_all)]
Expand Down
Loading
Loading