Packages
hl7v2
2.11.0
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/message.ex
defmodule HL7v2.Message do
@moduledoc """
Programmatic HL7v2 message construction.
Low-level message constructor for building HL7v2 messages from typed segment
structs. The MSH segment is auto-populated with sensible defaults (field
separator, encoding characters, processing ID, version, timestamp, control ID)
and can be overridden via opts.
**Note:** This is an append-only builder — it does not enforce segment ordering
or required-segment presence. Use `HL7v2.validate/1` after building to check
structural conformance.
## Examples
msg =
HL7v2.Message.new("ADT", "A01",
sending_application: "PHAOS",
sending_facility: "HOSP"
)
|> HL7v2.Message.add_segment(%HL7v2.Segment.PID{
patient_identifier_list: [%HL7v2.Type.CX{id: "12345"}],
patient_name: [%HL7v2.Type.XPN{family_name: %HL7v2.Type.FN{surname: "Smith"}, given_name: "John"}]
})
wire = HL7v2.Message.encode(msg)
"""
alias HL7v2.MessageDefinition
alias HL7v2.Segment.MSH
alias HL7v2.Segment.ZXX
alias HL7v2.Type.{HD, MSG, PT, VID, TS, DTM}
defstruct [:msh, segments: []]
@type t :: %__MODULE__{
msh: MSH.t(),
segments: [struct()]
}
@doc """
Creates a new message with MSH auto-populated.
## Options
* `:sending_application` -- string or `%HD{}` (default `nil`)
* `:sending_facility` -- string or `%HD{}` (default `nil`)
* `:receiving_application` -- string or `%HD{}` (default `nil`)
* `:receiving_facility` -- string or `%HD{}` (default `nil`)
* `:date_time` -- `%TS{}` or `%DTM{}` (default: current UTC time)
* `:message_control_id` -- string (default: auto-generated)
* `:processing_id` -- string (default: `"P"`)
* `:version_id` -- string (default: `"2.5.1"`)
"""
@spec new(binary(), binary(), keyword()) :: t()
def new(message_code, trigger_event, opts \\ [])
when is_binary(message_code) and is_binary(trigger_event) do
msh = %MSH{
field_separator: "|",
encoding_characters: "^~\\&",
sending_application: wrap_hd(opts[:sending_application]),
sending_facility: wrap_hd(opts[:sending_facility]),
receiving_application: wrap_hd(opts[:receiving_application]),
receiving_facility: wrap_hd(opts[:receiving_facility]),
date_time_of_message: build_timestamp(opts[:date_time]),
message_type: %MSG{
message_code: message_code,
trigger_event: trigger_event,
message_structure: canonical_structure(message_code, trigger_event)
},
message_control_id: opts[:message_control_id] || generate_control_id(),
processing_id: %PT{processing_id: opts[:processing_id] || "P"},
version_id: %VID{version_id: opts[:version_id] || "2.5.1"}
}
%__MODULE__{msh: msh, segments: []}
end
@doc """
Adds a segment to the message.
Segments are appended in order. MSH is managed separately and cannot be added
via this function.
"""
@spec add_segment(t(), struct()) :: t()
def add_segment(%__MODULE__{}, %HL7v2.Segment.MSH{}) do
raise ArgumentError, "MSH is managed by Message.new/3 and cannot be added via add_segment/2"
end
def add_segment(%__MODULE__{} = msg, segment) when is_struct(segment) do
unless function_exported?(segment.__struct__, :segment_id, 0) do
raise ArgumentError,
"expected an HL7v2 segment struct, got #{inspect(segment.__struct__)}"
end
%{msg | segments: msg.segments ++ [segment]}
end
@doc """
Returns all segments of a given type.
## Examples
HL7v2.Message.segments(msg, HL7v2.Segment.OBX)
#=> [%HL7v2.Segment.OBX{...}, %HL7v2.Segment.OBX{...}]
"""
@spec segments(t(), module()) :: [struct()]
def segments(%__MODULE__{} = msg, module) do
Enum.filter(msg.segments, &is_struct(&1, module))
end
@doc """
Returns the first segment of a given type, or `nil`.
## Examples
HL7v2.Message.segment(msg, HL7v2.Segment.PID)
#=> %HL7v2.Segment.PID{...}
"""
@spec segment(t(), module()) :: struct() | nil
def segment(%__MODULE__{} = msg, module) do
Enum.find(msg.segments, &is_struct(&1, module))
end
@doc """
Encodes the message to HL7v2 wire format binary.
Converts to a `RawMessage` and delegates to `HL7v2.Encoder`.
"""
@spec encode(t()) :: binary()
def encode(%__MODULE__{} = msg) do
msg
|> to_raw()
|> HL7v2.Encoder.encode()
end
@doc """
Converts to a `RawMessage` for encoding.
"""
@spec to_raw(t()) :: HL7v2.RawMessage.t()
def to_raw(%__MODULE__{msh: msh, segments: segments}) do
sep = separator_from_msh(msh)
raw_segments = [
{"MSH", MSH.encode(msh)}
| Enum.map(segments, fn seg ->
{segment_id_for(seg), seg.__struct__.encode(seg)}
end)
]
type = extract_type(msh)
%HL7v2.RawMessage{
separators: sep,
type: type,
segments: raw_segments
}
end
# --- Private ---
defp separator_from_msh(%MSH{field_separator: fs, encoding_characters: enc})
when is_binary(fs) and is_binary(enc) do
case HL7v2.Separator.from_msh("MSH" <> fs <> enc <> fs) do
{:ok, sep} -> sep
_ -> HL7v2.Separator.default()
end
end
defp separator_from_msh(_), do: HL7v2.Separator.default()
defp wrap_hd(nil), do: nil
defp wrap_hd(%HD{} = hd), do: hd
defp wrap_hd(value) when is_binary(value), do: %HD{namespace_id: value}
defp build_timestamp(nil) do
now = DateTime.utc_now()
%TS{
time: %DTM{
year: now.year,
month: now.month,
day: now.day,
hour: now.hour,
minute: now.minute,
second: now.second
}
}
end
defp build_timestamp(%TS{} = ts), do: ts
defp build_timestamp(%DTM{} = dtm), do: %TS{time: dtm}
defp generate_control_id do
timestamp = :os.system_time(:microsecond) |> Integer.to_string()
random = :crypto.strong_rand_bytes(4) |> Base.encode16(case: :lower)
"#{timestamp}_#{random}"
end
defp canonical_structure(code, event) do
MessageDefinition.canonical_structure(code, event)
end
defp extract_type(%MSH{message_type: %MSG{} = msg}) do
code = msg.message_code || ""
event = msg.trigger_event || ""
structure = msg.message_structure
if structure do
{code, event, structure}
else
{code, event}
end
end
defp extract_type(_), do: {"", ""}
defp segment_id_for(%ZXX{} = zxx), do: ZXX.segment_name(zxx)
defp segment_id_for(seg), do: seg.__struct__.segment_id()
end
defimpl String.Chars, for: HL7v2.Message do
def to_string(msg), do: HL7v2.Message.encode(msg)
end