Skip to main content
Ralph Parkison
Haste Health maintainer

Healthcare engineer with over a decade of experience building clinical data platforms, interoperability tooling, and provider-facing workflows; Background includes working at Epic Systems and Commure and building FHIR tools with Augolit, Genfhi, and IGUHealth.

View all authors

July 2026 Feature Updates

· 11 min read
Ralph Parkison
Haste Health maintainer

July opened with authentication and session hardening — multi-factor authentication landed end to end, CSRF protections were added across the interaction and OIDC flows, and password hashing moved to argon2 — then pivoted toward workspace health for the back half of the month: a SMART configuration discovery endpoint, a full documentation reorganization, and a long crate-by-crate clippy remediation pass that touched nearly every corner of the backend.

Backend Changes

Multi-factor authentication, end to end

  • A new encryption crate landed first, with pluggable encryptor providers (AWS, GCP, environment) behind a shared trait, later reorganized into an encryption submodule and extended with AES support.
  • A user_mfa table and repository layer (backend/crates/repository/src/pg/mfa.rs) were added, followed by MFA admin CRUD routes, an activation route, and a TOTP verification route wired into the login flow.
  • The login process now checks for and enforces MFA, and the admin app's Settings view gained links into MFA management that route to the new API endpoints.
  • PRs: #785, #786, #787, #788, #799, #804, #807, #814.

CSRF and session hardening

  • A synchronizer token pattern was added across the global login, signup, and tenant-select routes via a new csrf_token extractor, then extended to the OIDC authorize, interaction login, password reset, and scope routes.
  • Authorization session state was reworked into explicit enums, tightening how the OIDC authorize, federated callback, interaction login/logout, and scope routes track session progress.
  • Password hashing was migrated from the previous scheme to argon2 in the user repository.
  • PRs: #775, #800, #801, #802.

Auditing and public metadata

  • A new auditing middleware (fhir_client/middleware/auditing.rs) was added and wired into the FHIR client pipeline and MCP routes, disabled by default and gated by config, with a follow-up pass fixing storage/transaction interplay and re-enabling it.
  • cargo-audit was added as a CI workflow to catch vulnerable dependencies on every push.
  • The server capabilities/metadata endpoint gained a config flag to expose it publicly without authentication.
  • PRs: #774, #783, #784, #812.

Product and UX

ViewDefinition editor follow-ups

  • A batch of shared read-only components (AddressReadOnly, CodeableConceptReadOnly, HumanNameReadOnly, ReferenceReadOnly, and others) were reworked to prepare the Analytics ViewDefinition editor for a read-only mode.
  • The editor picked up an onchange-driven alteration flow and simplified sql-on-fhir component internals, and the resource view was reworked into a dedicated ViewDefinition view with that read-only mode and improved field grouping.
  • PRs: #772, #773, #782.

Platform and Runtime

SMART configuration discovery

  • A SMART configuration discovery document was added at /w/{tenant}/{project}/api/v1/.well-known/smart-configuration per spec, wired into the OIDC middleware and server routes, then corrected to nest properly under the FHIR root.
  • PRs: #833.

FHIRPath and search engine fixes

  • FHIRPath gained its first custom function implementation alongside an exists() fix, followed by join(separator) support and, later in the month, numeric comparison operators (>, <, >=, <=).
  • Elasticsearch date search gained proper not-equals handling and correct range-overlap comparisons for dates, closing a long-standing search gap and landing with new base testscripts.
  • PRs: #770, #771, #859, #861.

Codegen and terminology: from consts to inline functions

  • Generated terminology structs were migrated to snake_case const codes, touching the codegen templates, generated FHIR terminology, and downstream consumers in access-control, fhir-client, and fhir-profiling.
  • That migration continued with generated inline functions for the various terminology struct variants, then a second pass removed the old const arrays in favor of those inline functions across access-control and the codegen crate.
  • File loading in codegen was made deterministic by sorting inputs, since walkdir traversal order can differ across platforms, and a further clippy pass cleaned up the generated FHIR model output.
  • The legacy hand-written serializer/deserializer implementations in fhir-serialization-json and fhir-model's datetime handling were removed now that the generated equivalents cover the same ground.
  • The US Core profile load script gained a verification step confirming all profiles were actually indexed.
  • PRs: #810, #813, #830, #832, #834, #840, #852, #853, #873.

Reliability and dependency hygiene

  • Panics in request handlers are now caught rather than taking down the server, via a new panic-catching layer in server.rs, and Sentry gained panic, backtrace, and transport features for better crash visibility.
  • The workspace's Cargo dependencies were deduplicated and pulled up to shared workspace versions, trimming Cargo.lock substantially and removing an unused dependency.
  • License metadata was corrected across several backend crates and frontend packages.
  • Import ordering in generated code was fixed to conform to rustfmt, and the workspace was brought under a rustfmt CI check with a full formatting pass.
  • PRs: #776, #803, #806, #808, #809.

Artifacts, Tooling, and Maintenance

Frontend build migration and dependency maintenance

  • The admin app moved from its previous bundler to Vite, replacing the old public index.html/config setup with vite.config.ts and updating Docker/nginx wiring to match.
  • Storybook was upgraded across the components package, and a dedicated frontend_build CI workflow and frontend_audit workflow were added, alongside a TypeScript version bump and inquirer bump.
  • A later audit pass fixed vulnerable dependencies flagged in the website package, and a broader round of frontend dependency bumps followed, touching the admin app's resource editor, search modal, and several project views.
  • PRs: #777, #778, #779, #780, #781, #824, #856.

CI, release, and local dev

  • The local docker-compose setup switched to a generic Elasticsearch image so it also runs on non-arm64 hosts, and the Docker base image was bumped.
  • The experimental wal_worker crate was pinned and then pulled back out of the backend build while it's still being hardened, and a stray deno dependency was pinned to stop undefined-symbol errors during workspace builds.
  • Module functions in the binary crate were made private where they didn't need to be public, tightening the crate's API surface.
  • Test and e2e CI workflows picked up build caching that only persists once a run lands on main, the release workflow got a similar cache-based speedup, and the Docker build gained its own caching pass.
  • PRs: #790, #792, #793, #795, #796, #798, #829, #839, #843, #844, #860, #862.

Documentation overhaul

  • The docs site was reorganized, splitting authentication and authorization into their own sections, and generated FHIR docs were refreshed to include the new HL7V2 and ViewDefinition resources.
  • New guides were written for profile validation, SQL on FHIR, and HL7v2, plus a server configuration reference linked from both the root and backend READMEs.
  • CLI command documentation is now generated from the binary itself via clap-markdown, and the root README picked up a "Running Locally" section and other updates.
  • Repo .gitattributes were adjusted to mark generated TypeScript as generated, so GitHub's language detection reports this as a Rust repository.
  • PRs: #815, #816, #817, #818, #819, #820, #821, #865, #866, #871, #872.

A workspace-wide clippy pass

  • The last third of the month was dominated by a sequential, crate-by-crate clippy remediation effort covering access-control, artifacts, codegen, config, fhir-client, fhir-operation, fhir-operation-error, fhir-ops, fhir-query, fhir-reflect, fhir-serialization, fhir-terminology, fhirpath, jwt, pointer, testscript-runner, and the server crate itself, bringing the workspace much closer to a clean cargo clippy run.
  • PRs: #825, #826, #827, #828, #835, #837, #846, #848, #849, #850, #851, #854, #855, #858, #863, #867.
  • Backend/config fixes: re-enabled TLS for the SendGrid client, enabled a request-blocking client on the OTLP transport, nested the SMART configuration document under the FHIR root, and an ends_with reversion.
  • Docs and repo hygiene: a broken link fix, a clarification that SQL on FHIR does not yet support EHR launch, removal of a stale repository-structure section from the README, and a Sonar workflow condition update.
  • CI/deploy: a docker build-push-action change, a wrangler version bump, forcing a CI cache rebuild, a release-binary credentials listing fix, and a repo-root specifier fix for release binaries.
  • Version bumps: 54aa1e5ed, a43920942.

Contributors

Thanks to Luni-4 for contributions all month long, starting with fixing local project builds on non-arm64 hosts and a stale deno_core pin (#790), making docker-compose more portable (#793), tightening module visibility in the binary crate (#795), deduplicating the Cargo workspace's dependencies (#803), and bringing the whole workspace under a consistent rustfmt style (#808). The back half of the month was carried almost entirely by their crate-by-crate clippy remediation effort, covering fhir-reflect and fhir-serialization (#825), fhir-query, pointer, and jwt (#826), fhir-client, fhir-operation, and fhirpath (#827), access-control (#828, #835), a pinned deno dependency fix (#839), artifacts and codegen (#846, #837, #867), fhir-ops, config, fhir-terminology, and fhir-operation-error (#848, #849, #850, #851), testscript-runner (#855), and fhir-client again (#858, #854).

June 2026 Feature Updates

· 6 min read
Ralph Parkison
Haste Health maintainer

June was dominated by two new engines landing side by side: HL7v2/MLLP message ingestion through a template-driven FHIR Converter, and a SQL on FHIR ViewDefinition runtime. Both picked up their own admin/analytics UI by month end, and FHIRPath grew new built-ins to support them.

Backend Changes

HL7v2 ingestion and the FHIR Converter engine

  • The hl7v2 crate matured quickly: an explicit structure definition, sub-component and nested-field parsing fixes, primitive From impls, FHIR model parsing, serialization back to wire format, and null-handling fixes found while running real messages through the pipeline.
  • A new fhir-converter crate became the conversion engine, experimenting with both minijinja and Liquid templating and settling on Jinja-style templates with partial support, plus dedicated FHIRPath and HL7v2 filters and reflection-driven fhir_type generation to keep templates aligned with the generated FHIR model.
  • An MLLP agent and listener were stood up and plugged into a new hl7v2 CLI command, with a custom hl7v2_parse operation and HL7v2 StructureDefinition added to the FHIR server and frontend artifacts regenerated to match.
  • Deserialization in fhir-model was hardened along the way to absorb the loosely structured data produced by conversion (empty strings, empty structs, vector edge cases).
  • PRs: #713, #714, #715, #716, #717, #718, #719, #720, #721, #722, #723, #724, #725, #726, #727, #728, #729, #730, #731, #732, #733, #734, #735, #736, #737, #738.

SQL on FHIR: a ViewDefinition execution engine

  • ViewDefinition landed as a first-class resource with a run operation wired into the custom operations middleware, moved into its own standalone sql-on-fhir crate.
  • The projection engine grew column/select/forEach/forEachOrNull handling, cartesian products for repeating elements, null propagation, type inference, where-clause filtering, constants, and support for views that repeat over nested ViewDefinitions.
  • CSV output formatting and primitive conversion were built out, and task execution moved away from spawning a task per row for better throughput.
  • The run operation now supports JSON and NDJSON output in addition to CSV, including dedicated output writers in sql-on-fhir and format selection in the shared frontend components package.
  • PRs: #739, #740, #741, #744, #745, #746, #747, #748, #752, #753, #754, #755, #756, #757, #758, #759, #760, #761, #766.

Product and UX

Analytics: a ViewDefinition editor

  • Shipped the first cut of a ViewDefinition editor in the admin app's Analytics views, backed by a new sql-on-fhir component package (initially prototyped as fhir-on-sql), with a follow-up pass exploring alternate projection layouts.
  • Frontend FHIR types and generated ops were regenerated to pick up ViewDefinition and the HL7v2 additions.
  • Late in the month, the editor and artifacts were aligned to migrated ViewDefinition typing so generated backend/frontend models and artifact loading stayed in sync.
  • PRs: #749, #750, #751, #767.

Platform and Runtime

  • FHIRPath gained getReferenceKey and getResourceKey built-ins so views can resolve and dedupe on references, plus a shared FHIRPath/rule-engine config builder used across fhirpath, sql-on-fhir, and x-fhir-query.
  • Type-choice serialization and deserialization were fixed up in fhir-model to correctly round-trip value[x]-style fields.
  • Parameter handling was simplified across the server: URL building in fhir-client, the search parameter resolver, OIDC token routes, project selection, and artifact loading were all trimmed down.
  • Server and worker startup moved to typed config loading (TOML + HASTE_ environment overlays) using Figment, replacing ad-hoc environment variable reads and unifying configuration paths across commands.
  • Deployment and local runtime wiring were updated to match nested config keys (HASTE_REPO.*, HASTE_SEARCH.*) in compose and CI paths.
  • PRs: #742, #762, #763, #764, #765, #768, #769.

Artifacts, Tooling, and Maintenance

  • Dependency maintenance included bumping multer in the frontend, a SonarQube workflow version bump, and a couple of version bumps for the fhir-converter crate.
  • Auth docs got a round of URL corrections across the authorization code, client credentials, refresh token, and SMART on FHIR pages.
  • PRs: #743.

May 2026 Feature Updates

· 5 min read
Ralph Parkison
Haste Health maintainer

May was a heavy platform month. We shipped core runtime capabilities, expanded FHIR coverage, improved admin/history UX, and finished the week with worker orchestration and observability upgrades.

Backend Changes

Serialization and deserialization overhaul

Custom executor and Deno sandbox progression

  • Added custom executor support, parameter validation, and the needed code input context.
  • Advanced Deno snapshot generation, OpState handling, return plumbing, entry updates, and plugin experimentation.
  • PRs: #617, #618, #619, #620, #621, #624, #626, #629, #630, #651, #652.

Worker and observability push this week

  • Introduced trait-driven worker and sequence abstractions, stop-work behavior, and locking model updates.
  • Expanded tracing and OpenTelemetry work, including runtime OLTP config updates.
  • PRs: #703, #704, #705, #706, #707, #708, #709, #710, #711, #712.

Product and UX

Admin, dashboard, and history

Forms, auth, and website polish

  • FHIR form generation completed remaining primitive + editable complex form components.
  • Auth/input polish included header client credentials and charset handling.
  • Website updates included storybook theming, link refreshes, dashboard reorg, banner sizing, and author/team-page docs polish.
  • PRs: #657, #658, #662, #663, #694.

Platform and Runtime

  • FHIRPath and executor behavior improvements (upper/lower helpers, runtime stability, and performance tweaks).
  • Runtime/perf tuning included allocation separation and bundle full URL handling.
  • Admin/platform controls expanded with version UI, artifact command resets, project/IDP tier limits, and active-index filtering.
  • PRs: #627, #628, #631, #632, #634, #676, #677, #680, #691, #692, #693.

Artifacts, Tooling, and Maintenance

  • Subscription artifact work landed with R5 Subscription Backports.
  • Codegen/docs workflow improved with operation-definition doc generation, naming cleanup, and line-split scripts.
  • Dependency and maintenance cleanup continued with package and repository maintenance updates.
  • PRs: #696, #697, #698, #699, #700, #701, #702, #687.

April 2026 Feature Updates

· 4 min read
Ralph Parkison
Haste Health maintainer

March 2026 Feature Updates

· 3 min read
Ralph Parkison
Haste Health maintainer

February 2026 Feature Updates

· 3 min read
Ralph Parkison
Haste Health maintainer

January 2026 Feature Updates

· 3 min read
Ralph Parkison
Haste Health maintainer

Writing a High-Performance Clinical Data Repository in Rust

· 16 min read
Ralph Parkison
Haste Health maintainer

Background information

Rust is a systems programming language focused on safety, speed, and concurrency. We use Rust as the primary language for our backend at Haste Health.

  • FHIR - Modern standard for storing/retrieving healthcare data.
  • Hl7V2 - Legacy healthcare standard for messaging healthcare data.

Why Rust

Performance matters in healthcare

Healthcare systems process massive amounts of data. Most healthcare systems broadcast their data and events using a legacy standard called Hl7V2. To have a real time clinical data repository a neccessary requirement is to be able to injest these Hl7V2 messages.

Hl7V2 messages can be published for a variety of events within a healthcare system including patient admission, patient discharge, medication orders as a few examples. For most large healthcare systems this ends up being millions of Hl7V2 messages being sent daily.

Compounding on top of this is that the translation from Hl7V2 to other standards often requires mutating or creating multiple resources. As an example an ADT (Admit, Discharge, Transfer) Hl7V2 Message can provide information for a Patient, details about their Hospital visit, observations and clinical information recorded during their visit. A single message may require your system to perform multiple operations at once see below for an example.

The following are a few real world examples:

  • Healthcare System A

    • ~8 million HL7v2 messages a day
    • ~15 FHIR resources created per message for their implementation
    • ~1,500 FHIR resources/second being streamed out of their EHR.
  • Healthcare System B

    • ~15 million Hl7V2 message accross their organization
    • ~8,000 FHIR resources/second being streamed out of their EHR

Processing healthcare data can be compute intensive:

For our CDR we natively store data as FHIR resources. This requires a significant amount of data processing in the application. For each Resource created we must index it according to SearchParameter resources. These are FHIR resources that define what parameters are available to search for a given resource. And how to index resources by extracting values from the resource using FHIRPath which is a query language similiar to JSONPath.

For example the Patient resource contains the following parameters (+ a few of our custom parameters for our system):

ParameterFHIRPath Expression
_haste-health-author (reference)Resource.meta.extension.where(url='https://haste.health/author').value
_type (token)$this.type().name
_id (token)Resource.id
_lastUpdated (date)Resource.meta.lastUpdated
_profile (uri)Resource.meta.profile
_security (token)Resource.meta.security
_source (uri)Resource.meta.source
_tag (token)Resource.meta.tag
active (token)Patient.active
address (string)Patient.address / Person.address / Practitioner.address / RelatedPerson.address
address-city (string)Patient.address.city / Person.address.city / Practitioner.address.city / RelatedPerson.address.city
address-country (string)Patient.address.country / Person.address.country / Practitioner.address.country / RelatedPerson.address.country
address-postalcode (string)Patient.address.postalCode / Person.address.postalCode / Practitioner.address.postalCode / RelatedPerson.address.postalCode
address-state (string)Patient.address.state / Person.address.state / Practitioner.address.state / RelatedPerson.address.state
address-use (token)Patient.address.use / Person.address.use / Practitioner.address.use / RelatedPerson.address.use
birthdate (date)Patient.birthDate / Person.birthDate / RelatedPerson.birthDate
death-date (date)(Patient.deceased.ofType(dateTime))
deceased (token)Patient.deceased.exists() and Patient.deceased != false
email (token)Patient.telecom.where(system='email') / Person.telecom.where(system='email') / Practitioner.telecom.where(system='email') / PractitionerRole.telecom.where(system='email') / RelatedPerson.telecom.where(system='email')
family (string)Patient.name.family / Practitioner.name.family
gender (token)Patient.gender / Person.gender / Practitioner.gender / RelatedPerson.gender
general-practitioner (reference)Patient.generalPractitioner
given (string)Patient.name.given / Practitioner.name.given
identifier (token)Patient.identifier
language (token)Patient.communication.language
link (reference)Patient.link.other
name (string)Patient.name
organization (reference)Patient.managingOrganization
phone (token)Patient.telecom.where(system='phone') / Person.telecom.where(system='phone') / Practitioner.telecom.where(system='phone') / PractitionerRole.telecom.where(system='phone') / RelatedPerson.telecom.where(system='phone')
phoneticPatient.name / Person.name / Practitioner.name / RelatedPerson.name
telecom (token)Patient.telecom / Person.telecom / Practitioner.telecom / PractitionerRole.telecom / RelatedPerson.telecom

When a Patient resource is created we evaluate all of these parameters with fhirpath in the application and then index the extracted values to our search store.

Here is an example of indexing the name Parameter for a Patient resource:

Given the following Patient resource:

{
"resourceType": "Patient",
"id": "example",
"name": [
{
"use": "official",
"family": "Chalmers",
"given": ["Peter", "James"]
},
{
"use": "usual",
"given": ["Jim"]
}
]
}

The name parameter uses the FHIRPath expression Patient.name which extracts all name elements from the Patient resource. In this case

  • { "use": "official", "family": "Chalmers", "given": ["Peter", "James"] }
  • { "use": "usual", "given": ["Jim"] }.

We then run a conversion on these name elements to extract string tokens for indexing. From the first name element we extract the following tokens: Chalmers, Peter, James. From the second name element we extract the token: Jim.

For processing a single Patient resource you end up running ~25 FHIRPath expressions. Converting the results to indexable tokens and then writing those to your search index.

Compounding on top of this FHIR has a concept of transaction/batch processing where multiple resources can be created/updated/deleted in a single request.

Processing FHIR Transactions:

In FHIR a transaction is a bundle of multiple operations that must be processed atomically. Each operation within the transaction may have inter-dependencies between operations within the transaction.

For example an operation may create a Patient resource and a subsequent operation within the same transaction may create an Observation resource that references the newly created Patient. The following bundle creates a Patient, Encounter and an Observation within a single transaction and links them together via references.

{
"resourceType": "Bundle",
"type": "transaction",
"entry": [
{
"fullUrl": "observation",
"resource": {
"resourceType": "Observation",
"identifier": [
{
"use": "official",
"system": "http://www.bmc.nl/zorgportal/identifiers/observations",
"value": "6326"
}
],
"status": "final",
"code": {
"coding": [
{
"system": "http://loinc.org",
"code": "789-8",
"display": "Erythrocytes [#/volume] in Blood by Automated count"
}
]
},
"subject": {
"reference": "patient"
},
"encounter": {
"reference": "encounter"
},
"effectivePeriod": {
"start": "2013-04-02T10:30:10+01:00",
"end": "2013-04-05T10:30:10+01:00"
},
"issued": "2013-04-03T15:30:10+01:00",
"performer": [
{
"reference": "Practitioner/f005",
"display": "A. Langeveld"
}
],
"valueQuantity": {
"value": 4.12,
"unit": "10^12/L",
"system": "http://unitsofmeasure.org",
"code": "10*12/L"
},
"interpretation": [
{
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation",
"code": "L",
"display": "Low"
}
]
}
],
"referenceRange": [
{
"text": " 12-14 y Male: 4.4 - 5.2 x 10^12/L ; 12-14 y Female: 4.2 - 4.8 x 10^12/L ; 15-17 y Male: 4.6 - 5.4 x 10^12/L ; 15-17 y Female: 4.2 - 4.8 x 10^12/L ; 18-64 y Male: 4.6 - 5.4 x 10^12/L ; 18-64 y Female: 4.0 - 4.8 x 10^12/L ; 65-74 y Male: 4.3 - 5.3 x 10^12/L ; 65-74 y Female: 4.1 - 4.9 x 10^12/L "
}
]
},
"request": {
"method": "POST",
"url": "Observation"
}
},
{
"fullUrl": "patient",
"resource": {
"resourceType": "Patient",
"name": [{ "given": ["Bob"], "family": "Peter" }]
},
"request": {
"method": "POST",
"url": "Patient"
}
},
{
"fullUrl": "encounter",
"resource": {
"resourceType": "Encounter",
"id": "example",
"text": {
"status": "generated",
"div": "<div xmlns=\"http://www.w3.org/1999/xhtml\">Encounter with patient @example</div>"
},
"status": "in-progress",
"class": {
"system": "http://terminology.hl7.org/CodeSystem/v3-ActCode",
"code": "IMP",
"display": "inpatient encounter"
},
"subject": {
"reference": "patient"
}
},

"request": {
"method": "POST",
"url": "Encounter"
}
}
]
}

This requires the application to process each operation in correct order. In our case we go through a resources fields and check for links between them. Afterwards we build a dependency graph of all operations within the transaction and perform a topological sort to determine the correct order to process each operation.

All of this processing requires significant CPU resources. Using Rust allows us to maximize performance in these hot paths of the application.

Here are a few benchmarks we have collected comparing our Rust implementation vs our Typescript implementation.

Benchmarking Rust vs Typescript implementations.

FHIRPath Benchmarks:

RustTypescriptDescription
11.673 µs51.49 µsEvaluate all patient search parameters against a synthea generated patient

Transaction Processing Benchmarks:

RustTypescriptDescription
39ms110 msUsing a synthea transaction bundle with ~700 resources.

Challenges in Rust

The following are a few challenges encountered when building a large-scale clinical data repository in Rust.

Rust does not have reflection built in

Rust does not have a native reflection system like Java, We needed this for our FHIRPath engine to be able to evaluate FHIRPath expressions on arbitrary FHIR resources and for pieces down the road like FHIR Profiling.

We ended up building a simple custom derive macro that generates code to allow us to introspect FHIR resources at runtime.

Occasionally hitting a roadblock with the borrow checker

Broadly speaking Rust's borrow checker errors were relatively straightforward to solve. However, we ran into a few specific scenarios that were tricky.

For example:

In our FP implementation we generally want to return references to data within the resource being queried. And these references need to have the same lifetime as the resource being queried. However, it is also possible to return owned data from FP expressions; for example, the following FHIRPath 'my-string' would create a new string. This made it tricky to define the correct lifetimes for our evaluation context and return types.

What ultimately worked for us was to use an internal data structure to allocate owned data into a vector and then return references to that vector. At the end of the evaluation we return a wrapped value with the data allocated in the vector and references to either the data owned in the vector or to the resource passed in.

** We weren't able to use Cow here because the returned value was a trait not compatible with Cow **

Custom Serialization/Deserialization

FHIR is a very complex standard with many nuances. We had to implement custom serialization/deserialization for FHIR. The default serde_json implementation does not cover several edge cases within FHIR. Although not specific to Rust, this required building custom serialization/deserialization.

We ended up building a custom derive macro here. Here's a few examples of edge cases unique to FHIR Json serialization/deserialization.

  • Primitive types with extensions In FHIR primitive types can have extensions. These are represented in JSON by having a field with the same name as the primitive prefixed with an underscore. For example Patient.name.family is a FHIR string primitive. If it has extensions it would be represented in JSON as both Patient.name.family and for extension Patient.name._family.
  • Type choices In FHIR certain fields can be one of multiple types. These are represented in JSON by having the field name suffixed with the type name. For Example Patient.deceased can be a boolean or a dateTime. In JSON these would be represented as either deceasedBoolean or deceasedDateTime.

The following example shows "Patient.name.family" which is a FHIR string primitive with an extension, "Patient.name.given" primitve array with extensions. Finally "deceased" which is primitive with an extension and is a typechoice.

{
"resourceType": "Patient",
"id": "pw7n1cwjla0vek6vg0u3bjm9r3",
"meta": {
"extension": [
{
"url": "https://haste.health/author",
"valueReference": {
"reference": "Membership/tfjpl0ob8rpvthppkk0uxks-gf"
}
}
],
"versionId": "g43wfhebt6frvk1men5b6i0t42",
"lastUpdated": "2026-01-24T03:19:46.542+00:00"
},
"name": [
{
"family": "example",
"_family": {
"extension": [
{
"url": "http://example.org/fhir/StructureDefinition/custom-extension",
"valueString": "custom value"
}
]
},
"_given": [
{
"extension": [
{
"url": "http://example.org/fhir/StructureDefinition/extension-1",
"valueString": "value-1"
}
]
},
{
"extension": [
{
"url": "http://example.org/fhir/StructureDefinition/extension-2",
"valueString": "value-2"
}
]
},
{
"extension": [
{
"url": "http://example.org/fhir/StructureDefinition/extension-3",
"valueString": "value-3"
}
]
}
],
"given": [
"Bob",
"Peter",
"James"
]
}
],
"deceasedDateTime": "1974-12-25",
"_deceasedDateTime": {
"extension": [
{
"url": "http://hl7.org/fhir/StructureDefinition/birthdate-ext",
"valueCode": "estimated"
}
]
}
}

This would deserialize into the following Rust structure:

Patient {
id: Some(
"pw7n1cwjla0vek6vg0u3bjm9r3",
),
meta: Some(
Meta {
id: None,
extension: Some(
[
Extension {
id: None,
extension: None,
url: "https://haste.health/author",
value: Some(
Reference(
Reference {
id: None,
extension: None,
reference: Some(
FHIRString {
id: None,
extension: None,
value: Some(
"Membership/tfjpl0ob8rpvthppkk0uxks-gf",
),
},
),
type_: None,
identifier_: None,
display: None,
},
),
),
},
],
),
versionId: Some(
FHIRId {
id: None,
extension: None,
value: Some(
"bnim6uouyqzeajzp2gbxad4nla",
),
},
),
lastUpdated: Some(
FHIRInstant {
id: None,
extension: None,
value: Some(
Iso8601(
2026-01-24T03:24:17.805Z,
),
),
},
),
source: None,
profile: None,
security: None,
tag: None,
},
),
implicitRules: None,
language: None,
text: None,
contained: None,
extension: None,
modifierExtension: None,
identifier_: None,
active: None,
name: Some(
[
HumanName {
id: None,
extension: None,
use_: None,
text: None,
family: Some(
FHIRString {
id: None,
extension: Some(
[
Extension {
id: None,
extension: None,
url: "http://example.org/fhir/StructureDefinition/custom-extension",
value: Some(
String(
FHIRString {
id: None,
extension: None,
value: Some(
"custom value",
),
},
),
),
},
],
),
value: Some(
"example",
),
},
),
given: Some(
[
FHIRString {
id: None,
extension: Some(
[
Extension {
id: None,
extension: None,
url: "http://example.org/fhir/StructureDefinition/extension-1",
value: Some(
String(
FHIRString {
id: None,
extension: None,
value: Some(
"value-1",
),
},
),
),
},
],
),
value: Some(
"Bob",
),
},
FHIRString {
id: None,
extension: Some(
[
Extension {
id: None,
extension: None,
url: "http://example.org/fhir/StructureDefinition/extension-2",
value: Some(
String(
FHIRString {
id: None,
extension: None,
value: Some(
"value-2",
),
},
),
),
},
],
),
value: Some(
"Peter",
),
},
FHIRString {
id: None,
extension: Some(
[
Extension {
id: None,
extension: None,
url: "http://example.org/fhir/StructureDefinition/extension-3",
value: Some(
String(
FHIRString {
id: None,
extension: None,
value: Some(
"value-3",
),
},
),
),
},
],
),
value: Some(
"James",
),
},
],
),
prefix: None,
suffix: None,
period: None,
},
],
),
telecom: None,
gender: None,
birthDate: None,
deceased: Some(
DateTime(
FHIRDateTime {
id: None,
extension: Some(
[
Extension {
id: None,
extension: None,
url: "http://hl7.org/fhir/StructureDefinition/birthdate-ext",
value: Some(
Code(
FHIRCode {
id: None,
extension: None,
value: Some(
"estimated",
),
},
),
),
},
],
),
value: Some(
YearMonthDay(
1974,
12,
25,
),
),
},
),
),
address: None,
maritalStatus: None,
multipleBirth: None,
photo: None,
contact: None,
communication: None,
generalPractitioner: None,
managingOrganization: None,
link: None,
}

Where Rust shines

Data Modeling and validation

The FHIR standard defines it's data model with a metadata resource called StructureDefinition. This resource behaves similiarly to a JSON Schema or Protobuf definition. It defines the fields, types and constraints for each FHIR resource. We use these StructureDefinitions to generate Rust types for each FHIR resource. A nice aspect of our generated rust types is how much we are able to represent as enums in rust. For example Patient.gender is a code [string value with a fixed set of allowed values]. What values are allowed are defined here.

We were able to represent these various terminologies in rust as follows::

#[derive(Debug, Clone, FHIRJSONSerialize, FHIRJSONDeserialize)]
#[fhir_serialize_type = "valueset"]
pub enum AdministrativeGender {
#[doc = "Female"]
#[code = "female"]
Female(Option<Element>),
#[doc = "Male"]
#[code = "male"]
Male(Option<Element>),
#[doc = "Other"]
#[code = "other"]
Other(Option<Element>),
#[doc = "Unknown"]
#[code = "unknown"]
Unknown(Option<Element>),
#[doc = "If value is missing and just the element is present."]
Null(Option<Element>),
}

Because rust enums support nesting structs we can also represent FHIR extensions on these code values. For example

{
"resourceType": "Patient",
"_gender": {
"extension": [
{
"url": "https://haste.health/custom-extension",
"valueCode": "my-value"
}
]
},
"gender": "male"
}

gets deserialized into:

Patient {
...
gender: Some(
AdministrativeGender::Male (
Some(
Element {
...,
extension: Some(
[
Extension {
...
url: "https://haste.health/custom-extension",
value: Some(
Code(
FHIRCode {
id: None,
extension: None,
value: Some(
"my-value",
),
},
),
),
},
],
),
},
),
),
),
...
}

Because validation of these code values is enforced at the type level we get a lot of safety when working with FHIR data. For example you cannot accidentally assign an invalid value to Patient.gender.

Also because we deserialize directly into types we get very fast validation of a significant subset of FHIR terminologies.

Ecosystem and Cargo

Similiar to Clojure ecosystem the Rust libraries are generally very high quality and well maintained.
We ended up using axum for a backend and have found it to be flexible and easy to integrate with other libraries.

Cargo the Rust package manager is also fantastic. We setup our repository as a monorepo which cargo supports natively via workspaces.

Conclusion

Using Rust for processing FHIR resources in our clinical data repository has provided significant performance improvements in critical areas such as FHIRPath evaluation, search parameter indexing, and FHIR transaction processing.

Coming from a background in higher-level languages like TypeScript and Clojure, Rust was easy to build performant and reliable systems for handling complex healthcare data.

Hl7V2 Example

Using FHIR-Converter for an ADT A01 hl7v2 message we get corresponding FHIR transaction bundle. Each entry within the FHIR Bundle corresponds to an individual operation (PUT Patient, Put Practitioner etc...). When the system receives this bundle it processes all of these operations at once.

ADT A01 Raw Message

MSH|^~\\&|SIMHOSP|SFAC|RAPP|RFAC|20200508131015||ADT^A01|517|T|2.3|||AL||44|ASCII\nEVN|A01|20200508131015|||C005^Whittingham^Sylvia^^^Dr^^^DRNBR^PRSNL^^^ORGDR|\nPID|1|3735064194^^^SIMULATOR MRN^MRN|3735064194^^^SIMULATOR MRN^MRN~2021051528^^^NHSNBR^NHSNMBR||

Converts to the following:

FHIR Bundle

{
"resourceType": "Bundle",
"type": "transaction",
"timestamp": "2020-05-08T13:10:15Z",
"identifier": {
"value": "517"
},
"id": "7dcb7d92-7a75-3d65-42f9-0f790afac4db",
"entry": [
{
"fullUrl": "urn:uuid:aa521dd9-b613-0210-a661-82ce17e38fb3",
"resource": {
"resourceType": "MessageHeader",
"id": "aa521dd9-b613-0210-a661-82ce17e38fb3",
"source": {
"name": "SIMHOSP",
"_endpoint": {
"extension": [
{
"url": "http://hl7.org/fhir/StructureDefinition/data-absent-reason",
"valueCode": "unknown"
}
]
}
},
"destination": [
{
"name": "RAPP",
"_endpoint": {
"extension": [
{
"url": "http://hl7.org/fhir/StructureDefinition/data-absent-reason",
"valueCode": "unknown"
}
]
},
"receiver": {
"reference": "Organization/ab0676d9-fa24-ffd3-327d-f5b4c26ae628"
}
}
],
"meta": {
"tag": [
{
"code": "T",
"system": "http://terminology.hl7.org/CodeSystem/v2-0103"
}
]
},
"eventCoding": {
"code": "A01",
"system": "http://terminology.hl7.org/CodeSystem/v2-0003",
"display": "ADT^A01"
},
"sender": {
"reference": "Organization/33e23f3e-47b2-9c1b-c1f0-f964c2d28946"
}
},
"request": {
"method": "PUT",
"url": "MessageHeader/aa521dd9-b613-0210-a661-82ce17e38fb3"
}
},
{
"fullUrl": "urn:uuid:428efe87-9b32-2610-d2d2-0edfdc356ebe",
"resource": {
"resourceType": "Provenance",
"id": "428efe87-9b32-2610-d2d2-0edfdc356ebe",
"occurredDateTime": "2020-05-08T13:10:15Z",
"recorded": "2020-05-08T13:10:15Z",
"agent": [
{
"type": {
"coding": [
{
"code": "author",
"system": "http://terminology.hl7.org/CodeSystem/provenance-participant-type"
}
]
},
"who": {
"reference": "Organization/33e23f3e-47b2-9c1b-c1f0-f964c2d28946"
}
}
],
"activity": {
"coding": [
{
"display": "ADT^A01^"
}
]
},
"target": [
{
"reference": "Bundle/7dcb7d92-7a75-3d65-42f9-0f790afac4db"
}
]
},
"request": {
"method": "PUT",
"url": "Provenance/428efe87-9b32-2610-d2d2-0edfdc356ebe"
}
},
{
"fullUrl": "urn:uuid:33e23f3e-47b2-9c1b-c1f0-f964c2d28946",
"resource": {
"resourceType": "Organization",
"id": "33e23f3e-47b2-9c1b-c1f0-f964c2d28946",
"identifier": [
{
"value": "SFAC",
"system": "http://example.com/v2-to-fhir-converter/Identifier/SFAC"
}
],
"address": [
{
"country": "44"
}
]
},
"request": {
"method": "PUT",
"url": "Organization/33e23f3e-47b2-9c1b-c1f0-f964c2d28946"
}
},
{
"fullUrl": "urn:uuid:ab0676d9-fa24-ffd3-327d-f5b4c26ae628",
"resource": {
"resourceType": "Organization",
"id": "ab0676d9-fa24-ffd3-327d-f5b4c26ae628",
"identifier": [
{
"value": "RFAC",
"system": "http://example.com/v2-to-fhir-converter/Identifier/RFAC"
}
],
"address": [
{
"country": "44"
}
]
},
"request": {
"method": "PUT",
"url": "Organization/ab0676d9-fa24-ffd3-327d-f5b4c26ae628"
}
},
{
"fullUrl": "urn:uuid:9d697ec3-48c3-3e17-db6a-29a1765e22c6",
"resource": {
"resourceType": "Patient",
"id": "9d697ec3-48c3-3e17-db6a-29a1765e22c6",
"identifier": [
{
"value": "3735064194",
"type": {
"coding": [
{
"code": "MRN",
"display": "MRN"
}
]
},
"system": "http://example.com/v2-to-fhir-converter/Identifier/SIMULATOR-MRN"
},
{
"value": "3735064194",
"type": {
"coding": [
{
"code": "MRN",
"display": "MRN"
}
]
},
"system": "http://example.com/v2-to-fhir-converter/Identifier/SIMULATOR-MRN"
},
{
"value": "2021051528",
"type": {
"coding": [
{
"code": "NHSNMBR",
"display": "NHSNMBR"
}
]
},
"system": "http://example.com/v2-to-fhir-converter/Identifier/NHSNBR"
}
]
},
"request": {
"method": "PUT",
"url": "Patient/9d697ec3-48c3-3e17-db6a-29a1765e22c6"
}
},
{
"fullUrl": "urn:uuid:4f1394cd-4fdc-2671-f985-0ef923c48c5c",
"resource": {
"resourceType": "Provenance",
"id": "4f1394cd-4fdc-2671-f985-0ef923c48c5c",
"recorded": "2020-05-08T13:10:15Z",
"agent": [
{
"who": {
"reference": "Practitioner/07cb183d-bae8-cbc9-56cc-764ecfa02850"
}
}
],
"activity": {
"text": "ADT^A01"
},
"target": [
{
"reference": "Bundle/7dcb7d92-7a75-3d65-42f9-0f790afac4db"
}
]
},
"request": {
"method": "PUT",
"url": "Provenance/4f1394cd-4fdc-2671-f985-0ef923c48c5c"
}
},
{
"fullUrl": "urn:uuid:07cb183d-bae8-cbc9-56cc-764ecfa02850",
"resource": {
"resourceType": "Practitioner",
"id": "07cb183d-bae8-cbc9-56cc-764ecfa02850",
"identifier": [
{
"value": "C005",
"system": "http://example.com/v2-to-fhir-converter/Identifier/DRNBR",
"type": {
"coding": [
{
"code": "ORGDR",
"display": "ORGDR"
}
]
}
}
],
"name": [
{
"family": "Whittingham",
"given": [
"Sylvia"
],
"prefix": [
"Dr"
],
"use": "PRSNL"
}
]
},
"request": {
"method": "PUT",
"url": "Practitioner/07cb183d-bae8-cbc9-56cc-764ecfa02850"
}
}
]
}