Skip to content

Commit dfac6bd

Browse files
authored
Return subfields in unpack error (#313)
* Return subfield IDs upon unpack error * Tidy test * Comments * Make linter happy * Remove FieldIDs
1 parent edc9020 commit dfac6bd

File tree

6 files changed

+314
-54
lines changed

6 files changed

+314
-54
lines changed

errors.go

Lines changed: 0 additions & 29 deletions
This file was deleted.

errors/errors.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package errors
2+
3+
import (
4+
"errors"
5+
)
6+
7+
// UnpackError returns an error with the possibility to access the RawMessage when
8+
// the connection failed to unpack the message
9+
type UnpackError struct {
10+
Err error
11+
FieldID string
12+
RawMessage []byte
13+
}
14+
15+
func (e *UnpackError) Error() string {
16+
return e.Err.Error()
17+
}
18+
19+
func (e *UnpackError) Unwrap() error {
20+
return e.Err
21+
}
22+
23+
// FieldIDs returns the list of field and subfield IDs (if any) that errored from outermost inwards
24+
func (e *UnpackError) FieldIDs() []string {
25+
fieldIDs := []string{e.FieldID}
26+
err := e.Err
27+
var unpackError *UnpackError
28+
for {
29+
if errors.As(err, &unpackError) {
30+
fieldIDs = append(fieldIDs, unpackError.FieldID)
31+
err = unpackError.Unwrap()
32+
} else {
33+
break
34+
}
35+
}
36+
37+
return fieldIDs
38+
}
39+
40+
type PackError struct {
41+
Err error
42+
}
43+
44+
func (e *PackError) Error() string {
45+
return e.Err.Error()
46+
}
47+
48+
func (e *PackError) Unwrap() error {
49+
return e.Err
50+
}

field/composite.go

Lines changed: 35 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"sync"
1111

1212
"github.com/moov-io/iso8583/encoding"
13+
iso8583errors "github.com/moov-io/iso8583/errors"
1314
"github.com/moov-io/iso8583/prefix"
1415
"github.com/moov-io/iso8583/sort"
1516
"github.com/moov-io/iso8583/utils"
@@ -314,7 +315,7 @@ func (f *Composite) Unpack(data []byte) (int, error) {
314315
// data is stripped of the prefix before it is provided to unpack().
315316
// Therefore, it is unaware of when to stop parsing unless we bound the
316317
// length of the slice by the data length.
317-
read, err := f.unpack(data[offset:offset+dataLen], isVariableLength)
318+
read, err := f.wrapErrorUnpack(data[offset:offset+dataLen], isVariableLength)
318319
if err != nil {
319320
return 0, err
320321
}
@@ -333,7 +334,7 @@ func (f *Composite) SetBytes(data []byte) error {
333334
f.mu.Lock()
334335
defer f.mu.Unlock()
335336

336-
_, err := f.unpack(data, false)
337+
_, err := f.wrapErrorUnpack(data, false)
337338
return err
338339
}
339340

@@ -517,7 +518,22 @@ func (f *Composite) packByTag() ([]byte, error) {
517518
return packed, nil
518519
}
519520

520-
func (f *Composite) unpack(data []byte, isVariableLength bool) (int, error) {
521+
// wrapErrorUnpack calls the core unpacking logic and wraps any
522+
// errors in a *UnpackError. It assumes that the mutex is already
523+
// locked by the caller.
524+
func (f *Composite) wrapErrorUnpack(src []byte, isVariableLength bool) (int, error) {
525+
offset, tagID, err := f.unpack(src, isVariableLength)
526+
if err != nil {
527+
return offset, &iso8583errors.UnpackError{
528+
Err: err,
529+
FieldID: tagID,
530+
RawMessage: src,
531+
}
532+
}
533+
return offset, nil
534+
}
535+
536+
func (f *Composite) unpack(data []byte, isVariableLength bool) (int, string, error) {
521537
if f.bitmap() != nil {
522538
return f.unpackSubfieldsByBitmap(data)
523539
}
@@ -527,7 +543,7 @@ func (f *Composite) unpack(data []byte, isVariableLength bool) (int, error) {
527543
return f.unpackSubfields(data, isVariableLength)
528544
}
529545

530-
func (f *Composite) unpackSubfields(data []byte, isVariableLength bool) (int, error) {
546+
func (f *Composite) unpackSubfields(data []byte, isVariableLength bool) (int, string, error) {
531547
offset := 0
532548
for _, tag := range f.orderedSpecFieldTags {
533549
field, ok := f.subfields[tag]
@@ -537,7 +553,7 @@ func (f *Composite) unpackSubfields(data []byte, isVariableLength bool) (int, er
537553

538554
read, err := field.Unpack(data[offset:])
539555
if err != nil {
540-
return 0, fmt.Errorf("failed to unpack subfield %v: %w", tag, err)
556+
return 0, tag, fmt.Errorf("failed to unpack subfield %v: %w", tag, err)
541557
}
542558

543559
f.setSubfields[tag] = struct{}{}
@@ -549,10 +565,10 @@ func (f *Composite) unpackSubfields(data []byte, isVariableLength bool) (int, er
549565
}
550566
}
551567

552-
return offset, nil
568+
return offset, "", nil
553569
}
554570

555-
func (f *Composite) unpackSubfieldsByBitmap(data []byte) (int, error) {
571+
func (f *Composite) unpackSubfieldsByBitmap(data []byte) (int, string, error) {
556572
var off int
557573

558574
// Reset fields that were set.
@@ -562,7 +578,7 @@ func (f *Composite) unpackSubfieldsByBitmap(data []byte) (int, error) {
562578

563579
read, err := f.bitmap().Unpack(data[off:])
564580
if err != nil {
565-
return 0, fmt.Errorf("failed to unpack bitmap: %w", err)
581+
return 0, "", fmt.Errorf("failed to unpack bitmap: %w", err)
566582
}
567583

568584
off += read
@@ -573,12 +589,12 @@ func (f *Composite) unpackSubfieldsByBitmap(data []byte) (int, error) {
573589

574590
fl, ok := f.subfields[iStr]
575591
if !ok {
576-
return 0, fmt.Errorf("failed to unpack subfield %s: no specification found", iStr)
592+
return 0, iStr, fmt.Errorf("failed to unpack subfield %s: no specification found", iStr)
577593
}
578594

579595
read, err = fl.Unpack(data[off:])
580596
if err != nil {
581-
return 0, fmt.Errorf("failed to unpack subfield %s (%s): %w", iStr, fl.Spec().Description, err)
597+
return 0, iStr, fmt.Errorf("failed to unpack subfield %s (%s): %w", iStr, fl.Spec().Description, err)
582598
}
583599

584600
f.setSubfields[iStr] = struct{}{}
@@ -587,7 +603,7 @@ func (f *Composite) unpackSubfieldsByBitmap(data []byte) (int, error) {
587603
}
588604
}
589605

590-
return off, nil
606+
return off, "", nil
591607
}
592608

593609
const (
@@ -598,12 +614,12 @@ const (
598614
maxLenOfUnknownTag = math.MaxInt
599615
)
600616

601-
func (f *Composite) unpackSubfieldsByTag(data []byte) (int, error) {
617+
func (f *Composite) unpackSubfieldsByTag(data []byte) (int, string, error) {
602618
offset := 0
603619
for offset < len(data) {
604620
tagBytes, read, err := f.spec.Tag.Enc.Decode(data[offset:], f.spec.Tag.Length)
605621
if err != nil {
606-
return 0, fmt.Errorf("failed to unpack subfield Tag: %w", err)
622+
return 0, "", fmt.Errorf("failed to unpack subfield Tag: %w", err)
607623
}
608624
offset += read
609625

@@ -625,15 +641,15 @@ func (f *Composite) unpackSubfieldsByTag(data []byte) (int, error) {
625641
maxLen = maxLenOfUnknownTag
626642
}
627643

628-
fieldLength, readed, err := pref.DecodeLength(maxLen, data[offset:])
644+
fieldLength, read, err := pref.DecodeLength(maxLen, data[offset:])
629645
if err != nil {
630-
return 0, err
646+
return 0, "", err
631647
}
632-
offset += fieldLength + readed
648+
offset += fieldLength + read
633649
continue
634650
}
635651

636-
return 0, fmt.Errorf("failed to unpack subfield %v: field not defined in Spec", tag)
652+
return 0, tag, fmt.Errorf("failed to unpack subfield %v: field not defined in Spec", tag)
637653
}
638654

639655
field, ok := f.subfields[tag]
@@ -643,14 +659,14 @@ func (f *Composite) unpackSubfieldsByTag(data []byte) (int, error) {
643659

644660
read, err = field.Unpack(data[offset:])
645661
if err != nil {
646-
return 0, fmt.Errorf("failed to unpack subfield %v: %w", tag, err)
662+
return 0, tag, fmt.Errorf("failed to unpack subfield %v: %w", tag, err)
647663
}
648664

649665
f.setSubfields[tag] = struct{}{}
650666

651667
offset += read
652668
}
653-
return offset, nil
669+
return offset, "", nil
654670
}
655671

656672
func (f *Composite) skipUnknownTLVTags() bool {

field/composite_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@ import (
88
"testing"
99

1010
"github.com/moov-io/iso8583/encoding"
11+
iso8583errors "github.com/moov-io/iso8583/errors"
1112
"github.com/moov-io/iso8583/padding"
1213
"github.com/moov-io/iso8583/prefix"
1314
"github.com/moov-io/iso8583/sort"
15+
"github.com/stretchr/testify/assert"
1416
"github.com/stretchr/testify/require"
1517
)
1618

@@ -799,6 +801,10 @@ func TestCompositePacking(t *testing.T) {
799801
read, err := composite.Unpack([]byte("ABCDEF"))
800802
require.Equal(t, 0, read)
801803
require.Error(t, err)
804+
var unpackError *iso8583errors.UnpackError
805+
require.ErrorAs(t, err, &unpackError)
806+
assert.Equal(t, "3", unpackError.FieldID)
807+
assert.Equal(t, []string{"3"}, unpackError.FieldIDs())
802808
require.EqualError(t, err, "failed to unpack subfield 3: failed to set bytes: failed to convert into number")
803809
require.ErrorIs(t, err, strconv.ErrSyntax)
804810
})

message.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"strconv"
1010
"sync"
1111

12+
iso8583errors "github.com/moov-io/iso8583/errors"
1213
"github.com/moov-io/iso8583/field"
1314
"github.com/moov-io/iso8583/utils"
1415
)
@@ -175,7 +176,7 @@ func (m *Message) Pack() ([]byte, error) {
175176
func (m *Message) wrapErrorPack() ([]byte, error) {
176177
data, err := m.pack()
177178
if err != nil {
178-
return nil, &PackError{Err: err}
179+
return nil, &iso8583errors.PackError{Err: err}
179180
}
180181

181182
return data, nil
@@ -238,7 +239,7 @@ func (m *Message) Unpack(src []byte) error {
238239
// locked by the caller.
239240
func (m *Message) wrapErrorUnpack(src []byte) error {
240241
if fieldID, err := m.unpack(src); err != nil {
241-
return &UnpackError{
242+
return &iso8583errors.UnpackError{
242243
Err: err,
243244
FieldID: fieldID,
244245
RawMessage: src,

0 commit comments

Comments
 (0)