orthanc_api 0.0.2

Orthanc API hand-written response models
Documentation
//! Orthanc API request and response types.

mod dicom;
mod job;
mod types;

pub use crate::dicom::*;
pub use crate::job::*;
pub use crate::types::*;

#[cfg(test)]
mod tests {
    use super::*;
    use compact_str::{CompactString, ToCompactString};
    use pretty_assertions::assert_eq;
    use serde_json::json;

    #[test]
    fn test_uri_by_value() {
        let id = SeriesId::new("aa8e0a3c-8ddd1186-76a07503-f1b36dbe-c009f45d");
        let actual = id.uri();
        let expected = "/series/aa8e0a3c-8ddd1186-76a07503-f1b36dbe-c009f45d";
        assert_eq!(&actual, expected)
    }

    #[test]
    fn test_uri_by_reference() {
        let id = SeriesId::new("aa8e0a3c-8ddd1186-76a07503-f1b36dbe-c009f45d");
        let id_ref = &id;
        let actual = id_ref.uri();
        let expected = "/series/aa8e0a3c-8ddd1186-76a07503-f1b36dbe-c009f45d";
        assert_eq!(&actual, expected)
    }

    #[test]
    fn test_patient_no_requested_tags() {
        let data = json!({
            "ID": "f966c646-95f12576-9d667375-ca38e459-69a8468b",
            "IsProtected": false,
            "IsStable": true,
            "Labels": [],
            "LastUpdate": "20250704T010750",
            "MainDicomTags": {
                "PatientBirthDate": "20090701",
                "PatientID": "1449c1d",
                "PatientName": "anonymized",
                "PatientSex": "M"
            },
            "Studies": [
                "e1df078e-9b2bd072-b24090ad-2ad9c9c9-16c96754"
            ],
            "Type": "Patient"
        });
        let actual: <PatientId as ResourceId>::Item = serde_json::from_value(data).unwrap();
        assert_eq!(actual.requested_tags, None);
    }

    #[test]
    fn test_series_with_requested_tags() {
        let data = json!({
            "ExpectedNumberOfInstances": null,
            "ID": "aa8e0a3c-8ddd1186-76a07503-f1b36dbe-c009f45d",
            "Instances": [
                "00aee1d3-bdb5e57d-0c229af4-7c3c303c-86bb29a8",
                "019e09fd-6606258a-b42fcd26-662166da-6fbf3d11",
                "0327cc97-723b0f33-2e349452-f27ac737-e85d27c1",
                "035b8036-4eae909d-15fc59a7-50845ab0-1826e676",
                "0770dd57-685a8ae7-4b4e2a3c-04b87da6-2bb44abe",
                "0787f689-82839efa-f483986d-04a8c586-4e6154a7",
                "09ec3f9b-572388b8-8ad309c3-9530b36b-a78d517d",
                "0c4a65d5-49198fca-ca58729e-d9b1c8e3-e417d9ac",
            ],
            "IsStable": true,
            "Labels": [],
            "LastUpdate": "20250704T010750",
            "MainDicomTags": {
                "SeriesDate": "20130308",
                "SeriesDescription": "SAG MPRAGE 220 FOV",
                "SeriesInstanceUID": "1.3.12.2.1107.5.2.19.45152.2013030808061520200285270.0.0.0",
                "SeriesNumber": "5",
                "SeriesTime": "081102.762000",
                "StationName": "AWP45152"
            },
            "ParentStudy": "e1df078e-9b2bd072-b24090ad-2ad9c9c9-16c96754",
            "RequestedTags": {
                "BodyPartExamined": "BRAIN",
                "ProtocolName": "SAG MPRAGE 220 FOV"
            },
            "Status": "Unknown",
            "Type": "Series"
        });
        type SeriesWithTags = <SeriesId as DicomResourceId<BodyPartAndProtocol>>::Item;
        let actual: SeriesWithTags = serde_json::from_value(data).unwrap();
        let expected = BodyPartAndProtocol {
            body_part_examined: "BRAIN".to_compact_string(),
            protocol_name: "SAG MPRAGE 220 FOV".to_compact_string(),
        };
        assert_eq!(actual.requested_tags, expected);
    }

    #[derive(serde::Deserialize, Debug, PartialEq)]
    #[serde(rename_all = "PascalCase")]
    struct BodyPartAndProtocol {
        body_part_examined: CompactString,
        protocol_name: CompactString,
    }

    #[test]
    fn test_deserialize_job_retrieve_series() {
        let data = json!({
            "CompletionTime": "20250703T002648.691947",
            "Content": {
                "Description": "REST API",
                "LocalAet": "DEV",
                "Query": [
                    {
                        "0008,0050": "98edede8b2",
                        "0008,0052": "SERIES",
                        "0010,0020": "1449c1d",
                        "0020,000d": "1.2.840.113845.11.1000000001785349915.20130308061609.6346698",
                        "0020,000e": "1.3.12.2.1107.5.2.19.45152.2013030808061520200285270.0.0.0"
                    }
                ],
                "RemoteAet": "PACS",
                "TargetAet": "DEV"
            },
            "CreationTime": "20250703T002645.190848",
            "EffectiveRuntime": 3.5,
            "ErrorCode": 0,
            "ErrorDescription": "Success",
            "ErrorDetails": "",
            "ID": "0b09cfb2-d5c3-4340-9f96-0ae8812eadfe",
            "Priority": 0,
            "Progress": 100,
            "State": "Success",
            "Timestamp": "20250703T002655.833908",
            "Type": "DicomMoveScu"
        });
        let actual: JobInfo = serde_json::from_value(data).unwrap();
        let content = actual.content;
        let expected = JobContent::DicomMoveScu {
            description: CompactString::new("REST API"),
            local_aet: CompactString::new("DEV"),
            query: vec![MoveScuJobQuery::Series {
                patient_id: CompactString::new("1449c1d"),
                accession_number: CompactString::new("98edede8b2"),
                study_instance_uid: "1.2.840.113845.11.1000000001785349915.20130308061609.6346698"
                    .to_string(),
                series_instance_uid: "1.3.12.2.1107.5.2.19.45152.2013030808061520200285270.0.0.0"
                    .to_string(),
            }],
            remote_aet: CompactString::new("PACS"),
            target_aet: CompactString::new("DEV"),
        };
        assert_eq!(content, expected)
    }

    #[test]
    fn test_deserialize_resource_modification_study() {
        let value = json!({
            "CompletionTime": "20250707T134050.679818",
            "Content": {
                "Description": "REST API",
                "FailedInstancesCount": 0,
                "ID": "cfd54023-bf099aee-89d171ec-6602b46f-91058a39",
                "InstancesCount": 61,
                "IsAnonymization": true,
                "ParentResources": [
                    "11231bb6-426dbf6b-69897722-7ebab601-bd2c626c"
                ],
                "Path": "/studies/cfd54023-bf099aee-89d171ec-6602b46f-91058a39",
                "PatientID": "b9b65cc9-b7e4281d-8727a7f1-0b681068-0c2d3de8",
                "Type": "Study"
            },
            "CreationTime": "20250707T134048.977341",
            "EffectiveRuntime": 1.701,
            "ErrorCode": 0,
            "ErrorDescription": "Success",
            "ErrorDetails": "",
            "ID": "c304b4ec-43c9-418a-bebd-4f3a648015d5",
            "Priority": 0,
            "Progress": 100,
            "State": "Success",
            "Timestamp": "20250707T135101.755033",
            "Type": "ResourceModification"
        });
        let actual: JobInfo = serde_json::from_value(value).unwrap();
        let expected = ResourceModification {
            description: CompactString::new("REST API"),
            failed_instances_count: 0,
            id: StudyId::new("cfd54023-bf099aee-89d171ec-6602b46f-91058a39"),
            instances_count: 61,
            is_anonymization: true,
            parent_resources: vec![StudyId::new("11231bb6-426dbf6b-69897722-7ebab601-bd2c626c")],
            path: "/studies/cfd54023-bf099aee-89d171ec-6602b46f-91058a39".to_string(),
            patient_id: PatientId::new("b9b65cc9-b7e4281d-8727a7f1-0b681068-0c2d3de8"),
        };
        let expected_variant =
            JobContent::ResourceModification(ResourceModificationContent::Study(expected));
        assert_eq!(actual.content, expected_variant)
    }
}