use std::collections::BTreeSet;
use std::fmt;
use std::net::IpAddr;
use jiff::Timestamp;
use secrecy::SecretString;
use crate::classify::Classification;
use crate::emergency::Emergency;
use crate::label::Labels;
use crate::retention::Retention;
use crate::transformation::Transformation;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum Action {
Query,
Read,
Create,
Update,
Delete,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum Outcome {
Success,
MinorFailure,
SeriousFailure,
MajorFailure,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct Purpose {
pub system: Option<String>,
pub code: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum Acting {
Person,
Client,
Delegated,
External,
System,
}
impl Acting {
#[must_use]
pub const fn code(self) -> &'static str {
match self {
Self::Person => "person",
Self::Client => "client",
Self::Delegated => "delegated",
Self::External => "external",
Self::System => "system",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum AssuranceLevel {
Low,
Substantial,
High,
}
impl AssuranceLevel {
#[must_use]
pub const fn code(self) -> &'static str {
match self {
Self::Low => "low",
Self::Substantial => "substantial",
Self::High => "high",
}
}
}
#[derive(Clone, Default, PartialEq, Eq)]
pub struct Professional {
pub name: Option<String>,
pub identifier: Option<String>,
}
impl fmt::Debug for Professional {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Professional")
.field("name", &self.name.is_some())
.field("identifier", &self.identifier.is_some())
.finish()
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct Coded {
pub system: Option<String>,
pub code: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum Via {
ContactPoint {
issuer: String,
},
Federation {
issuer: String,
},
}
impl Via {
#[must_use]
pub const fn code(&self) -> &'static str {
match self {
Self::ContactPoint { .. } => "contact-point",
Self::Federation { .. } => "federation",
}
}
#[must_use]
pub fn issuer(&self) -> &str {
match self {
Self::ContactPoint { issuer } | Self::Federation { issuer } => issuer,
}
}
}
#[derive(Clone, PartialEq, Eq)]
pub struct NameId {
pub value: String,
pub sp_provided_id: Option<String>,
pub issuer: String,
}
impl NameId {
#[must_use]
pub fn user_id(&self) -> String {
format!(
"{}<{}@{}>",
self.sp_provided_id.as_deref().unwrap_or_default(),
self.value,
self.issuer
)
}
}
impl fmt::Debug for NameId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("NameId")
.field("sp_provided_id", &self.sp_provided_id.is_some())
.finish_non_exhaustive()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AuthenticationMethod {
MobileTwoFactorUnregistered,
MobileTwoFactorContract,
X509,
Spki,
SmartcardPki,
SoftwarePki,
TlsClient,
}
impl AuthenticationMethod {
const ALL: [Self; 7] = [
Self::MobileTwoFactorUnregistered,
Self::MobileTwoFactorContract,
Self::X509,
Self::Spki,
Self::SmartcardPki,
Self::SoftwarePki,
Self::TlsClient,
];
#[must_use]
pub const fn uri(self) -> &'static str {
match self {
Self::MobileTwoFactorUnregistered => {
"urn:oasis:names:tc:SAML:2.0:ac:classes:MobileTwoFactorUnregistered"
}
Self::MobileTwoFactorContract => {
"urn:oasis:names:tc:SAML:2.0:ac:classes:MobileTwoFactorContract"
}
Self::X509 => "urn:oasis:names:tc:SAML:2.0:ac:classes:X509",
Self::Spki => "urn:oasis:names:tc:SAML:2.0:ac:classes:SPKI",
Self::SmartcardPki => "urn:oasis:names:tc:SAML:2.0:ac:classes:SmartcardPKI",
Self::SoftwarePki => "urn:oasis:names:tc:SAML:2.0:ac:classes:SoftwarePKI",
Self::TlsClient => "urn:oasis:names:tc:SAML:2.0:ac:classes:TLSClient",
}
}
#[must_use]
pub fn from_uri(uri: &str) -> Option<Self> {
Self::ALL.into_iter().find(|method| method.uri() == uri)
}
}
#[derive(Clone, PartialEq, Eq)]
pub struct RelayedParts {
pub via: Via,
pub country_code: String,
pub professional: Option<RelayedProfessional>,
pub provider: RelayedProvider,
pub agreement: Option<Agreement>,
pub point_of_care: Option<String>,
pub identity_assertion_id: Option<String>,
pub trc_assertion_id: Option<String>,
pub message_id: Option<String>,
pub gateways: Gateways,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Gateways {
pub consumer: Option<String>,
pub provider: Option<String>,
}
#[derive(Clone, PartialEq, Eq)]
pub struct Agreement {
pub name: String,
pub version: String,
pub legal_ground: Vec<Coded>,
pub consent: Option<String>,
pub access_class: Option<AccessClass>,
}
impl fmt::Debug for Agreement {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Agreement")
.field("legal_ground", &self.legal_ground.len())
.field("consent", &self.consent.is_some())
.field("access_class", &self.access_class)
.finish_non_exhaustive()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum AccessClass {
Automated,
Chained,
}
impl AccessClass {
#[must_use]
pub const fn code(self) -> &'static str {
match self {
Self::Automated => "automated",
Self::Chained => "chained",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
#[non_exhaustive]
pub enum RelayedError {
#[error("a contact point relayed no health professional")]
ContactPointWithoutProfessional,
#[error("a federation access names no agreement")]
FederationWithoutAgreement,
#[error("an organisation without a named person has no access class")]
OrganisationWithoutAccessClass,
#[error("the provider's organization-id is no urn:oid: or urn:hl7ii: identifier")]
ProviderIdentifierForm,
#[error("a clerical support worker is relayed without on-behalf-of")]
OnBehalfOfMissing,
}
pub const ISCO: &str = "2.16.840.1.113883.2.9.6.2.7";
#[derive(Clone, PartialEq, Eq)]
pub struct Relayed(RelayedParts);
impl Relayed {
#[must_use]
pub fn as_parts(&self) -> &RelayedParts {
&self.0
}
#[must_use]
pub fn into_parts(self) -> RelayedParts {
self.0
}
#[must_use]
pub fn names_a_professional(&self) -> bool {
self.0.professional.is_some()
}
}
impl TryFrom<RelayedParts> for Relayed {
type Error = RelayedError;
fn try_from(parts: RelayedParts) -> Result<Self, Self::Error> {
match (&parts.via, &parts.professional) {
(Via::ContactPoint { .. }, None) => {
return Err(RelayedError::ContactPointWithoutProfessional);
}
(Via::ContactPoint { .. }, Some(professional)) => {
let identifier = &parts.provider.identifier;
if !identifier.starts_with("urn:oid:") && !identifier.starts_with("urn:hl7ii:") {
return Err(RelayedError::ProviderIdentifierForm);
}
let clerical = professional
.roles
.iter()
.any(|role| role.code == "44" && role.system.as_deref() == Some(ISCO));
if clerical && professional.on_behalf_of.is_none() {
return Err(RelayedError::OnBehalfOfMissing);
}
}
(Via::Federation { .. }, professional) => {
let Some(agreement) = &parts.agreement else {
return Err(RelayedError::FederationWithoutAgreement);
};
if professional.is_none() && agreement.access_class.is_none() {
return Err(RelayedError::OrganisationWithoutAccessClass);
}
}
}
Ok(Self(parts))
}
}
impl fmt::Debug for Relayed {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Relayed")
.field("via", &self.0.via.code())
.field("country_code", &self.0.country_code)
.field("professional", &self.0.professional)
.field("provider", &self.0.provider)
.field("agreement", &self.0.agreement)
.finish_non_exhaustive()
}
}
impl fmt::Debug for RelayedParts {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("RelayedParts")
.field("via", &self.via.code())
.field("country_code", &self.country_code)
.finish_non_exhaustive()
}
}
#[derive(Clone, PartialEq, Eq)]
pub struct RelayedProfessional {
pub name: String,
pub family_name: Option<String>,
pub given_name: Option<String>,
pub identifier: String,
pub issuing_authority: String,
pub roles: Vec<Coded>,
pub healthcare_provider_identifier: Option<String>,
pub name_id: Option<NameId>,
pub authentication_method: Option<AuthenticationMethod>,
pub on_behalf_of: Option<Coded>,
pub permissions: Vec<String>,
}
impl fmt::Debug for RelayedProfessional {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("RelayedProfessional")
.field("roles", &self.roles.len())
.field("name_id", &self.name_id)
.field("authentication_method", &self.authentication_method)
.field("on_behalf_of", &self.on_behalf_of.is_some())
.field("permissions", &self.permissions.len())
.finish_non_exhaustive()
}
}
#[derive(Clone, PartialEq, Eq)]
pub struct RelayedProvider {
pub identifier: String,
pub issuing_authority: String,
pub name: Option<String>,
pub address: Option<String>,
pub facility_type: Option<Coded>,
pub home_community_id: Option<String>,
pub organisation_type: Option<Coded>,
}
impl fmt::Debug for RelayedProvider {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("RelayedProvider")
.field("facility_type", &self.facility_type)
.field("organisation_type", &self.organisation_type)
.finish_non_exhaustive()
}
}
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct PartyRef {
pub namespace: String,
pub kind: String,
pub id: String,
}
impl fmt::Debug for PartyRef {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("PartyRef")
.field("kind", &self.kind)
.finish_non_exhaustive()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BoundParty {
pub party: PartyRef,
pub groups: Vec<PartyRef>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum Binding {
Bound {
party: BoundParty,
guest: bool,
},
Provisional {
party: BoundParty,
},
External,
Organisation,
System,
PartySelf,
RefusedUnbound,
RefusedAmbiguous,
RefusedInactive {
party: PartyRef,
},
RefusedAbandoned {
party: PartyRef,
},
RefusedKeyClaimMissing {
claim: String,
},
RefusedGroupsUnavailable,
RefusedGuest,
RefusedExternalIncomplete {
fields: Vec<String>,
},
RefusedTrcPatientMismatch,
}
impl Binding {
#[must_use]
pub const fn code(&self) -> &'static str {
match self {
Self::Bound { .. } => "bound",
Self::Provisional { .. } => "provisional",
Self::External => "external",
Self::Organisation => "organisation",
Self::System => "system",
Self::PartySelf => "self",
Self::RefusedUnbound => "refused-unbound",
Self::RefusedAmbiguous => "refused-ambiguous",
Self::RefusedInactive { .. } => "refused-inactive",
Self::RefusedAbandoned { .. } => "refused-abandoned",
Self::RefusedKeyClaimMissing { .. } => "refused-key-claim-missing",
Self::RefusedGroupsUnavailable => "refused-groups-unavailable",
Self::RefusedGuest => "refused-guest",
Self::RefusedExternalIncomplete { .. } => "refused-external-incomplete",
Self::RefusedTrcPatientMismatch => "refused-trc-patient-mismatch",
}
}
#[must_use]
pub const fn party(&self) -> Option<&PartyRef> {
match self {
Self::Bound { party, .. } | Self::Provisional { party } => Some(&party.party),
Self::RefusedInactive { party } | Self::RefusedAbandoned { party } => Some(party),
_ => None,
}
}
#[must_use]
pub fn groups(&self) -> &[PartyRef] {
match self {
Self::Bound { party, .. } | Self::Provisional { party } => &party.groups,
_ => &[],
}
}
#[must_use]
pub const fn is_guest(&self) -> bool {
matches!(self, Self::Bound { guest: true, .. })
}
#[must_use]
pub const fn is_refusal(&self) -> bool {
matches!(
self,
Self::RefusedUnbound
| Self::RefusedAmbiguous
| Self::RefusedInactive { .. }
| Self::RefusedAbandoned { .. }
| Self::RefusedKeyClaimMissing { .. }
| Self::RefusedGroupsUnavailable
| Self::RefusedGuest
| Self::RefusedExternalIncomplete { .. }
| Self::RefusedTrcPatientMismatch
)
}
#[must_use]
pub const fn is_security_event(&self) -> bool {
matches!(self, Self::RefusedTrcPatientMismatch)
}
}
#[derive(Clone, PartialEq, Eq)]
pub struct Actor {
pub issuer: Option<String>,
pub subject: String,
}
impl fmt::Debug for Actor {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Actor")
.field("issuer", &self.issuer.is_some())
.finish_non_exhaustive()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Representation {
pub represented: PartyRef,
pub ground: Coded,
}
#[derive(Clone, PartialEq, Eq)]
pub struct Accessor {
pub issuer: Option<String>,
pub subject: String,
pub client_id: Option<String>,
pub audience: Option<String>,
pub provider: Option<String>,
pub acting: Option<Acting>,
pub roles: Vec<Coded>,
pub token_id: Option<String>,
pub assurance: Option<AssuranceLevel>,
pub professional: Professional,
pub alt_id: Option<String>,
pub purposes: Vec<Purpose>,
pub policies: Vec<String>,
pub relayed: Option<Relayed>,
pub binding: Option<Binding>,
pub actors: Vec<Actor>,
pub represents: Option<Representation>,
}
impl Accessor {
#[must_use]
pub fn party(&self) -> Option<&PartyRef> {
self.binding.as_ref().and_then(Binding::party)
}
}
impl fmt::Debug for Accessor {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Accessor")
.field("provider", &self.provider.is_some())
.field("acting", &self.acting)
.field("roles", &self.roles)
.field("token_id", &self.token_id.is_some())
.field("assurance", &self.assurance)
.field("professional", &self.professional)
.field("alt_id", &self.alt_id.is_some())
.field("purposes", &self.purposes)
.field("policies", &self.policies.len())
.field("relayed", &self.relayed)
.field("binding", &self.binding)
.field("actors", &self.actors)
.field("represents", &self.represents)
.finish_non_exhaustive()
}
}
#[derive(Debug, Clone)]
pub struct PatientIdentifier {
pub namespace: Option<String>,
pub value: SecretString,
}
#[derive(Clone)]
#[non_exhaustive]
pub enum PatientLookup {
RequestNamed,
Found(Vec<PatientIdentifier>),
NotFound,
Unavailable,
NotConfigured,
Unsupported,
}
impl PatientLookup {
#[must_use]
pub const fn code(&self) -> &'static str {
match self {
Self::RequestNamed => "request-named",
Self::Found(_) => "found",
Self::NotFound => "not-found",
Self::Unavailable => "unavailable",
Self::NotConfigured => "not-configured",
Self::Unsupported => "unsupported",
}
}
}
impl fmt::Debug for PatientLookup {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Found(found) => write!(f, "Found({})", found.len()),
other => f.write_str(other.code()),
}
}
}
#[derive(Clone)]
pub struct EhrAt {
pub endpoint: String,
pub ehr_id: String,
pub patient: PatientLookup,
}
impl fmt::Debug for EhrAt {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("EhrAt")
.field("endpoint", &self.endpoint)
.field("patient", &self.patient)
.finish_non_exhaustive()
}
}
#[derive(Clone, Default)]
pub struct DataSubject {
pub patient: Option<PatientIdentifier>,
pub ehrs: Vec<EhrAt>,
pub population: Option<Population>,
}
impl fmt::Debug for DataSubject {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("DataSubject")
.field("patient", &self.patient.is_some())
.field("ehrs", &self.ehrs.len())
.field("population", &self.population)
.finish()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum PopulationReason {
NotProjected,
OverLimit,
NotDelivered,
Unattributed,
}
impl PopulationReason {
#[must_use]
pub const fn code(self) -> &'static str {
match self {
Self::NotProjected => "not-projected",
Self::OverLimit => "over-limit",
Self::NotDelivered => "not-delivered",
Self::Unattributed => "unattributed",
}
}
}
#[derive(Clone, PartialEq, Eq)]
pub struct Population {
pub reason: PopulationReason,
pub ehrs: Option<usize>,
pub limit: usize,
pub templates: BTreeSet<String>,
pub archetypes: BTreeSet<String>,
}
impl fmt::Debug for Population {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Population")
.field("reason", &self.reason)
.field("ehrs", &self.ehrs)
.field("limit", &self.limit)
.field("templates", &self.templates.len())
.field("archetypes", &self.archetypes.len())
.finish()
}
}
#[derive(Clone, PartialEq, Eq)]
pub struct Origin {
pub endpoint: Option<String>,
pub node: Option<String>,
pub system_id: Option<String>,
pub status: Option<String>,
pub rows: Option<usize>,
pub version: Option<WrittenVersion>,
pub created_ehr: Option<CreatedEhr>,
pub categories: Option<Classification>,
}
impl Origin {
#[must_use]
pub fn key(&self) -> Option<&str> {
self.endpoint.as_deref().or(self.system_id.as_deref())
}
}
impl fmt::Debug for Origin {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Origin")
.field("endpoint", &self.endpoint)
.field("node", &self.node)
.field("status", &self.status)
.field("rows", &self.rows)
.field("version", &self.version)
.field("created_ehr", &self.created_ehr)
.field("categories", &self.categories)
.finish_non_exhaustive()
}
}
#[derive(Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum WrittenVersion {
Named(String),
Unnamed,
Malformed,
}
impl WrittenVersion {
#[must_use]
pub fn named(&self) -> Option<&str> {
match self {
Self::Named(version) => Some(version),
Self::Unnamed | Self::Malformed => None,
}
}
#[must_use]
pub fn unread(&self) -> Option<&'static str> {
match self {
Self::Named(_) => None,
Self::Unnamed => Some("unnamed"),
Self::Malformed => Some("malformed"),
}
}
}
impl fmt::Debug for WrittenVersion {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Named(_) => f.write_str("Named(..)"),
Self::Unnamed => f.write_str("Unnamed"),
Self::Malformed => f.write_str("Malformed"),
}
}
}
#[derive(Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum CreatedEhr {
Named(String),
Unnamed,
Malformed,
}
impl CreatedEhr {
#[must_use]
pub fn named(&self) -> Option<&str> {
match self {
Self::Named(ehr_id) => Some(ehr_id),
Self::Unnamed | Self::Malformed => None,
}
}
#[must_use]
pub fn unread(&self) -> Option<&'static str> {
match self {
Self::Named(_) => None,
Self::Unnamed => Some("unnamed"),
Self::Malformed => Some("malformed"),
}
}
}
impl fmt::Debug for CreatedEhr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Named(_) => f.write_str("Named(..)"),
Self::Unnamed => f.write_str("Unnamed"),
Self::Malformed => f.write_str("Malformed"),
}
}
}
#[derive(Clone)]
pub struct Request {
pub id: Option<String>,
pub operation: String,
pub operation_system: Option<String>,
pub resource: Option<String>,
pub query: Option<SecretString>,
pub stored_query: Option<String>,
pub client_address: Option<IpAddr>,
pub correlation: Option<Correlation>,
}
#[derive(Clone, PartialEq, Eq)]
pub struct Correlation {
pub value: String,
pub source: CorrelationSource,
}
impl fmt::Debug for Correlation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Correlation")
.field("source", &self.source)
.finish_non_exhaustive()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum CorrelationSource {
TokenClaim,
AuthorizationDetails,
Header,
}
impl CorrelationSource {
#[must_use]
pub const fn code(self) -> &'static str {
match self {
Self::TokenClaim => "token-claim",
Self::AuthorizationDetails => "authorization-details",
Self::Header => "header",
}
}
#[must_use]
pub const fn is_attested(self) -> bool {
matches!(self, Self::TokenClaim | Self::AuthorizationDetails)
}
}
impl fmt::Debug for Request {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Request")
.field("id", &self.id)
.field("operation", &self.operation)
.field("query", &self.query.is_some())
.field("correlation", &self.correlation)
.finish_non_exhaustive()
}
}
#[derive(Clone)]
pub struct AccessRecord {
pub action: Action,
pub recorded: Timestamp,
pub outcome: Outcome,
pub outcome_description: Option<String>,
pub accessor: Accessor,
pub subject: DataSubject,
pub categories: Classification,
pub delivered: Option<usize>,
pub origins: Vec<Origin>,
pub origin_count: Option<usize>,
pub request: Request,
pub retention: Retention,
pub labels: Labels,
pub emergency: Option<Emergency>,
pub inserted: Option<Insertion>,
pub transformations: Vec<Transformation>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
#[non_exhaustive]
pub enum InvalidRecord {
#[error("a chained organisation access has no attested correlation id")]
ChainedWithoutAttestedCorrelation,
}
impl AccessRecord {
pub fn validate(&self) -> Result<(), InvalidRecord> {
let chained = self.accessor.relayed.as_ref().is_some_and(|relayed| {
let parts = relayed.as_parts();
parts.professional.is_none()
&& parts
.agreement
.as_ref()
.is_some_and(|agreement| agreement.access_class == Some(AccessClass::Chained))
});
let attested = self
.request
.correlation
.as_ref()
.is_some_and(|correlation| correlation.source.is_attested());
if chained && !attested {
return Err(InvalidRecord::ChainedWithoutAttestedCorrelation);
}
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum Insertion {
Subject,
Representative,
}
impl fmt::Debug for AccessRecord {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("AccessRecord")
.field("action", &self.action)
.field("recorded", &self.recorded)
.field("outcome", &self.outcome)
.field("outcome_description", &self.outcome_description.is_some())
.field("accessor", &self.accessor)
.field("subject", &self.subject)
.field("categories", &self.categories)
.field("delivered", &self.delivered)
.field("origins", &self.origins)
.field("origin_count", &self.origin_count)
.field("request", &self.request)
.field("retention", &self.retention)
.field("labels", &self.labels)
.field("emergency", &self.emergency)
.field("inserted", &self.inserted)
.field("transformations", &self.transformations)
.finish()
}
}