use dicom_toolkit_data::DataSet;
pub const STATUS_SUCCESS: u16 = 0x0000;
pub const STATUS_PENDING: u16 = 0xFF00;
pub const STATUS_OUT_OF_RESOURCES: u16 = 0xA700;
pub const STATUS_DATASET_MISMATCH: u16 = 0xA900;
pub const STATUS_PROCESSING_FAILURE: u16 = 0x0110;
pub const STATUS_UNRECOGNISED_OPERATION: u16 = 0x0211;
pub const STATUS_MOVE_DESTINATION_UNKNOWN: u16 = 0xA801;
pub const STATUS_WARNING: u16 = 0xB000;
#[derive(Debug, Clone)]
pub struct StoreEvent {
pub calling_ae: String,
pub sop_class_uid: String,
pub sop_instance_uid: String,
pub dataset: DataSet,
}
#[derive(Debug, Clone)]
pub struct StoreResult {
pub status: u16,
}
impl StoreResult {
pub fn success() -> Self {
Self {
status: STATUS_SUCCESS,
}
}
pub fn failure(status: u16) -> Self {
Self { status }
}
}
pub trait StoreServiceProvider: Send + Sync + 'static {
fn on_store(&self, event: StoreEvent) -> impl std::future::Future<Output = StoreResult> + Send;
}
#[derive(Debug, Clone)]
pub struct FindEvent {
pub calling_ae: String,
pub sop_class_uid: String,
pub identifier: DataSet,
}
pub trait FindServiceProvider: Send + Sync + 'static {
fn on_find(&self, event: FindEvent) -> impl std::future::Future<Output = Vec<DataSet>> + Send;
}
#[derive(Debug, Clone)]
pub struct RetrieveItem {
pub sop_class_uid: String,
pub sop_instance_uid: String,
pub dataset: Vec<u8>,
}
#[derive(Debug, Clone)]
pub struct GetEvent {
pub calling_ae: String,
pub sop_class_uid: String,
pub identifier: DataSet,
}
pub trait GetServiceProvider: Send + Sync + 'static {
fn on_get(
&self,
event: GetEvent,
) -> impl std::future::Future<Output = Vec<RetrieveItem>> + Send;
}
#[derive(Debug, Clone)]
pub struct MoveEvent {
pub calling_ae: String,
pub destination: String,
pub sop_class_uid: String,
pub identifier: DataSet,
}
pub trait MoveServiceProvider: Send + Sync + 'static {
fn on_move(
&self,
event: MoveEvent,
) -> impl std::future::Future<Output = Vec<RetrieveItem>> + Send;
}
pub trait DestinationLookup: Send + Sync + 'static {
fn lookup(&self, ae_title: &str) -> Option<String>;
}
pub struct StaticDestinationLookup {
entries: Vec<(String, String)>,
}
impl StaticDestinationLookup {
pub fn new(entries: Vec<(String, String)>) -> Self {
Self { entries }
}
}
impl DestinationLookup for StaticDestinationLookup {
fn lookup(&self, ae_title: &str) -> Option<String> {
let upper = ae_title.trim().to_uppercase();
self.entries
.iter()
.find(|(ae, _)| ae.trim().to_uppercase() == upper)
.map(|(_, addr)| addr.clone())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn static_destination_lookup_found() {
let lookup = StaticDestinationLookup::new(vec![
("STORESCP".to_string(), "127.0.0.1:4242".to_string()),
("ARCHIVE".to_string(), "10.0.0.1:11112".to_string()),
]);
assert_eq!(
lookup.lookup("STORESCP"),
Some("127.0.0.1:4242".to_string())
);
assert_eq!(lookup.lookup("ARCHIVE"), Some("10.0.0.1:11112".to_string()));
}
#[test]
fn static_destination_lookup_not_found() {
let lookup = StaticDestinationLookup::new(vec![]);
assert_eq!(lookup.lookup("UNKNOWN"), None);
}
#[test]
fn static_destination_lookup_case_insensitive() {
let lookup = StaticDestinationLookup::new(vec![(
"StOreScp".to_string(),
"127.0.0.1:4242".to_string(),
)]);
assert_eq!(
lookup.lookup("storescp"),
Some("127.0.0.1:4242".to_string())
);
}
#[test]
fn store_result_success() {
let r = StoreResult::success();
assert_eq!(r.status, STATUS_SUCCESS);
}
#[test]
fn store_result_failure() {
let r = StoreResult::failure(STATUS_OUT_OF_RESOURCES);
assert_eq!(r.status, STATUS_OUT_OF_RESOURCES);
}
}