use crate::entity::*;
use crate::error::Error;
use crate::models::*;
use crate::utils::check_http_error;
use crate::Result;
use bytes::Bytes;
use reqwest;
use serde_json::json;
use serde_json::Value;
use std::collections::HashMap;
use std::convert::TryFrom;
use std::io::prelude::*;
use std::str;
use std::time;
#[derive(Debug)]
pub struct Client {
server: String,
username: Option<String>,
password: Option<String>,
client: reqwest::blocking::Client,
}
impl Client {
pub fn new(server: impl Into<String>) -> Client {
let client = reqwest::blocking::ClientBuilder::new()
.timeout(time::Duration::from_secs(600))
.build()
.unwrap();
Client {
server: server.into(),
username: None,
password: None,
client,
}
}
pub fn auth(
mut self,
username: impl Into<String>,
password: impl Into<String>,
) -> Client {
self.username = Some(username.into());
self.password = Some(password.into());
self
}
fn add_auth(
&self,
request: reqwest::blocking::RequestBuilder,
) -> reqwest::blocking::RequestBuilder {
match (&self.username, &self.password) {
(Some(u), Some(p)) => request.basic_auth(u, Some(p)),
_ => request,
}
}
fn get(&self, path: &str) -> Result<Bytes> {
let url = format!("{}/{}", self.server, &path);
let mut request = self.client.get(&url);
request = self.add_auth(request);
let resp = request.send()?;
let status = resp.status();
let body = resp.bytes()?;
check_http_error(status, body)
}
fn get_stream<W: Write>(&self, path: &str, mut writer: W) -> Result<()> {
let url = format!("{}/{}", self.server, &path);
let mut request = self.client.get(&url);
request = self.add_auth(request);
let mut resp = request.send()?;
let status = resp.status();
if status >= reqwest::StatusCode::BAD_REQUEST {
let message = format!("API error: {}", status);
let body = resp.bytes()?;
if body.is_empty() {
return Err(Error::new(&message, None));
};
return Err(Error::new(&message, serde_json::from_slice(&body)?));
}
resp.copy_to(&mut writer)?;
Ok(())
}
fn post(&self, path: &str, data: Option<Value>) -> Result<Bytes> {
let url = format!("{}/{}", self.server, path);
let mut request = self.client.post(&url);
if let Some(d) = data {
request = request.json(&d);
}
request = self.add_auth(request);
let resp = request.send()?;
let status = resp.status();
let body = resp.bytes()?;
check_http_error(status, body)
}
fn post_receive_stream<W: Write>(
&self,
path: &str,
data: Value,
mut writer: W,
) -> Result<()> {
let url = format!("{}/{}", self.server, path);
let mut request = self.client.post(&url).json(&data);
request = self.add_auth(request);
let mut resp = request.send()?;
let status = resp.status();
if status >= reqwest::StatusCode::BAD_REQUEST {
let message = format!("API error: {}", status);
let body = resp.bytes()?;
if body.is_empty() {
return Err(Error::new(&message, None));
};
return Err(Error::new(&message, serde_json::from_slice(&body)?));
}
resp.copy_to(&mut writer)?;
Ok(())
}
fn post_bytes(&self, path: &str, data: &[u8]) -> Result<Bytes> {
let url = format!("{}/{}", self.server, path);
let mut request = self.client.post(&url).body(data.to_vec());
request = self.add_auth(request);
let resp = request.send()?;
let status = resp.status();
let body = resp.bytes()?;
check_http_error(status, body)
}
fn put(&self, path: &str, data: Value) -> Result<Bytes> {
let url = format!("{}/{}", self.server, path);
let mut request = self.client.put(&url).json(&data);
request = self.add_auth(request);
let resp = request.send()?;
let status = resp.status();
let body = resp.bytes()?;
check_http_error(status, body)
}
fn delete(&self, path: &str) -> Result<Bytes> {
let url = format!("{}/{}", self.server, &path);
let mut request = self.client.delete(&url);
request = self.add_auth(request);
let resp = request.send()?;
let status = resp.status();
let body = resp.bytes()?;
check_http_error(status, body)
}
fn list(&self, entity: &str) -> Result<Vec<String>> {
let resp = self.get(entity)?;
let json: Vec<String> = serde_json::from_slice(&resp)?;
Ok(json)
}
fn anonymize(
&self,
entity: &str,
id: &str,
anonymization: Option<Anonymization>,
) -> Result<ModificationResult> {
let data = match anonymization {
Some(a) => a,
None => Anonymization {
replace: None,
keep: None,
keep_private_tags: None,
dicom_version: None,
force: None,
},
};
let resp = self.post(
&format!("{}/{}/anonymize", entity, id),
Some(serde_json::to_value(data)?),
)?;
let json: ModificationResult = serde_json::from_slice(&resp)?;
Ok(json)
}
fn modify(
&self,
entity: &str,
id: &str,
modification: Modification,
) -> Result<ModificationResult> {
let resp = self.post(
&format!("{}/{}/modify", entity, id),
Some(serde_json::to_value(modification)?),
)?;
let json: ModificationResult = serde_json::from_slice(&resp)?;
Ok(json)
}
pub fn modalities(&self) -> Result<Vec<String>> {
self.list("modalities")
}
pub fn modalities_expanded(&self) -> Result<HashMap<String, Modality>> {
let resp = self.get("modalities?expand")?;
let json: HashMap<String, Modality> = serde_json::from_slice(&resp)?;
Ok(json)
}
pub fn create_modality(&self, name: &str, modality: Modality) -> Result<()> {
self.put(
&format!("modalities/{}", name),
serde_json::to_value(modality)?,
)
.map(|_| ())
}
pub fn modify_modality(&self, name: &str, modality: Modality) -> Result<()> {
self.put(
&format!("modalities/{}", name),
serde_json::to_value(modality)?,
)
.map(|_| ())
}
pub fn delete_modality(&self, name: &str) -> Result<()> {
self.delete(&format!("modalities/{}", name)).map(|_| ())
}
pub fn modality_echo(&self, modality: &str, timeout: Option<u32>) -> Result<()> {
let mut data = HashMap::new();
if let Some(to) = timeout {
data.insert("Timeout", to);
}
self.post(
&format!("modalities/{}/echo", modality),
Some(serde_json::json!(data)),
)
.map(|_| ())
}
#[deprecated(note = "Renamed to modality_echo", since = "0.8.0")]
pub fn echo(&self, modality: &str, timeout: Option<u32>) -> Result<()> {
self.modality_echo(modality, timeout)
}
pub fn modality_store(
&self,
modality: &str,
ids: &[&str],
) -> Result<ModalityStoreResult> {
let resp = self.post(
&format!("modalities/{}/store", modality),
Some(serde_json::json!(ids)),
)?;
let json: ModalityStoreResult = serde_json::from_slice(&resp)?;
Ok(json)
}
#[deprecated(note = "Renamed to modality_store", since = "0.8.0")]
pub fn store(&self, modality: &str, ids: &[&str]) -> Result<ModalityStoreResult> {
self.modality_store(modality, ids)
}
pub fn modality_move(&self, modality: &str, move_request: ModalityMove) -> Result<()> {
self.post(
&format!("modalities/{}/move", modality),
Some(serde_json::to_value(move_request)?),
)
.map(|_| ())
}
pub fn modality_find(
&self,
modality: &str,
level: EntityKind,
query: HashMap<String, String>,
normalize: Option<bool>,
) -> Result<ModalityFindResult> {
let body = ModalityFind {
level,
query,
normalize,
};
let resp = self.post(
&format!("modalities/{}/query", modality),
Some(serde_json::to_value(body)?),
)?;
let json: ModalityFindResult = serde_json::from_slice(&resp)?;
Ok(json)
}
pub fn peers(&self) -> Result<Vec<String>> {
self.list("peers")
}
pub fn peers_expanded(&self) -> Result<HashMap<String, Peer>> {
let resp = self.get("peers?expand")?;
let json: HashMap<String, Peer> = serde_json::from_slice(&resp)?;
Ok(json)
}
pub fn create_peer(&self, name: &str, peer: Peer) -> Result<()> {
self.put(&format!("peers/{}", name), serde_json::to_value(peer)?)
.map(|_| ())
}
pub fn modify_peer(&self, name: &str, peer: Peer) -> Result<()> {
self.put(&format!("peers/{}", name), serde_json::to_value(peer)?)
.map(|_| ())
}
pub fn delete_peer(&self, name: &str) -> Result<()> {
self.delete(&format!("peers/{}", name)).map(|_| ())
}
pub fn peer_store(&self, peer: &str, ids: &[&str]) -> Result<PeerStoreResult> {
let resp = self.post(
&format!("peers/{}/store", peer),
Some(serde_json::json!(ids)),
)?;
let json: PeerStoreResult = serde_json::from_slice(&resp)?;
Ok(json)
}
pub fn patients(&self) -> Result<Vec<String>> {
self.list("patients")
}
pub fn patients_expanded(&self) -> Result<Vec<Patient>> {
let resp = self.get("patients?expand")?;
let json: Vec<Patient> = serde_json::from_slice(&resp)?;
Ok(json)
}
pub fn patient(&self, id: &str) -> Result<Patient> {
let resp = self.get(&format!("patients/{}", id))?;
let json: Patient = serde_json::from_slice(&resp)?;
Ok(json)
}
pub fn patient_dicom<W: Write>(&self, id: &str, writer: W) -> Result<()> {
let path = format!("patients/{}/archive", id);
self.get_stream(&path, writer)
}
pub fn anonymize_patient(
&self,
id: &str,
anonymization: Option<Anonymization>,
) -> Result<ModificationResult> {
self.anonymize("patients", id, anonymization)
}
pub fn modify_patient(
&self,
id: &str,
modification: Modification,
) -> Result<ModificationResult> {
self.modify("patients", id, modification)
}
pub fn delete_patient(&self, id: &str) -> Result<RemainingAncestor> {
let resp = self.delete(&format!("patients/{}", id))?;
let json: RemainingAncestor = serde_json::from_slice(&resp)?;
Ok(json)
}
pub fn studies(&self) -> Result<Vec<String>> {
self.list("studies")
}
pub fn studies_expanded(&self) -> Result<Vec<Study>> {
let resp = self.get("studies?expand")?;
let json: Vec<Study> = serde_json::from_slice(&resp)?;
Ok(json)
}
pub fn study(&self, id: &str) -> Result<Study> {
let resp = self.get(&format!("studies/{}", id))?;
let json: Study = serde_json::from_slice(&resp)?;
Ok(json)
}
pub fn study_dicom<W: Write>(&self, id: &str, writer: W) -> Result<()> {
let path = format!("studies/{}/archive", id);
self.get_stream(&path, writer)?;
Ok(())
}
pub fn anonymize_study(
&self,
id: &str,
anonymization: Option<Anonymization>,
) -> Result<ModificationResult> {
self.anonymize("studies", id, anonymization)
}
pub fn modify_study(
&self,
id: &str,
modification: Modification,
) -> Result<ModificationResult> {
self.modify("studies", id, modification)
}
pub fn delete_study(&self, id: &str) -> Result<RemainingAncestor> {
let resp = self.delete(&format!("studies/{}", id))?;
let json: RemainingAncestor = serde_json::from_slice(&resp)?;
Ok(json)
}
pub fn series_list(&self) -> Result<Vec<String>> {
self.list("series")
}
pub fn series_expanded(&self) -> Result<Vec<Series>> {
let resp = self.get("series?expand")?;
let json: Vec<Series> = serde_json::from_slice(&resp)?;
Ok(json)
}
pub fn series(&self, id: &str) -> Result<Series> {
let resp = self.get(&format!("series/{}", id))?;
let json: Series = serde_json::from_slice(&resp)?;
Ok(json)
}
pub fn series_dicom<W: Write>(&self, id: &str, writer: W) -> Result<()> {
let path = format!("series/{}/archive", id);
self.get_stream(&path, writer)
}
pub fn anonymize_series(
&self,
id: &str,
anonymization: Option<Anonymization>,
) -> Result<ModificationResult> {
self.anonymize("series", id, anonymization)
}
pub fn modify_series(
&self,
id: &str,
modification: Modification,
) -> Result<ModificationResult> {
self.modify("series", id, modification)
}
pub fn delete_series(&self, id: &str) -> Result<RemainingAncestor> {
let resp = self.delete(&format!("series/{}", id))?;
let json: RemainingAncestor = serde_json::from_slice(&resp)?;
Ok(json)
}
pub fn instances(&self) -> Result<Vec<String>> {
self.list("instances")
}
pub fn instances_expanded(&self) -> Result<Vec<Instance>> {
let resp = self.get("instances?expand")?;
let json: Vec<Instance> = serde_json::from_slice(&resp)?;
Ok(json)
}
pub fn instance(&self, id: &str) -> Result<Instance> {
let resp = self.get(&format!("instances/{}", id))?;
let json: Instance = serde_json::from_slice(&resp)?;
Ok(json)
}
pub fn instance_tags(&self, id: &str) -> Result<Value> {
let resp = self.get(&format!("instances/{}/simplified-tags", id))?;
let json: Value = serde_json::from_slice(&resp)?;
Ok(json)
}
pub fn instance_tags_expanded(&self, id: &str) -> Result<Value> {
let resp = self.get(&format!("instances/{}/tags", id))?;
let json: Value = serde_json::from_slice(&resp)?;
Ok(json)
}
pub fn instance_content(&self, id: &str) -> Result<Vec<String>> {
let resp = self.get(&format!("instances/{}/content", id))?;
let json = serde_json::from_slice(&resp)?;
Ok(json)
}
pub fn instance_tag(&self, id: &str, tag: &str) -> Result<String> {
let resp = self.get(&format!("instances/{}/content/{}", id, tag))?;
Ok(String::from_utf8_lossy(&resp).trim().to_string())
}
pub fn instance_dicom<W: Write>(&self, id: &str, writer: W) -> Result<()> {
let path = format!("instances/{}/file", id);
self.get_stream(&path, writer)
}
pub fn anonymize_instance<W: Write>(
&self,
id: &str,
anonymization: Option<Anonymization>,
writer: W,
) -> Result<()> {
let data = match anonymization {
Some(a) => a,
None => Anonymization {
replace: None,
keep: None,
keep_private_tags: None,
dicom_version: None,
force: None,
},
};
self.post_receive_stream(
&format!("instances/{}/anonymize", id),
serde_json::to_value(data)?,
writer,
)?;
Ok(())
}
pub fn modify_instance<W: Write>(
&self,
id: &str,
modification: Modification,
writer: W,
) -> Result<()> {
self.post_receive_stream(
&format!("instances/{}/modify", id),
serde_json::to_value(modification)?,
writer,
)?;
Ok(())
}
pub fn delete_instance(&self, id: &str) -> Result<RemainingAncestor> {
let resp = self.delete(&format!("instances/{}", id))?;
let json: RemainingAncestor = serde_json::from_slice(&resp)?;
Ok(json)
}
pub fn queries(&self) -> Result<Vec<String>> {
self.list("queries")
}
pub fn query_level(&self, id: &str) -> Result<EntityKind> {
Ok(EntityKind::try_from(
self.get(&format!("queries/{}/level", id))?,
)?)
}
pub fn query_modality(&self, id: &str) -> Result<String> {
let resp = self.get(&format!("queries/{}/modality", id))?;
Ok(str::from_utf8(&resp)?.to_string())
}
pub fn query_query(&self, id: &str) -> Result<Value> {
let resp = self.get(&format!("queries/{}/query", id))?;
let json: Value = serde_json::from_slice(&resp)?;
Ok(json)
}
pub fn query_answers(&self, id: &str) -> Result<Vec<String>> {
let resp = self.get(&format!("queries/{}/answers", id))?;
let json: Vec<String> = serde_json::from_slice(&resp)?;
Ok(json)
}
pub fn query_answer(&self, id: &str, answer_id: &str) -> Result<Value> {
let resp = self.get(&format!("queries/{}/answers/{}/content", id, answer_id))?;
let json: Value = serde_json::from_slice(&resp)?;
Ok(json)
}
pub fn retrieve_query_answer(
&self,
id: &str,
answer_id: &str,
target_aet: Option<&str>,
) -> Result<()> {
self.post(
&format!("queries/{}/answers/{}/retrieve", id, answer_id),
target_aet.map(|t| {
json!(ModalityRetrieve {
target_aet: t.to_string()
})
}),
)
.map(|_| ())
}
pub fn retrieve_query_answers(&self, id: &str, target_aet: Option<&str>) -> Result<()> {
self.post(
&format!("queries/{}/retrieve", id),
target_aet.map(|t| {
json!(ModalityRetrieve {
target_aet: t.to_string()
})
}),
)
.map(|_| ())
}
pub fn system(&self) -> Result<System> {
let resp = self.get("system")?;
let json: System = serde_json::from_slice(&resp)?;
Ok(json)
}
pub fn upload(&self, data: &[u8]) -> Result<UploadResult> {
let resp = self.post_bytes("instances", data)?;
let json: UploadResult = serde_json::from_slice(&resp)?;
Ok(json)
}
pub fn search<T: Entity>(&self, query: HashMap<String, String>) -> Result<Vec<T>> {
let kind = T::kind();
let search = Search {
level: kind,
query,
expand: Some(true),
};
let resp = self.post("tools/find", Some(serde_json::to_value(search)?))?;
let json: Vec<T> = serde_json::from_slice(&resp)?;
Ok(json)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::error::ApiError;
use httpmock::{Method, Mock, MockServer};
use maplit::hashmap;
#[test]
fn test_default_fields() {
let cl = Client::new("http://localhost:8042");
assert_eq!(cl.server, "http://localhost:8042".to_string());
assert_eq!(cl.username, None);
assert_eq!(cl.password, None);
}
#[test]
fn test_auth() {
let cl = Client::new("http://localhost:8042").auth("foo", "bar");
assert_eq!(cl.username, Some("foo".to_string()));
assert_eq!(cl.password, Some("bar".to_string()));
}
#[test]
fn test_get() {
let mock_server = MockServer::start();
let url = mock_server.url("");
let m = Mock::new()
.expect_method(Method::GET)
.expect_path("/foo")
.expect_header("Authorization", "Basic Zm9vOmJhcg==")
.return_status(200)
.return_body("bar")
.create_on(&mock_server);
let cl = Client::new(url).auth("foo", "bar");
let resp = cl.get("foo").unwrap();
assert_eq!(resp, "bar");
assert_eq!(m.times_called(), 1);
}
#[test]
fn test_get_stream() {
let mock_server = MockServer::start();
let url = mock_server.url("");
let m = Mock::new()
.expect_method(Method::GET)
.expect_path("/foo")
.expect_header("Authorization", "Basic Zm9vOmJhcg==")
.return_status(200)
.return_body("bar")
.create_on(&mock_server);
let cl = Client::new(url).auth("foo", "bar");
let mut writer: Vec<u8> = vec![];
cl.get_stream("foo", &mut writer).unwrap();
assert_eq!(&writer, &b"bar");
assert_eq!(m.times_called(), 1);
}
#[test]
fn test_post() {
let mock_server = MockServer::start();
let url = mock_server.url("");
let m = Mock::new()
.expect_method(Method::POST)
.expect_path("/foo")
.expect_body("\"bar\"")
.expect_header("Authorization", "Basic Zm9vOmJhcg==")
.return_header("Content-Type", "application/json")
.return_status(200)
.return_body("baz")
.create_on(&mock_server);
let cl = Client::new(url).auth("foo", "bar");
let resp = cl.post("foo", Some(serde_json::json!("bar"))).unwrap();
assert_eq!(resp, "baz");
assert_eq!(m.times_called(), 1);
}
#[test]
fn test_post_no_body() {
let mock_server = MockServer::start();
let url = mock_server.url("");
let m = Mock::new()
.expect_method(Method::POST)
.expect_path("/foo")
.expect_header("Authorization", "Basic Zm9vOmJhcg==")
.return_header("Content-Type", "application/json")
.return_status(200)
.return_body("baz")
.create_on(&mock_server);
let cl = Client::new(url).auth("foo", "bar");
let resp = cl.post("foo", None).unwrap();
assert_eq!(resp, "baz");
assert_eq!(m.times_called(), 1);
}
#[test]
fn test_post_receive_stream() {
let mock_server = MockServer::start();
let url = mock_server.url("");
let m = Mock::new()
.expect_method(Method::POST)
.expect_path("/foo")
.expect_body("\"bar\"")
.expect_header("Authorization", "Basic Zm9vOmJhcg==")
.return_header("Content-Type", "application/json")
.return_status(200)
.return_body("baz")
.create_on(&mock_server);
let cl = Client::new(url).auth("foo", "bar");
let mut writer: Vec<u8> = vec![];
cl.post_receive_stream("foo", serde_json::json!("bar"), &mut writer)
.unwrap();
assert_eq!(&writer, &b"baz");
assert_eq!(m.times_called(), 1);
}
#[test]
fn test_post_bytes() {
let mock_server = MockServer::start();
let url = mock_server.url("");
let m = Mock::new()
.expect_method(Method::POST)
.expect_path("/foo")
.expect_body("bar")
.expect_header("Authorization", "Basic Zm9vOmJhcg==")
.return_header("Content-Type", "application/json")
.return_status(200)
.return_body("baz")
.create_on(&mock_server);
let cl = Client::new(url).auth("foo", "bar");
let resp = cl.post_bytes("foo", "bar".as_bytes()).unwrap();
assert_eq!(resp, "baz");
assert_eq!(m.times_called(), 1);
}
#[test]
fn test_delete() {
let mock_server = MockServer::start();
let url = mock_server.url("");
let m = Mock::new()
.expect_method(Method::DELETE)
.expect_path("/foo")
.expect_header("Authorization", "Basic Zm9vOmJhcg==")
.return_status(200)
.create_on(&mock_server);
let cl = Client::new(url).auth("foo", "bar");
let resp = cl.delete("foo").unwrap();
assert_eq!(resp, "");
assert_eq!(m.times_called(), 1);
}
#[test]
fn test_get_error_response() {
let mock_server = MockServer::start();
let url = mock_server.url("");
let m = Mock::new()
.expect_method(Method::GET)
.expect_path("/foo")
.return_status(400)
.return_body(
r#"
{
"Details" : "Cannot parse an invalid DICOM file (size: 12 bytes)",
"HttpError" : "Bad Request",
"HttpStatus" : 400,
"Message" : "Bad file format",
"Method" : "POST",
"OrthancError" : "Bad file format",
"OrthancStatus" : 15,
"Uri" : "/instances"
}
"#,
)
.create_on(&mock_server);
let cl = Client::new(url);
let resp = cl.get("foo");
assert_eq!(
resp.unwrap_err(),
Error {
message: "API error: 400 Bad Request".to_string(),
details: Some(ApiError {
method: "POST".to_string(),
uri: "/instances".to_string(),
message: "Bad file format".to_string(),
details: Some(
"Cannot parse an invalid DICOM file (size: 12 bytes)".to_string()
),
http_status: 400,
http_error: "Bad Request".to_string(),
orthanc_status: 15,
orthanc_error: "Bad file format".to_string(),
},),
},
);
assert_eq!(m.times_called(), 1);
}
#[test]
fn test_get_stream_error_response() {
let mock_server = MockServer::start();
let url = mock_server.url("");
let m = Mock::new()
.expect_method(Method::GET)
.expect_path("/foo")
.return_status(400)
.return_body(
r#"
{
"Details" : "Cannot parse an invalid DICOM file (size: 12 bytes)",
"HttpError" : "Bad Request",
"HttpStatus" : 400,
"Message" : "Bad file format",
"Method" : "POST",
"OrthancError" : "Bad file format",
"OrthancStatus" : 15,
"Uri" : "/instances"
}
"#,
)
.create_on(&mock_server);
let cl = Client::new(url);
let mut writer: Vec<u8> = vec![];
let resp = cl.get_stream("foo", &mut writer);
assert_eq!(
resp.unwrap_err(),
Error {
message: "API error: 400 Bad Request".to_string(),
details: Some(ApiError {
method: "POST".to_string(),
uri: "/instances".to_string(),
message: "Bad file format".to_string(),
details: Some(
"Cannot parse an invalid DICOM file (size: 12 bytes)".to_string()
),
http_status: 400,
http_error: "Bad Request".to_string(),
orthanc_status: 15,
orthanc_error: "Bad file format".to_string(),
},),
},
);
assert_eq!(m.times_called(), 1);
}
#[test]
fn test_get_stream_error_response_empty_body() {
let mock_server = MockServer::start();
let url = mock_server.url("");
let m = Mock::new()
.expect_method(Method::GET)
.expect_path("/foo")
.return_status(400)
.create_on(&mock_server);
let cl = Client::new(url);
let mut writer: Vec<u8> = vec![];
let resp = cl.get_stream("foo", &mut writer);
assert_eq!(
resp.unwrap_err(),
Error {
message: "API error: 400 Bad Request".to_string(),
details: None,
},
);
assert_eq!(m.times_called(), 1);
}
#[test]
fn test_post_error_response() {
let mock_server = MockServer::start();
let url = mock_server.url("");
let m = Mock::new()
.expect_method(Method::POST)
.expect_path("/foo")
.return_status(400)
.return_body(
r#"
{
"Details" : "Cannot parse an invalid DICOM file (size: 12 bytes)",
"HttpError" : "Bad Request",
"HttpStatus" : 400,
"Message" : "Bad file format",
"Method" : "POST",
"OrthancError" : "Bad file format",
"OrthancStatus" : 15,
"Uri" : "/instances"
}
"#,
)
.create_on(&mock_server);
let cl = Client::new(url);
let resp = cl.post("foo", Some(serde_json::json!("bar")));
assert_eq!(
resp.unwrap_err(),
Error {
message: "API error: 400 Bad Request".to_string(),
details: Some(ApiError {
method: "POST".to_string(),
uri: "/instances".to_string(),
message: "Bad file format".to_string(),
details: Some(
"Cannot parse an invalid DICOM file (size: 12 bytes)".to_string()
),
http_status: 400,
http_error: "Bad Request".to_string(),
orthanc_status: 15,
orthanc_error: "Bad file format".to_string(),
},),
},
);
assert_eq!(m.times_called(), 1);
}
#[test]
fn test_post_receive_stream_error_response() {
let mock_server = MockServer::start();
let url = mock_server.url("");
let m = Mock::new()
.expect_method(Method::POST)
.expect_path("/foo")
.return_status(400)
.return_body(
r#"
{
"Details" : "Cannot parse an invalid DICOM file (size: 12 bytes)",
"HttpError" : "Bad Request",
"HttpStatus" : 400,
"Message" : "Bad file format",
"Method" : "POST",
"OrthancError" : "Bad file format",
"OrthancStatus" : 15,
"Uri" : "/instances"
}
"#,
)
.create_on(&mock_server);
let cl = Client::new(url);
let mut writer: Vec<u8> = vec![];
let resp = cl.post_receive_stream("foo", serde_json::json!("bar"), &mut writer);
assert_eq!(
resp.unwrap_err(),
Error {
message: "API error: 400 Bad Request".to_string(),
details: Some(ApiError {
method: "POST".to_string(),
uri: "/instances".to_string(),
message: "Bad file format".to_string(),
details: Some(
"Cannot parse an invalid DICOM file (size: 12 bytes)".to_string()
),
http_status: 400,
http_error: "Bad Request".to_string(),
orthanc_status: 15,
orthanc_error: "Bad file format".to_string(),
},),
},
);
assert_eq!(m.times_called(), 1);
}
#[test]
fn test_post_receive_stream_error_response_empty_body() {
let mock_server = MockServer::start();
let url = mock_server.url("");
let m = Mock::new()
.expect_method(Method::POST)
.expect_path("/foo")
.return_status(400)
.create_on(&mock_server);
let cl = Client::new(url);
let mut writer: Vec<u8> = vec![];
let resp = cl.post_receive_stream("foo", serde_json::json!("bar"), &mut writer);
assert_eq!(
resp.unwrap_err(),
Error {
message: "API error: 400 Bad Request".to_string(),
details: None,
},
);
assert_eq!(m.times_called(), 1);
}
#[test]
fn test_post_bytes_error_response() {
let mock_server = MockServer::start();
let url = mock_server.url("");
let m = Mock::new()
.expect_method(Method::POST)
.expect_path("/foo")
.return_status(400)
.return_body(
r#"
{
"Details" : "Cannot parse an invalid DICOM file (size: 12 bytes)",
"HttpError" : "Bad Request",
"HttpStatus" : 400,
"Message" : "Bad file format",
"Method" : "POST",
"OrthancError" : "Bad file format",
"OrthancStatus" : 15,
"Uri" : "/instances"
}
"#,
)
.create_on(&mock_server);
let cl = Client::new(url);
let resp = cl.post_bytes("foo", &[13, 42, 17]);
assert_eq!(
resp.unwrap_err(),
Error {
message: "API error: 400 Bad Request".to_string(),
details: Some(ApiError {
method: "POST".to_string(),
uri: "/instances".to_string(),
message: "Bad file format".to_string(),
details: Some(
"Cannot parse an invalid DICOM file (size: 12 bytes)".to_string()
),
http_status: 400,
http_error: "Bad Request".to_string(),
orthanc_status: 15,
orthanc_error: "Bad file format".to_string(),
},),
},
);
assert_eq!(m.times_called(), 1);
}
#[test]
fn test_put() {
let mock_server = MockServer::start();
let url = mock_server.url("");
let m = Mock::new()
.expect_method(Method::PUT)
.expect_path("/foo")
.expect_body("\"bar\"")
.expect_header("Authorization", "Basic Zm9vOmJhcg==")
.return_header("Content-Type", "application/json")
.return_status(200)
.return_body("baz")
.create_on(&mock_server);
let cl = Client::new(url).auth("foo", "bar");
let resp = cl.put("foo", serde_json::json!("bar")).unwrap();
assert_eq!(resp, "baz");
assert_eq!(m.times_called(), 1);
}
#[test]
fn test_put_error_response() {
let mock_server = MockServer::start();
let url = mock_server.url("");
let m = Mock::new()
.expect_method(Method::PUT)
.expect_path("/foo")
.return_status(400)
.return_body(
r#"
{
"Details" : "Cannot parse an invalid DICOM file (size: 12 bytes)",
"HttpError" : "Bad Request",
"HttpStatus" : 400,
"Message" : "Bad file format",
"Method" : "POST",
"OrthancError" : "Bad file format",
"OrthancStatus" : 15,
"Uri" : "/instances"
}
"#,
)
.create_on(&mock_server);
let cl = Client::new(url);
let resp = cl.put("foo", serde_json::json!("bar"));
assert_eq!(
resp.unwrap_err(),
Error {
message: "API error: 400 Bad Request".to_string(),
details: Some(ApiError {
method: "POST".to_string(),
uri: "/instances".to_string(),
message: "Bad file format".to_string(),
details: Some(
"Cannot parse an invalid DICOM file (size: 12 bytes)".to_string()
),
http_status: 400,
http_error: "Bad Request".to_string(),
orthanc_status: 15,
orthanc_error: "Bad file format".to_string(),
},),
},
);
assert_eq!(m.times_called(), 1);
}
#[test]
fn test_delete_error_response() {
let mock_server = MockServer::start();
let url = mock_server.url("");
let m = Mock::new()
.expect_method(Method::DELETE)
.expect_path("/foo")
.return_status(400)
.return_body(
r#"
{
"Details" : "Cannot parse an invalid DICOM file (size: 12 bytes)",
"HttpError" : "Bad Request",
"HttpStatus" : 400,
"Message" : "Bad file format",
"Method" : "POST",
"OrthancError" : "Bad file format",
"OrthancStatus" : 15,
"Uri" : "/instances"
}
"#,
)
.create_on(&mock_server);
let cl = Client::new(url);
let resp = cl.delete("foo");
assert_eq!(
resp.unwrap_err(),
Error {
message: "API error: 400 Bad Request".to_string(),
details: Some(ApiError {
method: "POST".to_string(),
uri: "/instances".to_string(),
message: "Bad file format".to_string(),
details: Some(
"Cannot parse an invalid DICOM file (size: 12 bytes)".to_string()
),
http_status: 400,
http_error: "Bad Request".to_string(),
orthanc_status: 15,
orthanc_error: "Bad file format".to_string(),
},),
},
);
assert_eq!(m.times_called(), 1);
}
#[test]
fn test_error_response_no_body() {
let mock_server = MockServer::start();
let url = mock_server.url("");
let m = Mock::new()
.expect_method(Method::GET)
.expect_path("/foo")
.return_status(404)
.create_on(&mock_server);
let cl = Client::new(url);
let resp = cl.get("foo");
assert!(resp.is_err());
assert_eq!(
resp.unwrap_err(),
Error {
message: "API error: 404 Not Found".to_string(),
details: None,
},
);
assert_eq!(m.times_called(), 1);
}
#[test]
fn test_list() {
let mock_server = MockServer::start();
let url = mock_server.url("");
let m = Mock::new()
.expect_method(Method::GET)
.expect_path("/foos")
.expect_header("Authorization", "Basic Zm9vOmJhcg==")
.return_status(200)
.return_body(r#"["bar", "baz", "qux"]"#)
.create_on(&mock_server);
let cl = Client::new(url).auth("foo", "bar");
let resp = cl.list("foos").unwrap();
assert_eq!(resp, vec!["bar", "baz", "qux"]);
assert_eq!(m.times_called(), 1);
}
#[test]
fn test_modify() {
let mock_server = MockServer::start();
let url = mock_server.url("");
let m = Mock::new()
.expect_method(Method::POST)
.expect_path("/studies/foo/modify")
.expect_json_body(&Modification {
replace: Some(hashmap! {"Tag1".to_string() => "value1".to_string()}),
remove: Some(vec!["Tag2".to_string()]),
force: None,
})
.return_status(200)
.return_body(
r#"
{
"ID": "86a3054b-32bb888a-e5f42e28-4b2e82d2-b1d7e14c",
"Path": "/studies/86a3054b-32bb888a-e5f42e28-4b2e82d2-b1d7e14c",
"PatientID": "86a3054b-32bb888a-e5f42e28-4b2e82d2-b1d7e14c",
"Type": "Study"
}
"#,
)
.create_on(&mock_server);
let cl = Client::new(url);
let resp = cl
.modify(
"studies",
"foo",
Modification {
replace: Some(hashmap! {"Tag1".to_string() => "value1".to_string()}),
remove: Some(vec!["Tag2".to_string()]),
force: None,
},
)
.unwrap();
assert_eq!(
resp,
ModificationResult {
id: "86a3054b-32bb888a-e5f42e28-4b2e82d2-b1d7e14c".to_string(),
patient_id: "86a3054b-32bb888a-e5f42e28-4b2e82d2-b1d7e14c".to_string(),
path: "/studies/86a3054b-32bb888a-e5f42e28-4b2e82d2-b1d7e14c".to_string(),
entity: EntityKind::Study
}
);
assert_eq!(m.times_called(), 1);
}
#[test]
fn test_anonymize() {
let mock_server = MockServer::start();
let url = mock_server.url("");
let m = Mock::new()
.expect_method(Method::POST)
.expect_path("/studies/foo/anonymize")
.expect_json_body(&Anonymization {
replace: Some(hashmap! {"Tag1".to_string() => "value1".to_string()}),
keep: Some(vec!["Tag2".to_string(), "Tag3".to_string()]),
keep_private_tags: None,
dicom_version: None,
force: None,
})
.return_status(200)
.return_body(
r#"
{
"ID": "86a3054b-32bb888a-e5f42e28-4b2e82d2-b1d7e14c",
"Path": "/studies/86a3054b-32bb888a-e5f42e28-4b2e82d2-b1d7e14c",
"PatientID": "86a3054b-32bb888a-e5f42e28-4b2e82d2-b1d7e14c",
"Type": "Study"
}
"#,
)
.create_on(&mock_server);
let cl = Client::new(url);
let resp = cl
.anonymize(
"studies",
"foo",
Some(Anonymization {
replace: Some(hashmap! {"Tag1".to_string() => "value1".to_string()}),
keep: Some(vec!["Tag2".to_string(), "Tag3".to_string()]),
keep_private_tags: None,
dicom_version: None,
force: None,
}),
)
.unwrap();
assert_eq!(
resp,
ModificationResult {
id: "86a3054b-32bb888a-e5f42e28-4b2e82d2-b1d7e14c".to_string(),
patient_id: "86a3054b-32bb888a-e5f42e28-4b2e82d2-b1d7e14c".to_string(),
path: "/studies/86a3054b-32bb888a-e5f42e28-4b2e82d2-b1d7e14c".to_string(),
entity: EntityKind::Study,
}
);
assert_eq!(m.times_called(), 1);
}
}