moov-io / iso8583

A golang implementation to marshal and unmarshal iso8583 message.
https://moov.io
Apache License 2.0
306 stars 100 forks source link

Clear field in message using zero-value #192

Closed svz-ya closed 1 year ago

svz-ya commented 1 year ago

I would like to have the ability to reset a field in message. So calling

msg.Field(14, "")

or

msg.BinaryField(14, nil)

clears Field 14 from message and it will not appear in raw message after packing.

alovak commented 1 year ago

@svz-ya thank you for your contribution to the package!

Do you want to remove fields from the request message in order to use it as a response message?

Setting zero value to remove the field works as unexpected behavior. For example, if a field has a padder and the field is set with an empty string, it will be padded (or filled) during packing (it will not be empty string as a result, but something like 0000000). So, we can't use empty values to remove fields.

We also have a PR with a pretty long discussion here: https://github.com/moov-io/iso8583/pull/113

Removing the field from the message has one concern - we may not remove/reset all we need, and it may result in undesired side effects. For example, this PR does not unset bit in the bitmap, and it also doesn't remove the field from m.fields. What should happen if you remove field 1 (bitmap)?

If you want to proceed with the PR, let's focus on RemoveFields (any better name? Unset?) method then. We cleaned up some internal things in the package since https://github.com/moov-io/iso8583/pull/113 and I feel more confident with implementing field removal now. Let's just address things that I mentioned above.

Also, do you use data structs to (un)marshal messages? If so, then here is what you can do in order to create a copy of the request. I'm providing two variants:

package demo

import (
    "github.com/moov-io/iso8583"
    "github.com/moov-io/iso8583/field"
)

// 0100
type AuthorizationRequest struct {
    MTI                      *field.String  `index:"0"`
    PrimaryAccountNumber     *field.String  `index:"2"`
    ProcessingCode           *field.String  `index:"3"`
    AmountTransaction        *field.Numeric `index:"4"`
    TransmissionDateTime     *field.String  `index:"7"`
    STAN                     *field.String  `index:"11"`
    LocalTransactionTime     *field.String  `index:"12"`
    LocalTransactionDate     *field.String  `index:"13"`
    RetrievalReferenceNumber *field.String  `index:"37"`
}

// 0110
type AuthorizationResponse struct {
    MTI                      *field.String  `index:"0"`
    PrimaryAccountNumber     *field.String  `index:"2"`
    ProcessingCode           *field.String  `index:"3"`
    AmountTransaction        *field.Numeric `index:"4"`
    TransmissionDateTime     *field.String  `index:"7"`
    STAN                     *field.String  `index:"11"`
    LocalTransactionTime     *field.String  `index:"12"`
    LocalTransactionDate     *field.String  `index:"13"`
    RetrievalReferenceNumber *field.String  `index:"37"`
    // reponse has additional fields
    AuthorizationCode *field.String `index:"38"`
    ResponseCode      *field.String `index:"39"`
}

// handler for incoming messages
func handler(requestMessage *iso8583.Message) (*iso8583.Message, error) {
    // *******************************************************************
    // variant 1
    // copy all fields from the request message into a new response message
    responseMessage, err := copyRequestFieldsIntoNewMessage(requestMessage)
    if err != nil {
        // handle error
    }

    // set or overwrite fields in the response message
    // set the MTI to 0110
    responseMessage.MTI("0110")
    // set the authorization code to 123456
    responseMessage.Field(38, "123456")
    // set the response code to 00
    responseMessage.Field(39, "00")

    // send the response message to the network connection
    // ...

    // *******************************************************************
    // variant 2
    responseData, err := copyRequestFieldsIntoResponse(requestMessage)
    if err != nil {
        // handle error
    }

    responseData.AuthorizationCode.Value = "123456"
    responseData.ResponseCode.Value = "00"

    // create response
    responseMessage := iso8583.NewMessage(spec)
    err = responseMessage.Marshal(responseData)
    if err != nil {
        // handle error
    }

    // send the response message to the network connection
    // ...
}

func copyRequestFieldsIntoNewMessage(requestMessage *iso8583.Message) (*iso8583.Message, error) {
    // by unmarshalling the request message into response data, we get
    // values for all fields defined in the response struct
    responseData := &AuthorizationResponse{}
    err := requestMessage.Unmarshal(responseData)
    if err != nil {
        // handle error
    }

    responseMessage := iso8583.NewMessage(spec)
    err = responseMessage.Marshal(responseData)
    if err != nil {
        // handle error
    }

    return responseMessage, nil
}

func copyRequestFieldsIntoResponse(requestMessage *iso8583.Message) (*AuthorizationResponse, error) {
    // by unmarshalling the request message into response data, we get
    // values for all fields defined in the response struct
    responseData := &AuthorizationResponse{}
    err := requestMessage.Unmarshal(responseData)
    if err != nil {
        // handle error
    }

    return responseData, nil
}
svz-ya commented 1 year ago

@alovak Thank you for such a detailed comment! Your point is clear and I will proceed with Variant 2.

So I think the proposed change is not needed anymore :)