Skip to content

Commit

Permalink
Add composite capability to handle bitmaps
Browse files Browse the repository at this point in the history
  • Loading branch information
FacundoMora committed Jan 26, 2023
1 parent df3dc8a commit e53af04
Show file tree
Hide file tree
Showing 2 changed files with 143 additions and 13 deletions.
148 changes: 139 additions & 9 deletions field/composite.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,15 @@ import (
"fmt"
"reflect"
"regexp"
"strconv"

"github.com/moov-io/iso8583/padding"
"github.com/moov-io/iso8583/sort"
"github.com/moov-io/iso8583/utils"
)

const compositeBitmapKey = "0"

var _ Field = (*Composite)(nil)
var _ json.Marshaler = (*Composite)(nil)
var _ json.Unmarshaler = (*Composite)(nil)
Expand Down Expand Up @@ -58,6 +61,8 @@ var _ json.Unmarshaler = (*Composite)(nil)
type Composite struct {
spec *Spec

bitmap *Bitmap

orderedSpecFieldTags []string

// stores all fields according to the spec
Expand Down Expand Up @@ -115,7 +120,10 @@ func (f *Composite) SetSpec(spec *Spec) {
panic(err)
}
f.spec = spec
f.orderedSpecFieldTags = orderedKeys(spec.Subfields, spec.Tag.Sort)

if spec.Tag != nil && spec.Tag.Sort != nil {
f.orderedSpecFieldTags = orderedKeys(spec.Subfields, spec.Tag.Sort)
}
}

func (f *Composite) Unmarshal(v interface{}) error {
Expand Down Expand Up @@ -290,6 +298,18 @@ func (f *Composite) Bytes() ([]byte, error) {
return f.pack()
}

// Bitmap returns an instantiation of Bitmap using the BitmapSpec from the spec attribute.
func (f *Composite) Bitmap() *Bitmap {
if f.bitmap == nil {
if field, ok := f.subfields[compositeBitmapKey]; ok {
f.bitmap = field.(*Bitmap)
}

}

return f.bitmap
}

// String iterates over the receiver's subfields, packs them and converts the
// result to a string. The result does not incorporate the encoded aggregate
// length of the subfields in the prefix.
Expand Down Expand Up @@ -342,6 +362,53 @@ func (f *Composite) UnmarshalJSON(b []byte) error {
}

func (f *Composite) pack() ([]byte, error) {
if f.Bitmap() != nil {
return f.packSubfieldsByBitmap()
}

return f.packSubfieldsByTag()
}

func (f *Composite) packSubfieldsByBitmap() ([]byte, error) {
packed := []byte{}
f.Bitmap().Reset()

ids, err := f.packableFieldIDs()
if err != nil {
return nil, fmt.Errorf("failed to pack message: %w", err)
}

for _, id := range ids {
// index 0 is for bitmap
// regular field number startd from index 1
idInt, err := strconv.Atoi(id)
if err != nil {
return nil, fmt.Errorf("failed to pack message: %w", err)
}

if idInt < 1 {
continue
}
f.Bitmap().Set(idInt)
}

// pack fields
for _, i := range ids {
field, ok := f.subfields[i]
if !ok {
return nil, fmt.Errorf("failed to pack field %v: no specification found", i)
}
packedField, err := field.Pack()
if err != nil {
return nil, fmt.Errorf("failed to pack field %v (%s): %w", i, field.Spec().Description, err)
}
packed = append(packed, packedField...)
}

return packed, nil
}

func (f *Composite) packSubfieldsByTag() ([]byte, error) {
packed := []byte{}
for _, tag := range f.orderedSpecFieldTags {
field, ok := f.subfields[tag]
Expand Down Expand Up @@ -377,6 +444,9 @@ func (f *Composite) pack() ([]byte, error) {
}

func (f *Composite) unpack(data []byte, isVariableLength bool) (int, error) {
if f.Bitmap() != nil {
return f.unpackSubfieldsByBitmap(data)
}
if f.spec.Tag.Enc != nil {
return f.unpackSubfieldsByTag(data)
}
Expand Down Expand Up @@ -442,21 +512,63 @@ func (f *Composite) unpackSubfieldsByTag(data []byte) (int, error) {
return offset, nil
}

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

// reset fields that were set
f.setSubfields = map[string]struct{}{}

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

off += read

for i := 1; i <= f.Bitmap().Len(); i++ {
if f.Bitmap().IsSet(i) {
iStr := fmt.Sprintf("%v", i)

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

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

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

off += read
}
}

return off, nil
}

func validateCompositeSpec(spec *Spec) error {
if spec.Tag == nil || spec.Tag.Sort == nil {
return fmt.Errorf("Composite spec requires a Tag.Sort function to be defined")
var bm *Bitmap
if value, ok := spec.Subfields[compositeBitmapKey]; ok {
bm = value.(*Bitmap)
}
if spec.Pad != nil && spec.Pad != padding.None {
return fmt.Errorf("Composite spec only supports nil or None spec padding values")

if (spec.Tag == nil || spec.Tag.Sort == nil) && bm == nil {
return fmt.Errorf("Composite spec requires a Tag.Sort function or a BitmapSpec to be defined")
}
if spec.Enc != nil {
return fmt.Errorf("Composite spec only supports a nil Enc value")
if spec.Tag != nil && spec.Tag.Sort != nil && bm != nil {
return fmt.Errorf("Composite spec doesn't support Tag.Sort function when a BitmapSpec is defined")
}
if spec.Tag != nil && spec.Tag.Enc == nil && spec.Tag.Length > 0 {
return fmt.Errorf("Composite spec requires a Tag.Enc to be defined if Tag.Length > 0")
}
if spec.Tag.Sort == nil {
return fmt.Errorf("Composite spec requires a Tag.Sort function to be defined")
if spec.Pad != nil && spec.Pad != padding.None {
return fmt.Errorf("Composite spec only supports nil or None spec padding values")
}
if spec.Enc != nil {
return fmt.Errorf("Composite spec only supports a nil Enc value")
}
return nil
}
Expand Down Expand Up @@ -488,3 +600,21 @@ func getFieldIndexOrTag(field reflect.StructField) (string, error) {

return "", nil
}

func (f *Composite) packableFieldIDs() ([]string, error) {
// Index 0 represent bitmap which is always populated.
populatedFieldIDs := []string{compositeBitmapKey}

for id := range f.setSubfields {
// represents the bitmap
if id == compositeBitmapKey {
continue
}

populatedFieldIDs = append(populatedFieldIDs, id)
}

sort.StringsByInt(populatedFieldIDs)

return populatedFieldIDs, nil
}
8 changes: 4 additions & 4 deletions field/composite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -979,8 +979,8 @@ func TestCompositePanicsOnSpecValidationFailures(t *testing.T) {
spec *Spec
}{
{
desc: "panics on nil Tag being defined in spec",
err: "Composite spec requires a Tag.Sort function to be defined",
desc: "panics on nil Tag and no Bitmap being defined in spec",
err: "Composite spec requires a Tag.Sort function or a BitmapSpec to be defined",
spec: &Spec{
Length: 6,
Pref: prefix.ASCII.Fixed,
Expand All @@ -989,8 +989,8 @@ func TestCompositePanicsOnSpecValidationFailures(t *testing.T) {
},
},
{
desc: "panics on nil Tag.Sort being defined in spec",
err: "Composite spec requires a Tag.Sort function to be defined",
desc: "panics on nil Tag and no Bitmap being defined in spec",
err: "Composite spec requires a Tag.Sort function or a BitmapSpec to be defined",
spec: &Spec{
Length: 6,
Pref: prefix.ASCII.Fixed,
Expand Down

0 comments on commit e53af04

Please sign in to comment.