Skip to main content

ehds_logging/
record.rs

1// SPDX-FileCopyrightText: Cadasto B.V.
2// SPDX-License-Identifier: BUSL-1.1
3
4//! The access record: what the logging component records "on every access
5//! event or group of events" (Regulation (EU) 2025/327 Annex II 3.2).
6//!
7//! Annex II 3.2 asks for (a) the healthcare provider or other individuals
8//! that accessed the data, (b) the specific natural person or persons, (c)
9//! the categories of data accessed, (d) the time and date, and (e) the
10//! origin or origins of the data. An [`AccessRecord`] carries each, and
11//! beside them the data subject whose data were accessed and the purpose of
12//! use the access was made for, which Art 9(2) asks the patient be told. A
13//! record whose accessor declared an emergency purpose carries the mark
14//! Art 11(5) asks for ([`emergency`](crate::emergency)).
15//!
16//! A record names the patient and the person who accessed their data, so
17//! `Debug` shows neither, nor any id the classification read: a record
18//! leaves through an [`AccessSink`](crate::sink::AccessSink) alone.
19
20use std::collections::BTreeSet;
21use std::fmt;
22use std::net::IpAddr;
23
24use jiff::Timestamp;
25use secrecy::SecretString;
26
27use crate::classify::Classification;
28use crate::emergency::Emergency;
29use crate::label::Labels;
30use crate::retention::Retention;
31use crate::transformation::Transformation;
32
33/// What the access did to the data.
34#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35#[non_exhaustive]
36pub enum Action {
37    /// A query over the data, an ad hoc or a stored one.
38    Query,
39    /// A read of one resource.
40    Read,
41    /// The creation of a resource.
42    Create,
43    /// The update of a resource.
44    Update,
45    /// The deletion of a resource.
46    Delete,
47}
48
49/// How the access ended.
50#[derive(Debug, Clone, Copy, PartialEq, Eq)]
51#[non_exhaustive]
52pub enum Outcome {
53    /// The data were delivered or written as asked.
54    Success,
55    /// The access was answered with a failure, or with less than asked.
56    MinorFailure,
57    /// The access failed on the side that records it.
58    SeriousFailure,
59}
60
61/// One purpose of use the accessor declared: a code and the system that
62/// defines it.
63#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
64pub struct Purpose {
65    /// The code system, when one is named.
66    pub system: Option<String>,
67    /// The code.
68    pub code: String,
69}
70
71/// Who acts behind the token an access was authenticated by.
72#[derive(Debug, Clone, Copy, PartialEq, Eq)]
73#[non_exhaustive]
74pub enum Acting {
75    /// The natural person the token's `sub` names.
76    Person,
77    /// A client application, which the recording system admits to personal
78    /// data only for the professional the token names.
79    Client,
80}
81
82impl Acting {
83    /// The code a record writes the acting mode as: `person` or `client`.
84    #[must_use]
85    pub const fn code(self) -> &'static str {
86        match self {
87            Self::Person => "person",
88            Self::Client => "client",
89        }
90    }
91}
92
93/// An assurance level of an electronic identification means, as Regulation
94/// (EU) No 910/2014 Art 8(2) names them, lowest first.
95#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
96pub enum AssuranceLevel {
97    /// Level low, Art 8(2)(a).
98    Low,
99    /// Level substantial, Art 8(2)(b).
100    Substantial,
101    /// Level high, Art 8(2)(c).
102    High,
103}
104
105impl AssuranceLevel {
106    /// The code a record writes the level as: `low`, `substantial` or
107    /// `high`.
108    #[must_use]
109    pub const fn code(self) -> &'static str {
110        match self {
111            Self::Low => "low",
112            Self::Substantial => "substantial",
113            Self::High => "high",
114        }
115    }
116}
117
118/// The professional's identification, as the token the access was
119/// authenticated by states it.
120///
121/// `Debug` shows which members are present and none of their values.
122#[derive(Clone, Default, PartialEq, Eq)]
123pub struct Professional {
124    /// The professional's name.
125    pub name: Option<String>,
126    /// The identifier their national authority issued them as a health
127    /// professional.
128    pub identifier: Option<String>,
129}
130
131impl fmt::Debug for Professional {
132    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
133        f.debug_struct("Professional")
134            .field("name", &self.name.is_some())
135            .field("identifier", &self.identifier.is_some())
136            .finish()
137    }
138}
139
140/// A coded value, such as a professional role: a code and the system that
141/// defines it.
142#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
143pub struct Coded {
144    /// The code system, when one is named.
145    pub system: Option<String>,
146    /// The code.
147    pub code: String,
148}
149
150/// What a national contact point for digital health relays with a request.
151///
152/// The health professional and the healthcare provider of another Member
153/// State, as the contact point asserts them (Implementing Regulation (EU)
154/// 2026/2099 Art 7 and its Annex Tables 1 and 2). The recording system verified the contact point, never the professional:
155/// Art 6(1) and (2) give their identification and authentication to the
156/// entity the Member State of the professional lists. `Debug` shows the
157/// country and none of the other values.
158#[derive(Clone, PartialEq, Eq)]
159pub struct Relayed {
160    /// The contact point that asserted them, by the issuer of its token.
161    pub contact_point: String,
162    /// `country_code`: the ISO 3166-1 alpha-2 code of the Member State that
163    /// issued the professional's identification data.
164    pub country_code: String,
165    /// The health professional (Annex Table 1).
166    pub professional: RelayedProfessional,
167    /// The healthcare provider (Annex Table 2).
168    pub provider: RelayedProvider,
169}
170
171impl fmt::Debug for Relayed {
172    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
173        f.debug_struct("Relayed")
174            .field("country_code", &self.country_code)
175            .finish_non_exhaustive()
176    }
177}
178
179/// The health professional a contact point relays (2026/2099 Annex
180/// Table 1).
181#[derive(Clone, PartialEq, Eq)]
182pub struct RelayedProfessional {
183    /// `family_name`.
184    pub family_name: String,
185    /// `given_name`.
186    pub given_name: String,
187    /// `hp_identifier`, unique among the identifiers the Member State where
188    /// the professional is registered issues.
189    pub identifier: String,
190    /// `issuing_authority_name`: the agency that issued `hp_identifier`.
191    pub issuing_authority: String,
192    /// `hp_professional_role`, as the codes the contact point states.
193    pub roles: Vec<Coded>,
194}
195
196impl fmt::Debug for RelayedProfessional {
197    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
198        f.debug_struct("RelayedProfessional")
199            .field("roles", &self.roles.len())
200            .finish_non_exhaustive()
201    }
202}
203
204/// The healthcare provider a contact point relays (2026/2099 Annex
205/// Table 2), where the professional provides the treatment.
206#[derive(Clone, PartialEq, Eq)]
207pub struct RelayedProvider {
208    /// `healthcare_provider_identifier`, unique in the Member State that
209    /// issued it.
210    pub identifier: String,
211    /// `issuing_authority_name`: the agency that issued the identifier.
212    pub issuing_authority: String,
213    /// `healthcare_provider_name`.
214    pub name: String,
215    /// `healthcare_provider_address`: the official registered full address.
216    pub address: String,
217}
218
219impl fmt::Debug for RelayedProvider {
220    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
221        f.debug_struct("RelayedProvider").finish_non_exhaustive()
222    }
223}
224
225/// Who accessed the data (Annex II 3.2(a), (b)): the person, as the token
226/// the access was authenticated by names them, the client they used, and
227/// the provider they act for.
228///
229/// Each value is the token's, as the recording system verified it: the
230/// record infers none, so the assurance level is absent unless the
231/// authentication established one.
232#[derive(Clone, PartialEq, Eq)]
233pub struct Accessor {
234    /// The issuer that vouched for the person (`iss`).
235    pub issuer: String,
236    /// The person (`sub`).
237    pub subject: String,
238    /// The client application they used (`client_id`).
239    pub client_id: String,
240    /// The audience the token named the recording system by.
241    pub audience: Option<String>,
242    /// The healthcare provider or other organisation the person acts for.
243    pub provider: Option<String>,
244    /// Who acts behind the token: the person, or a client application.
245    pub acting: Acting,
246    /// The assurance level the authentication reached, when the recording
247    /// system established one from the token.
248    pub assurance: Option<AssuranceLevel>,
249    /// The professional the token names (3.2(b)), who acts in person or
250    /// whom a client acts for.
251    pub professional: Professional,
252    /// Another identification of the person the token states beside its
253    /// `sub`, such as a professional number a claim of the deployment's
254    /// naming carries.
255    pub alt_id: Option<String>,
256    /// Every purpose of use the token declares.
257    pub purposes: Vec<Purpose>,
258    /// The professional and the provider of another Member State a national
259    /// contact point relays, marked as asserted by it, when the client is
260    /// one (Implementing Regulation (EU) 2026/2099 Art 7).
261    pub relayed: Option<Relayed>,
262}
263
264impl fmt::Debug for Accessor {
265    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
266        f.debug_struct("Accessor")
267            .field("provider", &self.provider.is_some())
268            .field("acting", &self.acting)
269            .field("assurance", &self.assurance)
270            .field("professional", &self.professional)
271            .field("alt_id", &self.alt_id.is_some())
272            .field("purposes", &self.purposes)
273            .field("relayed", &self.relayed)
274            .finish_non_exhaustive()
275    }
276}
277
278/// The patient an access concerned, by the identifier the request named and
279/// the namespace that issued it; `Debug` shows the namespace alone.
280#[derive(Debug, Clone)]
281pub struct PatientIdentifier {
282    /// The issuing namespace.
283    pub namespace: String,
284    /// The identifier.
285    pub value: SecretString,
286}
287
288/// Whether the patient behind an `ehr_id` is named, so a search by the
289/// patient's identifier finds the access (Art 9(1)).
290///
291/// `Debug` shows the outcome and how many identifiers were found, never one.
292#[derive(Clone)]
293#[non_exhaustive]
294pub enum PatientLookup {
295    /// The request named the patient, so the record names them already.
296    RequestNamed,
297    /// The identity service holds these identifiers for the patient.
298    Found(Vec<PatientIdentifier>),
299    /// The identity service holds no identifier for the patient in a
300    /// namespace asked.
301    NotFound,
302    /// The identity service could not be asked, or did not answer.
303    Unavailable,
304    /// The recording system names no namespace to ask in.
305    NotConfigured,
306    /// The recording system has no identity service that names a patient
307    /// by an `ehr_id`.
308    Unsupported,
309}
310
311impl PatientLookup {
312    /// The outcome as a record writes it: `request-named`, `found`,
313    /// `not-found`, `unavailable`, `not-configured` or `unsupported`.
314    #[must_use]
315    pub const fn code(&self) -> &'static str {
316        match self {
317            Self::RequestNamed => "request-named",
318            Self::Found(_) => "found",
319            Self::NotFound => "not-found",
320            Self::Unavailable => "unavailable",
321            Self::NotConfigured => "not-configured",
322            Self::Unsupported => "unsupported",
323        }
324    }
325}
326
327impl fmt::Debug for PatientLookup {
328    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
329        match self {
330            Self::Found(found) => write!(f, "Found({})", found.len()),
331            other => f.write_str(other.code()),
332        }
333    }
334}
335
336/// One `ehr_id` an access reached, at the endpoint that holds it, with
337/// whether the patient it belongs to is named.
338#[derive(Clone)]
339pub struct EhrAt {
340    /// The endpoint.
341    pub endpoint: String,
342    /// The `ehr_id` at that endpoint.
343    pub ehr_id: String,
344    /// Whether the patient behind the `ehr_id` is named.
345    pub patient: PatientLookup,
346}
347
348impl fmt::Debug for EhrAt {
349    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
350        f.debug_struct("EhrAt")
351            .field("endpoint", &self.endpoint)
352            .field("patient", &self.patient)
353            .finish_non_exhaustive()
354    }
355}
356
357/// Whose data an access concerned: the patient the request named, each
358/// `ehr_id` the access reached, and, for an access over many patients' data
359/// that names none of them, the scope it read.
360#[derive(Clone, Default)]
361pub struct DataSubject {
362    /// The patient, when the request named one.
363    pub patient: Option<PatientIdentifier>,
364    /// The `ehr_id`s the access reached.
365    pub ehrs: Vec<EhrAt>,
366    /// The scope of an access over many patients' data whose record names
367    /// none of them: a population access.
368    pub population: Option<Population>,
369}
370
371impl fmt::Debug for DataSubject {
372    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
373        f.debug_struct("DataSubject")
374            .field("patient", &self.patient.is_some())
375            .field("ehrs", &self.ehrs.len())
376            .field("population", &self.population)
377            .finish()
378    }
379}
380
381/// Why the record of an access over many patients' data names none of the
382/// `ehr_id`s whose records it read.
383#[derive(Debug, Clone, Copy, PartialEq, Eq)]
384#[non_exhaustive]
385pub enum PopulationReason {
386    /// The query projects no `ehr_id`, so no delivered row says whose
387    /// record it came from.
388    NotProjected,
389    /// The delivered rows name more `ehr_id`s than a record names.
390    OverLimit,
391    /// No row was delivered, so no row names an `ehr_id`.
392    NotDelivered,
393    /// A delivered `ehr_id` was in no origin's own answer, so the record
394    /// cannot say which origin holds it.
395    Unattributed,
396}
397
398impl PopulationReason {
399    /// The reason as a record writes it: `not-projected`, `over-limit`,
400    /// `not-delivered` or `unattributed`.
401    #[must_use]
402    pub const fn code(self) -> &'static str {
403        match self {
404            Self::NotProjected => "not-projected",
405            Self::OverLimit => "over-limit",
406            Self::NotDelivered => "not-delivered",
407            Self::Unattributed => "unattributed",
408        }
409    }
410}
411
412/// The scope of a population access: what the access read, when the record
413/// names none of the persons whose records it read.
414///
415/// The origins the access asked are the record's origins. A person's
416/// search finds the record through an origin that holds their record, and
417/// it says that a query over that origin ran, never that it read their
418/// record. `Debug` shows the reason and the counts, never an id.
419#[derive(Clone, PartialEq, Eq)]
420pub struct Population {
421    /// Why the record names no `ehr_id`.
422    pub reason: PopulationReason,
423    /// How many `ehr_id`s the delivered rows named, when they were read.
424    pub ehrs: Option<usize>,
425    /// The most `ehr_id`s a record names.
426    pub limit: usize,
427    /// The template ids the query constrains its data to.
428    pub templates: BTreeSet<String>,
429    /// The archetype ids the query constrains its data to.
430    pub archetypes: BTreeSet<String>,
431}
432
433impl fmt::Debug for Population {
434    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
435        f.debug_struct("Population")
436            .field("reason", &self.reason)
437            .field("ehrs", &self.ehrs)
438            .field("limit", &self.limit)
439            .field("templates", &self.templates.len())
440            .field("archetypes", &self.archetypes.len())
441            .finish()
442    }
443}
444
445/// One origin of the data (Annex II 3.2(e)): an endpoint the access asked,
446/// with how it answered.
447#[derive(Clone, PartialEq, Eq)]
448pub struct Origin {
449    /// The endpoint, as the registry names it.
450    pub endpoint: String,
451    /// The node behind it.
452    pub node: Option<String>,
453    /// The node's openEHR `system_id`.
454    pub system_id: Option<String>,
455    /// How the endpoint answered, as the Federation Tier ยง11.1 status set
456    /// spells it, such as `active` or `node-error`.
457    pub status: String,
458    /// The rows or objects it contributed, when counted.
459    pub rows: Option<usize>,
460    /// The version a write left at the endpoint, as its answer named it, or
461    /// `None` for an access that wrote nothing there.
462    pub version: Option<WrittenVersion>,
463    /// The `ehr_id` an EHR create left at the endpoint, as its answer named
464    /// it, or `None` for an access that created no EHR there.
465    pub created_ehr: Option<CreatedEhr>,
466    /// The categories of what it contributed, when told apart from the
467    /// other origins'.
468    pub categories: Option<Classification>,
469}
470
471impl fmt::Debug for Origin {
472    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
473        f.debug_struct("Origin")
474            .field("endpoint", &self.endpoint)
475            .field("node", &self.node)
476            .field("status", &self.status)
477            .field("rows", &self.rows)
478            .field("version", &self.version)
479            .field("created_ehr", &self.created_ehr)
480            .field("categories", &self.categories)
481            .finish_non_exhaustive()
482    }
483}
484
485/// The version a write left at its origin, which is the data a write
486/// accessed (Annex II 3.2), as the origin's answer named it.
487///
488/// The recording system holds the version id to the `OBJECT_VERSION_ID`
489/// form before it is recorded, so a value the answer carried in its place,
490/// whatever it holds, never reaches the record.
491#[derive(Clone, PartialEq, Eq)]
492#[non_exhaustive]
493pub enum WrittenVersion {
494    /// The answer named this `OBJECT_VERSION_ID`.
495    Named(String),
496    /// The answer named no version, so the record cannot tell which one the
497    /// write left.
498    Unnamed,
499    /// The answer named a version that is no `OBJECT_VERSION_ID`, so the
500    /// record cannot tell which one the write left.
501    Malformed,
502}
503
504impl WrittenVersion {
505    /// The version id, when the answer named one.
506    #[must_use]
507    pub fn named(&self) -> Option<&str> {
508        match self {
509            Self::Named(version) => Some(version),
510            Self::Unnamed | Self::Malformed => None,
511        }
512    }
513
514    /// Why the record cannot tell the version, as a record writes it:
515    /// `unnamed` or `malformed`, or `None` when the answer named it.
516    #[must_use]
517    pub fn unread(&self) -> Option<&'static str> {
518        match self {
519            Self::Named(_) => None,
520            Self::Unnamed => Some("unnamed"),
521            Self::Malformed => Some("malformed"),
522        }
523    }
524}
525
526impl fmt::Debug for WrittenVersion {
527    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
528        match self {
529            Self::Named(_) => f.write_str("Named(..)"),
530            Self::Unnamed => f.write_str("Unnamed"),
531            Self::Malformed => f.write_str("Malformed"),
532        }
533    }
534}
535
536/// The `ehr_id` of the EHR a create left at its origin, as the origin's
537/// answer named it.
538///
539/// The recording system holds the `ehr_id` to the `HIER_OBJECT_ID` form
540/// before it is recorded, so a value the answer carried in its place,
541/// whatever it holds, never reaches the record.
542#[derive(Clone, PartialEq, Eq)]
543#[non_exhaustive]
544pub enum CreatedEhr {
545    /// The answer named this `ehr_id`.
546    Named(String),
547    /// The answer named no `ehr_id`, so the record cannot tell which EHR the
548    /// create left.
549    Unnamed,
550    /// The answer named an `ehr_id` that is no `HIER_OBJECT_ID`, or two
551    /// that differ, so the record cannot tell which EHR the create left.
552    Malformed,
553}
554
555impl CreatedEhr {
556    /// The `ehr_id`, when the answer named one.
557    #[must_use]
558    pub fn named(&self) -> Option<&str> {
559        match self {
560            Self::Named(ehr_id) => Some(ehr_id),
561            Self::Unnamed | Self::Malformed => None,
562        }
563    }
564
565    /// Why the record cannot tell the `ehr_id`, as a record writes it:
566    /// `unnamed` or `malformed`, or `None` when the answer named it.
567    #[must_use]
568    pub fn unread(&self) -> Option<&'static str> {
569        match self {
570            Self::Named(_) => None,
571            Self::Unnamed => Some("unnamed"),
572            Self::Malformed => Some("malformed"),
573        }
574    }
575}
576
577impl fmt::Debug for CreatedEhr {
578    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
579        match self {
580            Self::Named(_) => f.write_str("Named(..)"),
581            Self::Unnamed => f.write_str("Unnamed"),
582            Self::Malformed => f.write_str("Malformed"),
583        }
584    }
585}
586
587/// What the access asked for.
588#[derive(Clone)]
589pub struct Request {
590    /// The id the recording system knows the request by, which its own log
591    /// names too.
592    pub id: String,
593    /// The operation, such as an ITS-REST `operationId`.
594    pub operation: String,
595    /// The resource the request addressed, when it addressed one.
596    pub resource: Option<String>,
597    /// The query text, for a query.
598    pub query: Option<SecretString>,
599    /// The name of the stored query, for a stored-query execution.
600    pub stored_query: Option<String>,
601    /// The address the request came from, when known.
602    pub client_address: Option<IpAddr>,
603    /// The identifier the client correlates the request by in its own
604    /// log, such as a national contact point's exchange log, when it sent
605    /// one.
606    pub correlation: Option<String>,
607}
608
609impl fmt::Debug for Request {
610    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
611        f.debug_struct("Request")
612            .field("id", &self.id)
613            .field("operation", &self.operation)
614            .field("query", &self.query.is_some())
615            .field("correlation", &self.correlation.is_some())
616            .finish_non_exhaustive()
617    }
618}
619
620/// One access event (Annex II 3.2).
621#[derive(Clone)]
622pub struct AccessRecord {
623    /// What the access did.
624    pub action: Action,
625    /// When it ended (3.2(d)).
626    pub recorded: Timestamp,
627    /// How it ended.
628    pub outcome: Outcome,
629    /// Who accessed the data (3.2(a), (b)), with their purposes of use.
630    pub accessor: Accessor,
631    /// Whose data.
632    pub subject: DataSubject,
633    /// The categories of the data accessed (3.2(c)).
634    pub categories: Classification,
635    /// The rows or objects delivered to the accessor, when counted.
636    pub delivered: Option<usize>,
637    /// The origins of the data (3.2(e)), in the order the access asked them.
638    pub origins: Vec<Origin>,
639    /// What the access asked for.
640    pub request: Request,
641    /// How long the record is kept (Art 9(2), Annex II 3.4).
642    pub retention: Retention,
643    /// The security labels the record carries for its categories and its
644    /// origins, so the store that holds it can limit who reads it (Annex II
645    /// 3.4).
646    pub labels: Labels,
647    /// The mark of an access the accessor declared, through its purpose of
648    /// use, as made to protect the vital interests of the data subject (Art
649    /// 11(5)), when it did.
650    pub emergency: Option<Emergency>,
651    /// Who inserted the information the access wrote under the data
652    /// subject's own grant, which the written data marks as inserted by
653    /// them (Art 5), when it did.
654    pub inserted: Option<Insertion>,
655    /// Each semantic transformation the access applied between openEHR and
656    /// the exchange format (eHealth Network General Guidelines Release 3.1
657    /// Art 6(3)), in the order it applied them.
658    pub transformations: Vec<Transformation>,
659}
660
661/// Who inserted the information a write carried under the data subject's
662/// own grant, as the written data marks it (Art 5).
663#[derive(Debug, Clone, Copy, PartialEq, Eq)]
664#[non_exhaustive]
665pub enum Insertion {
666    /// The data subject, in person.
667    Subject,
668    /// A natural person who acts for the data subject, a proxy or a legal
669    /// representative (Art 4(2)), whom the written data names.
670    Representative,
671}
672
673impl fmt::Debug for AccessRecord {
674    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
675        f.debug_struct("AccessRecord")
676            .field("action", &self.action)
677            .field("recorded", &self.recorded)
678            .field("outcome", &self.outcome)
679            .field("accessor", &self.accessor)
680            .field("subject", &self.subject)
681            .field("categories", &self.categories)
682            .field("delivered", &self.delivered)
683            .field("origins", &self.origins)
684            .field("request", &self.request)
685            .field("retention", &self.retention)
686            .field("labels", &self.labels)
687            .field("emergency", &self.emergency)
688            .field("inserted", &self.inserted)
689            .field("transformations", &self.transformations)
690            .finish()
691    }
692}