Packages
hl7v2
1.4.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.
Builds well-formed 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.
## 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_number: "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 \\ []) 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__{} = msg, segment) do
%{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