Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Return subfields in unpack error #313

Merged
merged 5 commits into from
Jul 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 0 additions & 29 deletions errors.go

This file was deleted.

50 changes: 50 additions & 0 deletions errors/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package errors
alovak marked this conversation as resolved.
Show resolved Hide resolved

import (
"errors"
)

// UnpackError returns an error with the possibility to access the RawMessage when
// the connection failed to unpack the message
type UnpackError struct {
Err error
FieldID string
RawMessage []byte
}

func (e *UnpackError) Error() string {
return e.Err.Error()
}

func (e *UnpackError) Unwrap() error {
return e.Err
}

// FieldIDs returns the list of field and subfield IDs (if any) that errored from outermost inwards
func (e *UnpackError) FieldIDs() []string {
fieldIDs := []string{e.FieldID}
err := e.Err
var unpackError *UnpackError
for {
if errors.As(err, &unpackError) {
fieldIDs = append(fieldIDs, unpackError.FieldID)
err = unpackError.Unwrap()
} else {
break
}
}

return fieldIDs
}

type PackError struct {
Err error
}

func (e *PackError) Error() string {
return e.Err.Error()
}

func (e *PackError) Unwrap() error {
return e.Err
}
54 changes: 35 additions & 19 deletions field/composite.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"sync"

"github.com/moov-io/iso8583/encoding"
iso8583errors "github.com/moov-io/iso8583/errors"
"github.com/moov-io/iso8583/prefix"
"github.com/moov-io/iso8583/sort"
"github.com/moov-io/iso8583/utils"
Expand Down Expand Up @@ -312,7 +313,7 @@ func (f *Composite) Unpack(data []byte) (int, error) {
// data is stripped of the prefix before it is provided to unpack().
// Therefore, it is unaware of when to stop parsing unless we bound the
// length of the slice by the data length.
read, err := f.unpack(data[offset:offset+dataLen], isVariableLength)
read, err := f.wrapErrorUnpack(data[offset:offset+dataLen], isVariableLength)
if err != nil {
return 0, err
}
Expand All @@ -331,7 +332,7 @@ func (f *Composite) SetBytes(data []byte) error {
f.mu.Lock()
defer f.mu.Unlock()

_, err := f.unpack(data, false)
_, err := f.wrapErrorUnpack(data, false)
return err
}

Expand Down Expand Up @@ -515,7 +516,22 @@ func (f *Composite) packByTag() ([]byte, error) {
return packed, nil
}

func (f *Composite) unpack(data []byte, isVariableLength bool) (int, error) {
// wrapErrorUnpack calls the core unpacking logic and wraps any
// errors in a *UnpackError. It assumes that the mutex is already
// locked by the caller.
func (f *Composite) wrapErrorUnpack(src []byte, isVariableLength bool) (int, error) {
offset, tagID, err := f.unpack(src, isVariableLength)
if err != nil {
return offset, &iso8583errors.UnpackError{
Err: err,
FieldID: tagID,
RawMessage: src,
}
}
return offset, nil
}

func (f *Composite) unpack(data []byte, isVariableLength bool) (int, string, error) {
if f.bitmap() != nil {
return f.unpackSubfieldsByBitmap(data)
}
Expand All @@ -525,7 +541,7 @@ func (f *Composite) unpack(data []byte, isVariableLength bool) (int, error) {
return f.unpackSubfields(data, isVariableLength)
}

func (f *Composite) unpackSubfields(data []byte, isVariableLength bool) (int, error) {
func (f *Composite) unpackSubfields(data []byte, isVariableLength bool) (int, string, error) {
offset := 0
for _, tag := range f.orderedSpecFieldTags {
field, ok := f.subfields[tag]
Expand All @@ -535,7 +551,7 @@ func (f *Composite) unpackSubfields(data []byte, isVariableLength bool) (int, er

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

f.setSubfields[tag] = struct{}{}
Expand All @@ -547,10 +563,10 @@ func (f *Composite) unpackSubfields(data []byte, isVariableLength bool) (int, er
}
}

return offset, nil
return offset, "", nil
}

func (f *Composite) unpackSubfieldsByBitmap(data []byte) (int, error) {
func (f *Composite) unpackSubfieldsByBitmap(data []byte) (int, string, error) {
var off int

// Reset fields that were set.
Expand All @@ -560,7 +576,7 @@ func (f *Composite) unpackSubfieldsByBitmap(data []byte) (int, error) {

read, err := f.bitmap().Unpack(data[off:])
if err != nil {
return 0, fmt.Errorf("failed to unpack bitmap: %w", err)
return 0, "", fmt.Errorf("failed to unpack bitmap: %w", err)
}

off += read
Expand All @@ -571,12 +587,12 @@ func (f *Composite) unpackSubfieldsByBitmap(data []byte) (int, error) {

fl, ok := f.subfields[iStr]
if !ok {
return 0, fmt.Errorf("failed to unpack subfield %s: no specification found", iStr)
return 0, iStr, fmt.Errorf("failed to unpack subfield %s: no specification found", iStr)
}

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

f.setSubfields[iStr] = struct{}{}
Expand All @@ -585,7 +601,7 @@ func (f *Composite) unpackSubfieldsByBitmap(data []byte) (int, error) {
}
}

return off, nil
return off, "", nil
}

const (
Expand All @@ -596,12 +612,12 @@ const (
maxLenOfUnknownTag = math.MaxInt
)

func (f *Composite) unpackSubfieldsByTag(data []byte) (int, error) {
func (f *Composite) unpackSubfieldsByTag(data []byte) (int, string, error) {
offset := 0
for offset < len(data) {
tagBytes, read, err := f.spec.Tag.Enc.Decode(data[offset:], f.spec.Tag.Length)
if err != nil {
return 0, fmt.Errorf("failed to unpack subfield Tag: %w", err)
return 0, "", fmt.Errorf("failed to unpack subfield Tag: %w", err)
}
offset += read

Expand All @@ -623,15 +639,15 @@ func (f *Composite) unpackSubfieldsByTag(data []byte) (int, error) {
maxLen = maxLenOfUnknownTag
}

fieldLength, readed, err := pref.DecodeLength(maxLen, data[offset:])
fieldLength, read, err := pref.DecodeLength(maxLen, data[offset:])
if err != nil {
return 0, err
return 0, "", err
}
offset += fieldLength + readed
offset += fieldLength + read
continue
}

return 0, fmt.Errorf("failed to unpack subfield %v: field not defined in Spec", tag)
return 0, tag, fmt.Errorf("failed to unpack subfield %v: field not defined in Spec", tag)
}

field, ok := f.subfields[tag]
Expand All @@ -641,14 +657,14 @@ func (f *Composite) unpackSubfieldsByTag(data []byte) (int, error) {

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

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

offset += read
}
return offset, nil
return offset, "", nil
}

func (f *Composite) skipUnknownTLVTags() bool {
Expand Down
6 changes: 6 additions & 0 deletions field/composite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ import (
"testing"

"github.com/moov-io/iso8583/encoding"
iso8583errors "github.com/moov-io/iso8583/errors"
"github.com/moov-io/iso8583/padding"
"github.com/moov-io/iso8583/prefix"
"github.com/moov-io/iso8583/sort"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

Expand Down Expand Up @@ -799,6 +801,10 @@ func TestCompositePacking(t *testing.T) {
read, err := composite.Unpack([]byte("ABCDEF"))
require.Equal(t, 0, read)
require.Error(t, err)
var unpackError *iso8583errors.UnpackError
require.ErrorAs(t, err, &unpackError)
assert.Equal(t, "3", unpackError.FieldID)
assert.Equal(t, []string{"3"}, unpackError.FieldIDs())
require.EqualError(t, err, "failed to unpack subfield 3: failed to set bytes: failed to convert into number")
require.ErrorIs(t, err, strconv.ErrSyntax)
})
Expand Down
5 changes: 3 additions & 2 deletions message.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"strconv"
"sync"

iso8583errors "github.com/moov-io/iso8583/errors"
"github.com/moov-io/iso8583/field"
"github.com/moov-io/iso8583/utils"
)
Expand Down Expand Up @@ -175,7 +176,7 @@ func (m *Message) Pack() ([]byte, error) {
func (m *Message) wrapErrorPack() ([]byte, error) {
data, err := m.pack()
if err != nil {
return nil, &PackError{Err: err}
return nil, &iso8583errors.PackError{Err: err}
}

return data, nil
Expand Down Expand Up @@ -238,7 +239,7 @@ func (m *Message) Unpack(src []byte) error {
// locked by the caller.
func (m *Message) wrapErrorUnpack(src []byte) error {
if fieldID, err := m.unpack(src); err != nil {
return &UnpackError{
return &iso8583errors.UnpackError{
Err: err,
FieldID: fieldID,
RawMessage: src,
Expand Down
Loading
Loading