Skip to content

Commit f690598

Browse files
committed
make comment more readable, refactor API
1 parent e83ba5f commit f690598

25 files changed

+8534
-2593
lines changed

Readme.md

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# HL7 v2 - Marshal and Unmarshal

coder.go

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ type Registry interface {
1414
ControlSegment() RegistryLookup
1515
Segment() RegistryLookup
1616
Trigger() RegistryLookup
17+
DataType() RegistryLookup
1718
}
1819

1920
const tagName = "hl7"

coder_test.go

+16-15
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ func TestEncode(t *testing.T) {
2626
},
2727
}
2828

29-
bb, err := Marshal(msg)
29+
e := NewEncoder(nil)
30+
bb, err := e.Encode(msg)
3031
if err != nil {
3132
t.Fatal(err)
3233
}
@@ -42,9 +43,8 @@ NTE|2||more comments here|||
4243
OBR|1|ABC||||||||||||||1234^Acron^Smith~5678^Beta^Zeta|||||||||||||||||||||||||||||||||||||||||||
4344
OBR|2|XYZ||||||||||||||903^Blacky|||||||||||||||||||||||||||||||||||||||||||`)
4445

45-
v, err := Unmarshal(raw, UnmarshalOption{
46-
Registry: v25.Registry,
47-
})
46+
d := NewDecoder(v25.Registry, nil)
47+
v, err := d.DecodeList(raw)
4848
if err != nil {
4949
t.Fatal(err)
5050
}
@@ -60,17 +60,17 @@ func TestGroup(t *testing.T) {
6060
MSA|AA|161||||
6161
`)
6262

63-
v, err := Unmarshal(raw, UnmarshalOption{
64-
Registry: v25.Registry,
65-
})
63+
d := NewDecoder(v25.Registry, nil)
64+
v, err := d.DecodeList(raw)
6665
if err != nil {
6766
t.Fatal(err)
6867
}
69-
root, err := Group(v, v25.Registry)
68+
root, err := group(v, v25.Registry)
7069
if err != nil {
7170
t.Fatal(err)
7271
}
73-
rt, err := Marshal(root)
72+
e := NewEncoder(nil)
73+
rt, err := e.Encode(root)
7474
if err != nil {
7575
t.Fatal(err)
7676
}
@@ -104,6 +104,10 @@ func TestRoundTrip(t *testing.T) {
104104
Separator: " ",
105105
}
106106
_ = c
107+
108+
d := NewDecoder(v251.Registry, nil)
109+
e := NewEncoder(nil)
110+
107111
for _, f := range dirList {
108112
if f.IsDir() {
109113
continue
@@ -114,26 +118,23 @@ func TestRoundTrip(t *testing.T) {
114118
if err != nil {
115119
t.Fatal(err)
116120
}
117-
uo := UnmarshalOption{
118-
Registry: v251.Registry,
119-
}
120-
v, err := Unmarshal(bb, uo)
121+
v, err := d.DecodeList(bb)
121122
if err != nil {
122123
t.Fatal("unmarshal", err)
123124
}
124125
if *dumpSegmentList {
125126
c.Dump(v)
126127
}
127128

128-
root, err := Group(v, v251.Registry)
129+
root, err := d.DecodeGroup(v)
129130
if err != nil {
130131
t.Fatal("group", err)
131132
}
132133
if *dumpSegmentGroup {
133134
c.Dump(root)
134135
}
135136

136-
rt, err := Marshal(root)
137+
rt, err := e.Encode(root)
137138
if err != nil {
138139
t.Fatal("marshal", err)
139140
}

decode.go

+76-38
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import (
99
"unicode"
1010
)
1111

12-
type decoder struct {
12+
type lineDecoder struct {
1313
sep byte // usually a |
1414
repeat byte // usually a ~
1515
dividers [3]byte // usually |, ^, &
@@ -18,26 +18,53 @@ type decoder struct {
1818
readSep bool
1919

2020
unescaper *strings.Replacer
21+
22+
opt DecodeOption
2123
}
2224

23-
func (d *decoder) setupUnescaper() {
24-
d.unescaper = strings.NewReplacer(
25-
string([]byte{d.escape, 'F', d.escape}), string(d.sep),
26-
string([]byte{d.escape, 'S', d.escape}), string(d.chars[0]),
27-
string([]byte{d.escape, 'R', d.escape}), string(d.chars[1]),
28-
string([]byte{d.escape, 'E', d.escape}), string(d.chars[2]),
29-
string([]byte{d.escape, 'T', d.escape}), string(d.chars[3]),
30-
)
25+
// Decode bytes into HL7 structures.
26+
type Decoder struct {
27+
registry Registry
28+
opt DecodeOption
3129
}
3230

33-
var timeType reflect.Type = reflect.TypeOf(time.Time{})
31+
// Decode options for the HL7 decoder.
32+
type DecodeOption struct {
33+
ErrorZSegment bool // Error on an unknown Zxx segment when true.
34+
}
35+
36+
// Create a new Decoder. A registry must be provided. Option is optional.
37+
func NewDecoder(registry Registry, opt *DecodeOption) *Decoder {
38+
d := &Decoder{
39+
registry: registry,
40+
}
41+
if opt != nil {
42+
d.opt = *opt
43+
}
3444

35-
type UnmarshalOption struct {
36-
Registry Registry
37-
ErrorZSegment bool // Error on an unknown Zxx segment.
45+
return d
3846
}
3947

40-
func Unmarshal(data []byte, opt UnmarshalOption) ([]any, error) {
48+
// Decode takes an hl7 message and returns a final trigger with all segments grouped.
49+
func (d *Decoder) Decode(data []byte) (any, error) {
50+
list, err := d.DecodeList(data)
51+
if err != nil {
52+
return nil, fmt.Errorf("segment list: %w", err)
53+
}
54+
g, err := d.DecodeGroup(list)
55+
if err != nil {
56+
return nil, fmt.Errorf("trigger group: %w", err)
57+
}
58+
return g, nil
59+
}
60+
61+
// Group a list of elements into trigger groupings.
62+
func (d *Decoder) DecodeGroup(list []any) (any, error) {
63+
return group(list, d.registry)
64+
}
65+
66+
// Decode returns a list of segments without any grouping applied.
67+
func (d *Decoder) DecodeList(data []byte) ([]any, error) {
4168
// Explicitly accept both CR and LF as new lines. Some systems do use \n, despite the spec.
4269
lines := bytes.FieldsFunc(data, func(r rune) bool {
4370
switch r {
@@ -57,15 +84,15 @@ func Unmarshal(data []byte, opt UnmarshalOption) ([]any, error) {
5784

5885
ret := []any{}
5986

60-
d := &decoder{}
61-
segmentRegistry := opt.Registry.Segment()
87+
ld := &lineDecoder{}
88+
segmentRegistry := d.registry.Segment()
6289
for index, line := range lines {
6390
lineNumber := index + 1
6491
if len(line) == 0 {
6592
continue
6693
}
6794

68-
segTypeName, n := d.getID(line)
95+
segTypeName, n := ld.getID(line)
6996
remain := line[n:]
7097
if len(segTypeName) == 0 {
7198
return nil, fmt.Errorf("line %d: missing segment type", lineNumber)
@@ -74,7 +101,7 @@ func Unmarshal(data []byte, opt UnmarshalOption) ([]any, error) {
74101
seg, ok := segmentRegistry[segTypeName]
75102
if !ok {
76103
isZ := len(segTypeName) > 0 && segTypeName[0] == 'Z'
77-
if isZ && !opt.ErrorZSegment {
104+
if isZ && !d.opt.ErrorZSegment {
78105
continue
79106
}
80107
return nil, fmt.Errorf("line %d: unknown segment type %q", lineNumber, segTypeName)
@@ -139,33 +166,33 @@ func Unmarshal(data []byte, opt UnmarshalOption) ([]any, error) {
139166
if len(remain) < 5 {
140167
return nil, fmt.Errorf("missing format delims")
141168
}
142-
d.sep = remain[0]
143-
copy(d.chars[:], remain[1:5])
169+
ld.sep = remain[0]
170+
copy(ld.chars[:], remain[1:5])
144171

145-
d.dividers = [3]byte{d.sep, d.chars[0], d.chars[3]}
146-
d.repeat = d.chars[1]
147-
d.escape = d.chars[2]
148-
d.setupUnescaper()
149-
d.readSep = true
172+
ld.dividers = [3]byte{ld.sep, ld.chars[0], ld.chars[3]}
173+
ld.repeat = ld.chars[1]
174+
ld.escape = ld.chars[2]
175+
ld.setupUnescaper()
176+
ld.readSep = true
150177

151178
remain = remain[5:]
152179
offset = 2
153180
}
154181

155-
if d.sep == 0 {
182+
if ld.sep == 0 {
156183
return nil, fmt.Errorf("missing sep prior to field")
157184
}
158185

159-
parts := bytes.Split(remain, []byte{d.sep})
186+
parts := bytes.Split(remain, []byte{ld.sep})
160187

161188
ff := make([]field, SegmentSize)
162189
for _, f := range fieldList {
163190
if f.tag.FieldSep {
164-
f.field.SetString(string(d.sep))
191+
f.field.SetString(string(ld.sep))
165192
continue
166193
}
167194
if f.tag.FieldChars {
168-
f.field.SetString(string(d.chars[:]))
195+
f.field.SetString(string(ld.chars[:]))
169196
continue
170197
}
171198
index := int(f.tag.Order) - offset
@@ -189,19 +216,30 @@ func Unmarshal(data []byte, opt UnmarshalOption) ([]any, error) {
189216
if f.tag.Child {
190217
continue
191218
}
192-
err := d.decodeSegmentList(p, f.tag, f.field)
219+
err := ld.decodeSegmentList(p, f.tag, f.field)
193220
if err != nil {
194221
return ret, fmt.Errorf("line %d, %s.%s: %w", lineNumber, SegmentName, f.name, err)
195222
}
196223
}
197224

198225
ret = append(ret, rv.Interface())
199226
}
200-
201227
return ret, nil
202228
}
203229

204-
func (d *decoder) decodeSegmentList(data []byte, t tag, rv reflect.Value) error {
230+
func (d *lineDecoder) setupUnescaper() {
231+
d.unescaper = strings.NewReplacer(
232+
string([]byte{d.escape, 'F', d.escape}), string(d.sep),
233+
string([]byte{d.escape, 'S', d.escape}), string(d.chars[0]),
234+
string([]byte{d.escape, 'R', d.escape}), string(d.chars[1]),
235+
string([]byte{d.escape, 'E', d.escape}), string(d.chars[2]),
236+
string([]byte{d.escape, 'T', d.escape}), string(d.chars[3]),
237+
)
238+
}
239+
240+
var timeType reflect.Type = reflect.TypeOf(time.Time{})
241+
242+
func (d *lineDecoder) decodeSegmentList(data []byte, t tag, rv reflect.Value) error {
205243
if len(data) == 0 {
206244
return nil
207245
}
@@ -217,7 +255,7 @@ func (d *decoder) decodeSegmentList(data []byte, t tag, rv reflect.Value) error
217255
}
218256
return nil
219257
}
220-
func (d *decoder) decodeSegment(data []byte, t tag, rv reflect.Value, level int, mustBeSlice bool) error {
258+
func (d *lineDecoder) decodeSegment(data []byte, t tag, rv reflect.Value, level int, mustBeSlice bool) error {
221259
type field struct {
222260
tag tag
223261
field reflect.Value
@@ -331,7 +369,7 @@ func (d *decoder) decodeSegment(data []byte, t tag, rv reflect.Value, level int,
331369
return nil
332370
case timeType:
333371
v := d.decodeByte(data, t)
334-
t, err := parseDateTime(v)
372+
t, err := d.parseDateTime(v)
335373
if err != nil {
336374
return err
337375
}
@@ -351,20 +389,20 @@ func (d *decoder) decodeSegment(data []byte, t tag, rv reflect.Value, level int,
351389
}
352390
}
353391

354-
func (d *decoder) decodeByte(v []byte, t tag) string {
392+
func (d *lineDecoder) decodeByte(v []byte, t tag) string {
355393
if t.NoEscape {
356394
return string(v)
357395
}
358396
return d.unescaper.Replace(string(v))
359397
}
360-
func (d *decoder) decodeString(v string, t tag) string {
398+
func (d *lineDecoder) decodeString(v string, t tag) string {
361399
if t.NoEscape {
362400
return v
363401
}
364402
return d.unescaper.Replace(v)
365403
}
366404

367-
func (d *decoder) getID(data []byte) (string, int) {
405+
func (d *lineDecoder) getID(data []byte) (string, int) {
368406
if d.readSep {
369407
v, _, _ := bytes.Cut(data, []byte{d.sep})
370408
return string(v), len(v)
@@ -378,7 +416,7 @@ func (d *decoder) getID(data []byte) (string, int) {
378416
return string(data), len(data)
379417
}
380418

381-
func parseDateTime(dt string) (time.Time, error) {
419+
func (d *lineDecoder) parseDateTime(dt string) (time.Time, error) {
382420
var zoneIndex int
383421
for i, r := range dt {
384422
switch {

decodeGroup.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,8 @@ func (w *walker) process(list []any) (any, error) {
7474
return rootI, nil
7575
}
7676

77-
// Group a list of segments into hierarchical groups with a single root element.
78-
func Group(list []any, registry Registry) (any, error) {
77+
// group a list of segments into hierarchical groups with a single root element.
78+
func group(list []any, registry Registry) (any, error) {
7979
w, err := newWalker(list, registry)
8080
if err != nil {
8181
return nil, err

0 commit comments

Comments
 (0)