Skip to content

Commit 8fcd602

Browse files
committed
hl7: add option to ignore check for separator field runes in text fields
1 parent 15d6ae7 commit 8fcd602

13 files changed

+168
-66
lines changed

coder_test.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ OBR|2|XYZ||||||||||||||903^Blacky|||||||||||||||||||||||||||||||||||||||||||`)
7979
const (
8080
wantFirst = "John"
8181
wantLast = "Smith"
82-
wantError = `line 2, PID.DateTimeOfBirth: time.Time.7: field "19561192000000" : parsing time "19561192000000": day out of range`
82+
wantError = `line 2, PID.DateTimeOfBirth(time.Time)[7]: parsing time "19561192000000": day out of range`
8383
)
8484
var gotError, gotFirst, gotLast string
8585
for _, item := range v {
@@ -416,7 +416,7 @@ func TestParseDate(t *testing.T) {
416416
Input: "20011003",
417417
Want: "2001-10-03T00:00:00Z",
418418
},
419-
// These are not properly formatted, but accept them anyway, the meaning is sufficently clear.
419+
// These are not properly formatted, but accept them anyway, the meaning is sufficiently clear.
420420
{
421421
Name: "bad-accept1",
422422
Input: "2019-07-02 12:23:24+0300",
@@ -427,7 +427,7 @@ func TestParseDate(t *testing.T) {
427427
Input: "2019-07-02 12:23:24-0300",
428428
Want: "2019-07-02T12:23:24-03:00",
429429
},
430-
// The meaning is not sufficently clear, reject.
430+
// The meaning is not sufficiently clear, reject.
431431
{
432432
Name: "bad-length",
433433
Input: "2019-1",

decode.go

+116-21
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,20 @@ import (
44
"bytes"
55
"fmt"
66
"reflect"
7+
"strconv"
78
"strings"
89
"time"
910
"unicode"
1011
)
1112

1213
type lineDecoder struct {
13-
sep byte // usually a |
14-
repeat byte // usually a ~
15-
dividers [3]byte // usually |, ^, &
16-
chars [4]byte // usually ^!\&
17-
escape byte // usually a \
18-
readSep bool
14+
sep byte // usually a |
15+
repeat byte // usually a ~
16+
dividers [3]byte // usually |, ^, &
17+
chars [4]byte // usually ^!\&
18+
escape byte // usually a \
19+
readSep bool
20+
ignoreSep bool
1921

2022
unescaper *strings.Replacer
2123
}
@@ -28,8 +30,9 @@ type Decoder struct {
2830

2931
// Decode options for the HL7 decoder.
3032
type DecodeOption struct {
31-
ErrorZSegment bool // Error on an unknown Zxx segment when true.
32-
HeaderOnly bool // Only decode first segment, usually the header.
33+
ErrorZSegment bool // Error on an unknown Zxx segment when true.
34+
HeaderOnly bool // Only decode first segment, usually the header.
35+
IgnoreFieldSep bool // Ignore field separator values in text fields.
3336
}
3437

3538
// Create a new Decoder. A registry must be provided. Option is optional.
@@ -73,7 +76,7 @@ type variesFunc func() (reflect.Value, error)
7376

7477
var variesType = reflect.TypeFor[Varies]()
7578

76-
// SgmentError may be returned as part of the DecodeList result.
79+
// SegmentError may be returned as part of the DecodeList result.
7780
// This allows a single segment to be decoded poorly with error, while
7881
// still decoding the rest of the message.
7982
// This will not be returned as an error.
@@ -82,6 +85,21 @@ type SegmentError struct {
8285
Segment any
8386
}
8487

88+
func (e SegmentError) Error() string {
89+
sb := &strings.Builder{}
90+
fmt.Fprintf(sb, "errors in segment %[1]T=%[1]v:", e.Segment)
91+
for _, err := range e.ErrorList {
92+
sb.WriteRune('\n')
93+
sb.WriteRune('\t')
94+
sb.WriteString(err.Error())
95+
}
96+
return sb.String()
97+
}
98+
99+
func (e SegmentError) Unwrap() []error {
100+
return e.ErrorList
101+
}
102+
85103
// Decode returns a list of segments without any grouping applied.
86104
func (d *Decoder) DecodeList(data []byte) ([]any, error) {
87105
// Explicitly accept both CR and LF as new lines. Some systems do use \n, despite the spec.
@@ -103,7 +121,9 @@ func (d *Decoder) DecodeList(data []byte) ([]any, error) {
103121

104122
ret := []any{}
105123

106-
ld := &lineDecoder{}
124+
ld := &lineDecoder{
125+
ignoreSep: d.opt.IgnoreFieldSep,
126+
}
107127
for index, line := range lines {
108128
lineNumber := index + 1
109129
if len(line) == 0 {
@@ -243,7 +263,18 @@ func (d *Decoder) DecodeList(data []byte) ([]any, error) {
243263
}
244264
err := ld.decodeSegmentList(p, f.tag, f.field, vfc)
245265
if err != nil {
246-
err = fmt.Errorf("line %d, %s.%s: %w", lineNumber, SegmentName, f.name, err)
266+
if v, ok := err.(*DecodeSegmentError); ok && v.Line == 0 && len(v.SegmentName) == 0 && len(v.FieldName) == 0 {
267+
v.Line = lineNumber
268+
v.SegmentName = SegmentName
269+
v.FieldName = f.name
270+
} else {
271+
err = &DecodeSegmentError{
272+
Line: lineNumber,
273+
SegmentName: SegmentName,
274+
FieldName: f.name,
275+
Inner: err,
276+
}
277+
}
247278
segmentErrorList = append(segmentErrorList, err)
248279
}
249280
}
@@ -285,11 +316,68 @@ func (d *lineDecoder) decodeSegmentList(data []byte, t tag, rv reflect.Value, vf
285316
}
286317
err := d.decodeSegment(p, t, rv, 1, len(parts) > 1, vfc)
287318
if err != nil {
288-
return fmt.Errorf("%s.%d: %w", rv.Type().String(), t.Order, err)
319+
return &DecodeSegmentError{
320+
FieldType: rv.Type().String(),
321+
Ordinal: t.Order,
322+
Inner: err,
323+
}
289324
}
290325
}
291326
return nil
292327
}
328+
329+
type DecodeSegmentError struct {
330+
Line int
331+
SegmentName string
332+
FieldName string
333+
FieldType string
334+
Ordinal int32
335+
Inner error
336+
}
337+
338+
func (e *DecodeSegmentError) Error() string {
339+
sb := &strings.Builder{}
340+
if e.Line > 0 {
341+
sb.WriteString("line ")
342+
sb.WriteString(strconv.FormatInt(int64(e.Line), 10))
343+
sb.WriteString(", ")
344+
}
345+
if len(e.SegmentName) > 0 {
346+
sb.WriteString(e.SegmentName)
347+
sb.WriteString(".")
348+
}
349+
if len(e.FieldName) > 0 {
350+
sb.WriteString(e.FieldName)
351+
}
352+
if len(e.FieldType) > 0 {
353+
sb.WriteRune('(')
354+
sb.WriteString(e.FieldType)
355+
sb.WriteRune(')')
356+
}
357+
if e.Ordinal > 0 {
358+
sb.WriteRune('[')
359+
sb.WriteString(strconv.FormatInt(int64(e.Ordinal), 10))
360+
sb.WriteRune(']')
361+
}
362+
if e.Inner != nil {
363+
sb.WriteString(": ")
364+
sb.WriteString(e.Inner.Error())
365+
}
366+
return sb.String()
367+
}
368+
func (e *DecodeSegmentError) Unwrap() error {
369+
return e.Inner
370+
}
371+
372+
type DecodeDataError struct {
373+
Tag string
374+
Value string
375+
}
376+
377+
func (e *DecodeDataError) Error() string {
378+
return fmt.Sprintf("%s contains an escape character %s; data may be malformed, invalid type, or contain a bug", e.Tag, e.Value)
379+
}
380+
293381
func (d *lineDecoder) decodeSegment(data []byte, t tag, rv reflect.Value, level int, mustBeSlice bool, vfc variesFunc) error {
294382
type field struct {
295383
tag tag
@@ -407,7 +495,13 @@ func (d *lineDecoder) decodeSegment(data []byte, t tag, rv reflect.Value, level
407495
f := ff[i]
408496
err := d.decodeSegment(p, f.tag, f.field, level+1, false, vfc)
409497
if err != nil {
410-
return fmt.Errorf("%s-%s.%d: %w", SegmentName, f.field.Type().String(), f.tag.Order, err)
498+
return &DecodeSegmentError{
499+
SegmentName: SegmentName,
500+
FieldType: f.field.Type().String(),
501+
FieldName: f.tag.Name,
502+
Ordinal: f.tag.Order,
503+
Inner: err,
504+
}
411505
}
412506
}
413507
return nil
@@ -422,17 +516,21 @@ func (d *lineDecoder) decodeSegment(data []byte, t tag, rv reflect.Value, level
422516
}
423517
case reflect.String:
424518
c1, c2, c3 := d.dividers[0], d.dividers[1], d.dividers[2]
425-
for _, b := range data {
426-
switch b {
427-
case c1, c2, c3:
428-
return fmt.Errorf("%s contains an escape character %s; data may be malformed, invalid type, or contain a bug", t.Name, data)
519+
if !d.ignoreSep {
520+
for _, b := range data {
521+
switch b {
522+
case c1, c2, c3:
523+
return &DecodeDataError{
524+
Tag: t.Name,
525+
Value: string(data),
526+
}
527+
}
429528
}
430529
}
431530
rv.SetString(d.decodeByte(data, t))
432531
return nil
433532
}
434533
}
435-
436534
func (d *lineDecoder) decodeByte(v []byte, t tag) string {
437535
if t.NoEscape {
438536
return string(v)
@@ -549,8 +647,5 @@ loop:
549647
in = dt[:14]
550648
t, err = time.Parse("20060102150405", in)
551649
}
552-
if err != nil {
553-
err = fmt.Errorf("field %q : %w", dt, err)
554-
}
555650
return t, err
556651
}

h210/segment.go

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

h270/datatype.go

+3-3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

h270/registry.go

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

h270/segment.go

+7-7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

h271/datatype.go

+3-3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

h271/registry.go

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)