Packages
hl7v2
3.10.1
3.10.1
3.9.0
3.8.0
3.7.0
3.6.0
3.5.0
3.4.0
3.3.6
3.3.5
3.3.4
3.3.3
3.3.2
3.3.1
3.3.0
3.2.0
3.1.1
3.1.0
3.0.2
3.0.1
3.0.0
2.11.0
2.10.0
2.9.1
2.9.0
2.8.2
2.8.1
2.8.0
2.7.1
2.7.0
2.6.0
2.5.0
2.4.0
2.3.0
2.2.0
2.1.3
2.1.2
2.1.1
2.1.0
1.4.6
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.3.0
1.2.0
1.1.0
1.0.0
0.6.0
0.5.6
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.1.0
Pure Elixir HL7 v2.x toolkit — schema-driven parsing, typed segments, message builder, MLLP transport
Current section
Files
Jump to
Current section
Files
lib/hl7v2/conformance/fixtures.ex
defmodule HL7v2.Conformance.Fixtures do
@moduledoc """
Conformance fixture corpus statistics.
The canonical structure list and fixture filenames are **frozen at compile
time** by walking the fixture directory and extracting MSH-9 from each wire
file. This means:
- `coverage/0`, `list_fixtures/0`, and `unique_canonical_structures/0` return
compile-time-frozen results with no runtime disk access. The fixture files
ship inside the Hex package, and CI verifies the tarball fixture count
matches the frozen list.
- Changing any fixture file triggers recompilation via `@external_resource`.
- Canonical resolution uses the same fallback logic as
`HL7v2.Validation` — if the trigger-specific structure is unregistered,
the bare message_code is tried before giving up. This correctly reports
`ACK` for `ACK^A01^ACK_A01`, not the unregistered `ACK_A01`.
This module is the single source of truth for fixture corpus counts
reported in docs, CHANGELOG, and `mix hl7v2.coverage`.
"""
@fixture_dir Path.expand("../../../test/fixtures/conformance", __DIR__)
@total_official 186
@fixtures (case File.ls(@fixture_dir) do
{:ok, entries} ->
entries |> Enum.filter(&String.ends_with?(&1, ".hl7")) |> Enum.sort()
_ ->
[]
end)
# Recompile this module whenever any fixture file changes.
for file <- @fixtures do
@external_resource Path.join(@fixture_dir, file)
end
# Compile-time canonical resolution. Extracts MSH-9 via lightweight string
# parsing (no dependency on the full parser at compile time) and applies
# the same alias fallback as the validator.
@frozen_canonical @fixtures
|> Enum.map(fn file ->
path = Path.join(@fixture_dir, file)
{:ok, content} = File.read(path)
first_line =
content
|> String.split(["\r", "\n"], trim: true)
|> List.first() || ""
fields = String.split(first_line, "|")
msh9 = Enum.at(fields, 8, "")
parts = String.split(msh9, "^")
code = Enum.at(parts, 0, "")
event = Enum.at(parts, 1, "")
resolved = HL7v2.MessageDefinition.canonical_structure(code, event)
cond do
HL7v2.Standard.MessageStructure.get(resolved) != nil ->
resolved
HL7v2.Standard.MessageStructure.get(code) != nil ->
code
true ->
nil
end
end)
|> Enum.reject(&is_nil/1)
|> Enum.uniq()
|> Enum.sort()
@frozen_families @frozen_canonical
|> Enum.map(fn name ->
name |> String.split("_") |> List.first()
end)
|> Enum.uniq()
|> Enum.sort()
@type coverage :: %{
files: non_neg_integer(),
canonical: non_neg_integer(),
total_official: non_neg_integer(),
pct: float()
}
@doc """
Returns a map summarizing the conformance fixture corpus.
- `:files` — number of `.hl7` fixture files
- `:canonical` — number of unique canonical message structures covered
- `:total_official` — 186 (HL7 v2.5.1 official structures)
- `:pct` — canonical / total_official as a percentage, rounded to 1 decimal
"""
@spec coverage() :: coverage()
def coverage do
%{
files: length(@fixtures),
canonical: length(@frozen_canonical),
total_official: @total_official,
pct: Float.round(length(@frozen_canonical) / @total_official * 100, 1)
}
end
@doc """
Returns the sorted list of fixture filenames included in the corpus.
"""
@spec list_fixtures() :: [binary()]
def list_fixtures, do: @fixtures
@doc """
Returns the sorted deduplicated list of canonical message structures
covered by the corpus. Uses the same alias fallback as validation, so
`ACK^A01^ACK_A01` resolves to the registered `ACK` structure rather than
the unregistered `ACK_A01`.
The `files` argument is ignored and kept for backwards compatibility —
the result is frozen at compile time regardless of input.
"""
@spec unique_canonical_structures([binary()]) :: [binary()]
def unique_canonical_structures(_files \\ nil), do: @frozen_canonical
@doc """
Returns the sorted list of message family prefixes covered by the corpus
(e.g. `"ADT"`, `"ORU"`, `"MFN"`). Derived from canonical structure names.
"""
@spec families() :: [binary()]
def families, do: @frozen_families
@doc """
Compares the compile-time-frozen fixture list against the current on-disk
state of the fixture directory.
Returns:
- `:ok` — frozen list matches on-disk state
- `{:stale, on_disk_only: [...], frozen_only: [...]}` — drift detected
- `{:error, :fixture_dir_unavailable}` — the fixture directory does not
exist or is unreadable (e.g. the corpus was not shipped in a Hex
artifact). As of v3.3.3 the corpus ships in the Hex package, so a
missing directory is itself a release-surface regression and is
reported loudly by default.
Options for dependency injection in tests and opt-out for edge cases:
- `:dir` — override the directory to compare against (default: compile-time
`@fixture_dir`)
- `:frozen` — override the frozen list (default: compile-time `@fixtures`)
- `:allow_missing` — when `true`, return `:ok` instead of
`{:error, :fixture_dir_unavailable}` when the directory is not
readable. Default: `false`.
"""
@spec check_freshness(keyword()) ::
:ok | {:stale, keyword()} | {:error, :fixture_dir_unavailable}
def check_freshness(opts \\ []) do
dir = Keyword.get(opts, :dir, @fixture_dir)
frozen = Keyword.get(opts, :frozen, @fixtures)
allow_missing = Keyword.get(opts, :allow_missing, false)
case File.ls(dir) do
{:ok, entries} ->
on_disk =
entries
|> Enum.filter(&String.ends_with?(&1, ".hl7"))
|> MapSet.new()
frozen_set = MapSet.new(frozen)
on_disk_only = MapSet.difference(on_disk, frozen_set) |> MapSet.to_list() |> Enum.sort()
frozen_only = MapSet.difference(frozen_set, on_disk) |> MapSet.to_list() |> Enum.sort()
if on_disk_only == [] and frozen_only == [] do
:ok
else
{:stale, on_disk_only: on_disk_only, frozen_only: frozen_only}
end
_ ->
if allow_missing, do: :ok, else: {:error, :fixture_dir_unavailable}
end
end
end