use jiff::Timestamp;
use secrecy::{ExposeSecret, SecretString};
use url::Url;
use super::error::SubscribeError;
use super::feed::{Event, Feed, PatientIdentity};
use ihe_atna::balp::{
APPLICATION, CREATE, CUSTODIAN, Coded, DELETE, DESTINATION_ROLE, DOMAIN_RESOURCE, Direction,
Entity, EventKind, Exchange, NetworkAddress, Outcome, Peer, READ, REST, SEARCH, SOURCE_ROLE,
SYSTEM_OBJECT, request_text,
};
use ihe_atna::user::OnBehalfOf;
pub const ITI_93: Coded = Coded::fixed(
ihe_atna::balp::IHE_TRANSACTIONS,
"ITI-93",
"Mobile Patient Identity Feed",
);
pub const ITI_94: Coded = Coded::fixed(
ihe_atna::balp::IHE_TRANSACTIONS,
"ITI-94",
"Subscribe to Patient Updates",
);
pub const PATIENT_RECORD: Coded = Coded::fixed(
"http://dicom.nema.org/resources/ontology/DCM",
"110110",
"Patient Record",
);
pub const MESSAGE_HEADER: Coded = Coded::fixed(
"http://hl7.org/fhir/resource-types",
"MessageHeader",
"MessageHeader",
);
#[must_use]
pub fn feed_kind() -> EventKind {
EventKind {
profile: "https://profiles.ihe.net/ITI/PMIR/StructureDefinition/IHE.PMIR.Feed.Audit",
event_type: PATIENT_RECORD,
subtypes: vec![ITI_93],
action: "E",
client: SOURCE_ROLE,
server: DESTINATION_ROLE,
}
}
#[must_use]
pub fn subscription_create() -> EventKind {
EventKind {
profile: "https://profiles.ihe.net/ITI/PMIR/StructureDefinition/IHE.PMIR.Audit.Subscription.Create",
event_type: REST,
subtypes: vec![CREATE, ITI_94],
action: "C",
client: SOURCE_ROLE,
server: DESTINATION_ROLE,
}
}
#[must_use]
pub fn subscription_read() -> EventKind {
EventKind {
profile: "https://profiles.ihe.net/ITI/PMIR/StructureDefinition/IHE.PMIR.Audit.Subscription.Read",
event_type: REST,
subtypes: vec![ITI_94, READ],
action: "R",
client: DESTINATION_ROLE,
server: SOURCE_ROLE,
}
}
#[must_use]
pub fn subscription_delete() -> EventKind {
EventKind {
profile: "https://profiles.ihe.net/ITI/PMIR/StructureDefinition/IHE.PMIR.Audit.Subscription.Delete",
event_type: REST,
subtypes: vec![DELETE, ITI_94],
action: "D",
client: APPLICATION,
server: CUSTODIAN,
}
}
#[must_use]
pub fn subscription_search() -> EventKind {
EventKind {
profile: "https://profiles.ihe.net/ITI/BALP/StructureDefinition/IHE.BasicAudit.Query",
event_type: REST,
subtypes: vec![SEARCH],
action: "E",
client: SOURCE_ROLE,
server: DESTINATION_ROLE,
}
}
fn outcome<T>(result: &Result<T, SubscribeError>) -> Outcome {
match result {
Ok(_) => Outcome::Success,
Err(error) if error.answered() => Outcome::MinorFailure,
Err(_) => Outcome::SeriousFailure,
}
}
fn registry(subscriptions: &Url) -> Peer {
Peer::server(
&subscriptions
.join(".")
.unwrap_or_else(|_| subscriptions.clone()),
)
}
pub(super) fn subscription_reference(location: &Url) -> Option<String> {
location
.path_segments()
.and_then(|mut segments| segments.next_back())
.filter(|id| !id.is_empty())
.map(|id| format!("Subscription/{id}"))
}
pub(super) fn subscription<T>(
kind: EventKind,
subscriptions: &Url,
reference: Option<String>,
criteria: Option<String>,
result: &Result<T, SubscribeError>,
) -> Exchange {
Exchange {
kind,
recorded: Timestamp::now(),
outcome: outcome(result),
outcome_description: None,
direction: Direction::Sent {
server: registry(subscriptions),
},
on_behalf: OnBehalfOf::System,
entities: vec![Entity::Resource {
reference,
resource_type: "Subscription",
kind: SYSTEM_OBJECT,
role: Some(DOMAIN_RESOURCE),
name: None,
query: criteria,
}],
security: Vec::new(),
}
}
pub(super) fn search<T>(
subscriptions: &Url,
request: &Url,
result: &Result<T, SubscribeError>,
) -> Exchange {
Exchange {
kind: subscription_search(),
recorded: Timestamp::now(),
outcome: outcome(result),
outcome_description: None,
direction: Direction::Sent {
server: registry(subscriptions),
},
on_behalf: OnBehalfOf::System,
entities: vec![Entity::Query(request_text(request))],
security: Vec::new(),
}
}
#[must_use]
pub fn received(registry: &Url, endpoint: &Url, feed: Option<&Feed>) -> Exchange {
let mut entities = Vec::new();
if let Some(feed) = feed {
for event in feed.events() {
match event {
Event::Created(identity) | Event::Updated(identity) | Event::Deleted(identity) => {
entities.extend(patient(identity));
}
Event::Merged {
subsumed,
surviving,
} => {
entities.extend(patient(subsumed));
entities.push(Entity::PatientReference(surviving.clone()));
}
}
}
entities.push(Entity::Resource {
reference: Some(format!("MessageHeader/{}", feed.message_id())),
resource_type: "MessageHeader",
kind: MESSAGE_HEADER,
role: None,
name: Some(super::message::FEED_EVENT),
query: None,
});
}
Exchange {
kind: feed_kind(),
recorded: Timestamp::now(),
outcome: if feed.is_some() {
Outcome::Success
} else {
Outcome::MinorFailure
},
outcome_description: None,
direction: Direction::Received {
client: Peer::server(registry),
endpoint: NetworkAddress::uri(endpoint),
},
on_behalf: OnBehalfOf::System,
entities,
security: Vec::new(),
}
}
fn patient(identity: &PatientIdentity) -> Option<Entity> {
if let Some(id) = identity.id() {
return Some(Entity::PatientReference(SecretString::from(format!(
"Patient/{}",
id.expose_secret()
))));
}
identity.identifiers().iter().find_map(|identifier| {
identifier.system().map(|system| Entity::Patient {
system: Some(system.to_owned()),
namespace: None,
value: identifier.value().clone(),
})
})
}