use std::fmt;
use crate::record::{
Accessor, Agreement, Binding, Coded, IssuedId, PartyRef, Relayed, RelayedProfessional,
RelayedProvider,
};
pub const ACCESSOR_ENTITY: &str = "ehds-accessor";
pub const RELAYED_ENTITY: &str = "ehds-relayed";
pub const CATEGORY: &str = "ehds-category";
pub const BASIS: &str = "ehds-category-basis";
pub const CATEGORY_VERSION: &str = "ehds-category-version";
pub const NO_CATEGORY: &str = "ehds-no-category";
pub const UNCLASSIFIED: &str = "ehds-unclassified";
pub const TEMPLATE_ID: &str = "template-id";
pub const ARCHETYPE_ID: &str = "archetype-id";
pub const VERSION_UID: &str = "version-uid";
pub const UNMAPPED_ID: &str = "unmapped-id";
pub const TEMPLATE_ID_COUNT: &str = "template-id-count";
pub const ARCHETYPE_ID_COUNT: &str = "archetype-id-count";
pub const VERSION_UID_COUNT: &str = "version-uid-count";
pub const UNMAPPED_ID_COUNT: &str = "unmapped-id-count";
pub const NO_CATEGORY_KIND: &str = "ehds-no-category-kind";
pub const NO_CATEGORY_BASIS: &str = "ehds-no-category-basis";
pub const ORIGIN_COUNT: &str = "ehds-origin-count";
pub const MAP_DIGEST: &str = "category-map-digest";
pub const DELIVERED: &str = "delivered";
pub const STORED_QUERY: &str = "stored-query";
pub const NODE: &str = "node";
pub const SYSTEM_ID: &str = "system-id";
pub const STATUS: &str = "status";
pub const ROWS: &str = "rows";
pub const WRITTEN_VERSION: &str = "written-version";
pub const WRITTEN_VERSION_UNREAD: &str = "written-version-unread";
pub const CREATED_EHR: &str = "created-ehr";
pub const CREATED_EHR_UNREAD: &str = "created-ehr-unread";
pub const ENDPOINT: &str = "endpoint";
pub const PATIENT_LOOKUP: &str = "patient-lookup";
pub const CORRELATION: &str = "correlation-id";
pub const CORRELATION_SOURCE: &str = "correlation-source";
pub const RELAYED_VIA: &str = "relayed-via";
pub const RELAYED_ISSUER: &str = "relayed-issuer";
pub const ASSERTED: &str = "asserted";
pub const PROFESSIONAL_NAMED: &str = "professional-named";
pub const HP_NAME: &str = "hp-name";
pub const HP_PROVIDER_IDENTIFIER: &str = "hp-provider-identifier";
pub const HP_NAME_ID: &str = "hp-name-id";
pub const HP_AUTHENTICATION_METHOD: &str = "hp-authentication-method";
pub const HP_ON_BEHALF_OF: &str = "hp-on-behalf-of";
pub const HP_PERMISSION: &str = "hp-permission";
pub const PROVIDER_FACILITY_TYPE: &str = "provider-facility-type";
pub const PROVIDER_HOME_COMMUNITY_ID: &str = "provider-home-community-id";
pub const PROVIDER_ORGANISATION_TYPE: &str = "provider-organisation-type";
pub const POINT_OF_CARE: &str = "point-of-care";
pub const IDENTITY_ASSERTION_ID: &str = "identity-assertion-id";
pub const TRC_ASSERTION_ID: &str = "trc-assertion-id";
pub const REQUEST_MESSAGE_ID: &str = "request-message-id";
pub const CONSUMER_NCP: &str = "consumer-ncp";
pub const PROVIDER_NCP: &str = "provider-ncp";
pub const AGREEMENT_NAME: &str = "agreement-name";
pub const AGREEMENT_VERSION: &str = "agreement-version";
pub const AGREEMENT_LEGAL_GROUND: &str = "agreement-legal-ground";
pub const AGREEMENT_CONSENT: &str = "agreement-consent";
pub const AGREEMENT_ACCESS_CLASS: &str = "agreement-access-class";
pub const BINDING: &str = "binding";
pub const GUEST: &str = "guest";
pub const SECURITY_EVENT: &str = "security-event";
pub const PARTY: &str = "party";
pub const GROUP: &str = "group";
pub const BINDING_CLAIM: &str = "binding-claim";
pub const BINDING_FIELD: &str = "binding-field";
pub const ACTOR: &str = "actor";
pub const REPRESENTS: &str = "represents";
pub const REPRESENTS_GROUND: &str = "represents-ground";
pub const REPRESENTS_RELATIONSHIP: &str = "represents-relationship";
pub const ACTS_FOR: &str = "acts-for";
pub const FACILITY: &str = "facility";
pub const ASSERTED_FIELD: &str = "asserted-field";
pub const COUNTRY_CODE: &str = "country-code";
pub const FAMILY_NAME: &str = "hp-family-name";
pub const GIVEN_NAME: &str = "hp-given-name";
pub const HP_IDENTIFIER: &str = "hp-identifier";
pub const HP_ISSUING_AUTHORITY: &str = "hp-issuing-authority";
pub const HP_ROLE: &str = "hp-professional-role";
pub const PROVIDER_IDENTIFIER: &str = "provider-identifier";
pub const PROVIDER_ISSUING_AUTHORITY: &str = "provider-issuing-authority";
pub const PROVIDER_NAME: &str = "provider-name";
pub const PROVIDER_ADDRESS: &str = "provider-address";
pub const RETENTION_YEARS: &str = "ehds-retention-years";
pub const RETENTION_PERIOD: &str = "ehds-retention-period";
pub const RETENTION_FLOOR: &str = "ehds-retention-floor";
pub const RETENTION_ENDS: &str = "ehds-retention-ends";
pub const RETENTION_GROUND: &str = "ehds-retention-ground";
pub const EMERGENCY_ACCESS: &str = "ehds-emergency-access";
pub const EMERGENCY_PURPOSE: &str = "ehds-emergency-purpose";
pub const CONSENT_SET_ASIDE: &str = "ehds-consent-set-aside";
pub const POPULATION: &str = "ehds-population";
pub const POPULATION_REASON: &str = "ehds-population-reason";
pub const POPULATION_EHRS: &str = "ehds-population-ehrs";
pub const POPULATION_LIMIT: &str = "ehds-population-limit";
pub const TRANSFORMATION_DIRECTION: &str = "ehds-transformation-direction";
pub const TRANSFORMATION_CATEGORY: &str = "ehds-transformation-category";
pub const MAPPING_CONTEXT: &str = "mapping-context";
pub const MAPPING_TEMPLATE: &str = "mapping-template";
pub const MAPPING_PROFILE: &str = "mapping-profile";
pub const MAPPING_FILE: &str = "mapping-file";
pub const MAPPING_PACKAGE: &str = "mapping-package";
pub const TERMINOLOGY_VERSION: &str = "terminology-version";
pub const TRANSCODED: &str = "ehds-transcoded";
pub const TRANSCODED_PATH: &str = "ehds-transcoded-path";
pub const UNTRANSCODED: &str = "ehds-untranscoded";
pub const UNTRANSCODED_PATH: &str = "ehds-untranscoded-path";
pub const DROPPED: &str = "ehds-dropped";
pub const DROPPED_PATH: &str = "ehds-dropped-path";
#[derive(Clone, PartialEq, Eq)]
pub struct Entry {
pub name: &'static str,
pub value: String,
}
impl Entry {
#[must_use]
pub fn new(name: &'static str, value: impl Into<String>) -> Self {
Self {
name,
value: value.into(),
}
}
}
impl fmt::Debug for Entry {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Entry")
.field("name", &self.name)
.finish_non_exhaustive()
}
}
pub(crate) fn coded(system: Option<&str>, code: &str) -> String {
match system {
Some(system) => format!("{system}|{code}"),
None => code.to_owned(),
}
}
fn record_coded(value: &Coded) -> String {
coded(value.system.as_deref(), &value.code)
}
fn party_ref(party: &PartyRef) -> String {
format!("{}|{}|{}", party.namespace, party.kind, party.id)
}
#[must_use]
pub fn relayed(relayed: &Relayed) -> Vec<Entry> {
let parts = relayed.as_parts();
let mut details = vec![
Entry::new(RELAYED_VIA, parts.via.code()),
Entry::new(RELAYED_ISSUER, parts.via.issuer()),
Entry::new(ASSERTED, "true"),
];
details.extend(optional(COUNTRY_CODE, parts.country_code.as_ref()));
details.push(Entry::new(
PROFESSIONAL_NAMED,
if relayed.names_a_professional() {
"true"
} else {
"false"
},
));
if let Some(professional) = &parts.professional {
details.extend(professional_details(professional));
}
if let Some(provider) = &parts.provider {
details.extend(provider_details(provider));
}
for (name, value) in [
(POINT_OF_CARE, &parts.point_of_care),
(IDENTITY_ASSERTION_ID, &parts.identity_assertion_id),
(TRC_ASSERTION_ID, &parts.trc_assertion_id),
(REQUEST_MESSAGE_ID, &parts.message_id),
(CONSUMER_NCP, &parts.gateways.consumer),
(PROVIDER_NCP, &parts.gateways.provider),
] {
details.extend(optional(name, value.as_ref()));
}
if let Some(agreement) = &parts.agreement {
details.extend(agreement_details(agreement));
}
details.extend(parts.asserted.iter().flat_map(|(name, values)| {
values
.iter()
.map(move |value| Entry::new(ASSERTED_FIELD, format!("{name}={value}")))
}));
details
}
fn provider_details(provider: &RelayedProvider) -> Vec<Entry> {
let mut details = vec![Entry::new(PROVIDER_IDENTIFIER, provider.identifier.clone())];
details.extend(optional(
PROVIDER_ISSUING_AUTHORITY,
provider.issuing_authority.as_ref(),
));
details.extend(optional(PROVIDER_NAME, provider.name.as_ref()));
details.extend(optional(PROVIDER_ADDRESS, provider.address.as_ref()));
details.extend(
provider
.facility_type
.as_ref()
.map(|kind| Entry::new(PROVIDER_FACILITY_TYPE, record_coded(kind))),
);
details.extend(optional(
PROVIDER_HOME_COMMUNITY_ID,
provider.home_community_id.as_ref(),
));
details.extend(
provider
.organisation_type
.as_ref()
.map(|kind| Entry::new(PROVIDER_ORGANISATION_TYPE, record_coded(kind))),
);
details
}
fn agreement_details(agreement: &Agreement) -> Vec<Entry> {
let mut details = vec![
Entry::new(AGREEMENT_NAME, agreement.name.clone()),
Entry::new(AGREEMENT_VERSION, agreement.version.clone()),
];
details.extend(
agreement
.legal_ground
.iter()
.map(|ground| Entry::new(AGREEMENT_LEGAL_GROUND, record_coded(ground))),
);
details.extend(optional(AGREEMENT_CONSENT, agreement.consent.as_ref()));
details.extend(
agreement
.access_class
.map(|class| Entry::new(AGREEMENT_ACCESS_CLASS, class.code())),
);
details
}
fn optional(name: &'static str, value: Option<&String>) -> Option<Entry> {
value.map(|value| Entry::new(name, value.clone()))
}
fn professional_details(professional: &RelayedProfessional) -> Vec<Entry> {
let mut details = Vec::new();
details.extend(optional(HP_NAME, professional.name.as_ref()));
details.extend(optional(FAMILY_NAME, professional.family_name.as_ref()));
details.extend(optional(GIVEN_NAME, professional.given_name.as_ref()));
details.extend(optional(HP_IDENTIFIER, professional.identifier.as_ref()));
details.extend(optional(
HP_ISSUING_AUTHORITY,
professional.issuing_authority.as_ref(),
));
details.extend(
professional
.roles
.iter()
.map(|role| Entry::new(HP_ROLE, record_coded(role))),
);
details.extend(optional(
HP_PROVIDER_IDENTIFIER,
professional.healthcare_provider_identifier.as_ref(),
));
details.extend(
professional
.name_id
.as_ref()
.map(|name_id| Entry::new(HP_NAME_ID, name_id.user_id())),
);
details.extend(
professional
.authentication_method
.map(|method| Entry::new(HP_AUTHENTICATION_METHOD, method.uri())),
);
details.extend(
professional
.on_behalf_of
.as_ref()
.map(|role| Entry::new(HP_ON_BEHALF_OF, record_coded(role))),
);
details.extend(
professional
.permissions
.iter()
.map(|permission| Entry::new(HP_PERMISSION, permission.clone())),
);
details
}
#[must_use]
pub fn accessor(accessor: &Accessor) -> Vec<Entry> {
let mut details = Vec::new();
if let Some(binding) = &accessor.binding {
details.push(Entry::new(BINDING, binding.code()));
if binding.is_guest() {
details.push(Entry::new(GUEST, "true"));
}
if binding.is_security_event() {
details.push(Entry::new(SECURITY_EVENT, "true"));
}
details.extend(
binding
.party()
.map(|party| Entry::new(PARTY, party_ref(party))),
);
details.extend(
binding
.groups()
.iter()
.map(|group| Entry::new(GROUP, party_ref(group))),
);
match binding {
Binding::RefusedKeyClaimMissing { claim } => {
details.push(Entry::new(BINDING_CLAIM, claim.clone()));
}
Binding::RefusedExternalIncomplete { fields } => details.extend(
fields
.iter()
.map(|field| Entry::new(BINDING_FIELD, field.clone())),
),
_ => {}
}
}
details.extend(
accessor
.actors
.iter()
.map(|actor| Entry::new(ACTOR, coded(actor.issuer.as_deref(), &actor.subject))),
);
if let Some(representation) = &accessor.represents {
details.push(Entry::new(REPRESENTS, representation.represented.clone()));
details.extend(
representation
.ground
.as_ref()
.map(|ground| Entry::new(REPRESENTS_GROUND, record_coded(ground))),
);
details.extend(
representation.relationship.as_ref().map(|relationship| {
Entry::new(REPRESENTS_RELATIONSHIP, record_coded(relationship))
}),
);
}
details.extend(
accessor
.acts_for
.iter()
.map(|provider| Entry::new(ACTS_FOR, issued(provider))),
);
details.extend(
accessor
.facilities
.iter()
.map(|facility| Entry::new(FACILITY, issued(facility))),
);
details
}
fn issued(id: &IssuedId) -> String {
format!("{}|{}", id.issuer, id.id)
}