ihe-iti 0.0.44

The IHE IT Infrastructure (ITI) identity profiles: PIXm (ITI-83), PDQm (ITI-78, ITI-119), mCSD (ITI-90, ITI-91), PMIR (ITI-93, ITI-94) and XCPD (ITI-55), one feature per profile, with their audit records behind `balp` and `atna`
Documentation
// SPDX-FileCopyrightText: Cadasto B.V.
// SPDX-License-Identifier: BUSL-1.1

//! The ITI-78 and ITI-119 audit records of the Patient Demographics Consumer
//! (PDQm 3.2.0 §2:3.78.5.1 and §2:3.119.5.1.1), feature `balp`.
//!
//! [`PdqmClient::audited`](super::PdqmClient::audited) gives the client a
//! [`AuditRecorder`](ihe_atna::balp::AuditRecorder); every search and every
//! page it asks for is recorded as one [`Exchange`] the PDQm Query Consumer
//! audit profile fixes: built on the BALP Query pattern, the `ITI-78` and
//! `search` subtypes, and the request as sent in the query entity. The
//! profile's patient entity is `0..1`, for a query that identifies one
//! patient; a demographics search identifies none on its own, so none is
//! written.
//!
//! Every match is recorded as the PDQm Match Consumer audit profile fixes it:
//! the same pattern with the `ITI-119` subtype, the request as sent in the
//! query entity, and the patient entity when the input names the patient by
//! one identifier, which the profile asks for when "one patient is explicitly
//! identified".
//!
//! A search, a page or a match made for a user names them from their OAuth
//! token, in the user agent BALP's patterns leave optional.

use jiff::Timestamp;
use secrecy::SecretString;
use url::Url;

use super::error::PdqmError;
use ihe_atna::balp::{
    Coded, DESTINATION_ROLE, Direction, Entity, EventKind, Exchange, Outcome, Peer, REST, SEARCH,
    SOURCE_ROLE,
};
use ihe_atna::user::OnBehalfOf;

/// The `ITI-78` subtype.
pub const ITI_78: Coded = Coded::fixed(
    ihe_atna::balp::IHE_TRANSACTIONS,
    "ITI-78",
    "Mobile Patient Demographics Query",
);

/// The `ITI-119` subtype.
pub const ITI_119: Coded = Coded::fixed(
    ihe_atna::balp::IHE_TRANSACTIONS,
    "ITI-119",
    "Patient Demographics Match",
);

/// What the PDQm Query Consumer audit profile fixes.
#[must_use]
pub fn query_consumer() -> EventKind {
    EventKind {
        profile: "https://profiles.ihe.net/ITI/PDQm/StructureDefinition/IHE.PDQm.Query.Audit.Consumer",
        event_type: REST,
        subtypes: vec![ITI_78, SEARCH],
        action: "E",
        client: SOURCE_ROLE,
        server: DESTINATION_ROLE,
    }
}

/// What the PDQm Match Consumer audit profile fixes; the `search` subtype
/// beside `ITI-119` is the profile's own example's.
#[must_use]
pub fn match_consumer() -> EventKind {
    EventKind {
        profile: "https://profiles.ihe.net/ITI/PDQm/StructureDefinition/IHE.PDQm.Match.Audit.Consumer",
        event_type: REST,
        subtypes: vec![ITI_119, SEARCH],
        action: "E",
        client: SOURCE_ROLE,
        server: DESTINATION_ROLE,
    }
}

/// The audit record of one ITI-78 request `request` to the Supplier at
/// `base`, made for `on_behalf`, which ended in `result`.
pub(super) fn exchange<T>(
    base: &Url,
    request: SecretString,
    on_behalf: &OnBehalfOf,
    result: &Result<T, PdqmError>,
) -> Exchange {
    recorded(
        (query_consumer(), base),
        vec![Entity::Query(request)],
        on_behalf,
        result,
    )
}

/// The audit record of one ITI-119 request `request` to the Supplier at
/// `base`, naming the patient `patient` when the input identified one, made
/// for `on_behalf`, which ended in `result`.
pub(super) fn match_exchange<T>(
    base: &Url,
    (request, patient): (SecretString, Option<(&str, &SecretString)>),
    on_behalf: &OnBehalfOf,
    result: &Result<T, PdqmError>,
) -> Exchange {
    let mut entities = vec![Entity::Query(request)];
    if let Some((system, value)) = patient {
        entities.push(Entity::Patient {
            system: Some(system.to_owned()),
            namespace: None,
            value: value.clone(),
        });
    }
    recorded((match_consumer(), base), entities, on_behalf, result)
}

/// The record of `kind` for an exchange with the Supplier at `base` over
/// `entities`, made for `on_behalf`, which ended in `result`.
fn recorded<T>(
    (kind, base): (EventKind, &Url),
    entities: Vec<Entity>,
    on_behalf: &OnBehalfOf,
    result: &Result<T, PdqmError>,
) -> Exchange {
    let outcome = match result {
        Ok(_) => Outcome::Success,
        Err(PdqmError::Timeout | PdqmError::Transport(_)) => Outcome::SeriousFailure,
        Err(_) => Outcome::MinorFailure,
    };
    Exchange {
        kind,
        recorded: Timestamp::now(),
        outcome,
        outcome_description: None,
        direction: Direction::Sent {
            server: Peer::server(base),
        },
        on_behalf: on_behalf.clone(),
        entities,
        security: Vec::new(),
    }
}