Closed alovak closed 8 months ago
Hey @alovak, thanks for the explanation!
It works for us.
But the retrieval of subfield [CODE]
is not very straightforward though. We have to check the presence of different [CODE]
to get its actual value. Such as
if message.WhateverData.A != nil {
code = "A"
}
if message.WhateverData.B != nil {
code = "B"
}
...
Fortunately, we only have a few possible values to define in the message specification.
We will follow what you've suggested here, please let me know if you have other more elegant options. Thanks in advance!
One of the options can be creating a custom field, but it's more work and the resulting code will still require some if
s/switch/case
statements. The approach with custom field is the following:
// The following code may not compile; it is provided just to illustrate an idea.
// In your main code, define the field as ASCII or Binary, not as Composite.
// When you unpack the message, get the data of the field and use custom method (or type) to "unpack" it
type DataStruct struct {
TransactionData *TransactionData `index:"A"` // should be defined
VerificationData *VerificationData `index:"B"` // should be defined
}
type CustomField struct {
spec *field.Spec
Data *DataStruct
}
func (f *CustomField) Unpack(data []byte) (interface{}, error) {
// check the data length
// read first byte as a CODE
f.Code = string(data[0:1])
f.Data := &DataStruct{}
field := NewComposite(f.spec) // this spec is for subfields including CODE
err := field.Unmarshal(f.Data)
// ...
}
The more I think about the solution above, the less appealing it seems :)
In your specific case maybe adding the TransactionCode
method to the whateverData
struct is good solution:
func (wd *whateverData) TransactionCode() string {
switch {
case wd == nil:
return ""
case wd.A != nil:
return "A"
case wd.B != nil:
return "B"
default:
return "unknown"
}
}
Hey @alovak , we found another way to handle this case.
spec := &iso8583.MessageSpec{
Fields: map[int]field.Field{
0: field.NewString(&field.Spec{
....
3: field.NewComposite(&field.Spec{
Length: 999,
Description: "Whatever data",
Pref: prefix.ASCII.LLL,
Tag: &field.TagSpec{
Sort: sort.StringsByInt,
},
Subfields: map[string]field.Field{
"1": field.NewString(&field.Spec{
Length: 1,
Description: "Type",
Enc: encoding.ASCII,
Pref: prefix.ASCII.Fixed,
}),
"2": field.NewComposite(&field.Spec{
Description: "Additional Data",
Pref: prefix.None.Fixed,
Tag: &field.TagSpec{
Length: 2,
Enc: encoding.ASCII,
Sort: sort.StringsByInt,
},
Subfields: map[string]field.Field{
"01": field.NewString(&field.Spec{
Length: 5,
Description: "Code1",
Pref: prefix.ASCII.Fixed,
Enc: encoding.ASCII,
}),
"02": field.NewString(&field.Spec{
Length: 5,
Description: "Code2",
Pref: prefix.ASCII.Fixed,
Enc: encoding.ASCII,
}),
},
}),
},
}),
},
}
type transactionData struct {
Code1 string `index:"01"`
Code2 string `index:"02"`
}
type whateverData struct {
Type string `index:"1"`
TransactionData *transactionData `index:"2"`
}
type data struct {
MTI string `index:"0"`
PrimaryAccountNumber string `index:"2"`
WhateverData *whateverData `index:"3"`
}
in := &data{
MTI: "0100",
PrimaryAccountNumber: "4242424242424242",
WhateverData: &whateverData{
Type: "A",
TransactionData: &transactionData{
Code1: "12345",
Code2: "67890",
},
},
}
For the following field:
[LLL][CODE][TLVs]
[CODE]
is defined as a subfield "1"
with a fixed length.
[TLVs]
is defined as the second subfield "2"
. Thanks to prefix.None.Fixed
, it can represent the rest of the parent composite field.
Inside the second subfield, we can use a regular composite field to represent all TLVs.
It avoids leaking the values into the message spec.
Let's say you have a case where you have to build the following field:
[LLL][CODE][TLVs]
The main question here is how to place
CODE
between the length prefix and the set of TLVs?It can be achieved by using the
None.Fixed
prefix and putting composite field inside composite field making the parent composite subfield tag to be ourCODE
. TheNone.Fixed
prefix returns the length of the data it has as an input (maybeNone
name is not ideal). The composite inside composite allows us to dynamically add data (e.g.CODE
) depending on the subfields set.In the following test we will have the following packed value for field 3:
where:
If you define more than one subfield (e.g.
A
,B
) then only one such subfield can have value either A or B.