diff --git a/README.md b/README.md index 957331a0..342725c4 100644 --- a/README.md +++ b/README.md @@ -78,7 +78,7 @@ Create a file on the HTTP server: curl -X POST --data-binary "@./test/testdata/fedWireMessage-CustomerTransfer.txt" http://localhost:8088/files/create ``` ``` -{"id":"","fedWireMessage":{"id":"","senderSupplied":{"formatVersion":"30", ..... +{"id":"","fedWireMessages":[{"id":"","senderSupplied":{"formatVersion":"30", ..... ``` Get the file in its original format: diff --git a/client/api/openapi.yaml b/client/api/openapi.yaml index f4d75019..712394bb 100644 --- a/client/api/openapi.yaml +++ b/client/api/openapi.yaml @@ -359,9 +359,410 @@ components: schemas: WireFile: example: + validateOptions: + allowMissingSenderSupplied: true + skipMandatoryIMAD: true ID: 3f2d23ee214 - fedWireMessage: - orderingInstitution: + fedWireMessages: + - orderingInstitution: + swiftLineFour: Swift Line Four + swiftLineOne: Swift Line One + swiftLineFive: Swift Line Five + swiftLineThree: Swift Line Three + swiftLineSix: Swift Line Six + swiftLineTwo: Swift Line Two + swiftFieldTag: SWIFT + localInstrument: + localInstrumentCode: ANSI + proprietaryCode: WJD786363 + fiBeneficiaryFI: + lineTwo: Line Two + lineFive: Line Five + lineSix: Line Six + lineOne: Line One + lineFour: Line Four + lineThree: Line Three + errorWire: + errorDescription: Data Error + errorCategory: E + errorCode: E99 + beneficiaryCustomer: + swiftLineFour: Swift Line Four + swiftLineOne: Swift Line One + swiftLineFive: Swift Line Five + swiftLineThree: Swift Line Three + swiftLineSix: Swift Line Six + swiftLineTwo: Swift Line Two + swiftFieldTag: SWIFT + dateRemittanceDocument: + dateRemittanceDocument: "20190401" + messageDisposition: + testProductionCode: T + messageDuplicationCode: R + messageStatusIndicator: "0" + formatVersion: "30" + accountCreditedDrawdown: + drawdownCreditAccountNumber: "121042882" + exchangeRate: + exchangeRate: 1,2345 + orderingCustomer: + swiftLineFour: Swift Line Four + swiftLineOne: Swift Line One + swiftLineFive: Swift Line Five + swiftLineThree: Swift Line Three + swiftLineSix: Swift Line Six + swiftLineTwo: Swift Line Two + swiftFieldTag: SWIFT + instructedAmount: + amount: 1234,56 + currencyCode: USD + ID: 3f2d23ee214 + remittance: + swiftLineFour: Swift Line Four + swiftLineOne: Swift Line One + swiftLineFive: Swift Line Five + swiftLineThree: Swift Line Three + swiftLineSix: Swift Line Six + swiftLineTwo: Swift Line Two + swiftFieldTag: SWIFT + fiBeneficiaryAdvice: + adviceCode: HLD + lineTwo: Line Two + lineFive: Line Five + lineSix: Line Six + lineOne: Line One + lineFour: Line Four + lineThree: Line Three + fiAdditionalFIToFI: + lineTwo: Line Two + lineFive: Line Five + lineSix: Line Six + lineOne: Line One + lineFour: Line Four + lineThree: Line Three + paymentNotification: + contactMobileNumber: 555.555.5555 + contactName: Wade Arnold + faxNumber: 555.555.5555 + contactPhoneNumber: 555-555-5555 + paymentNotificationIndicator: "1" + contactNotificationElectronicAddress: https://moov.io/ + endToEndIdentification: WireTransfer 10001 + outputMessageAccountabilityData: + outputFRBApplicationIdentification: OB11 + outputSequenceNumber: "000001" + outputDate: "0401" + outputDestinationID: "12345678" + outputCycleDate: "20190401" + outputTime: "1305" + charges: + sendersChargesOne: USD1234,56 + sendersChargesFour: USD1234,56 + sendersChargesThree: USD1234,56 + chargeDetails: B + sendersChargesTwo: USD1234,56 + remittanceBeneficiary: + identificationCode: BANK + remittanceData: + country: US + townName: Any Town + addressType: ADDR + addressLineOne: AddressLineOne + addressLineFive: AddressLineFive + subDepartment: Service + addressLineSix: AddressLineSix + countryOfResidence: US + streetName: Street Way Boulevard + addressLineTwo: AddressLineTwo + countrySubDivisionState: PA + name: Wade Arnold + buildingNumber: 1A + postCode: "19465" + department: Buildings + addressLineThree: AddressLineThree + addressLineFour: AddressLineFour + addressLineSeven: AddressLineSeven + identificationNumber: "192827828" + identificationType: OI + dateBirthPlace: 03062013 Chester + identificationNumberIssuer: Identification Number Issuer + fiIntermediaryFIAdvice: + adviceCode: HLD + lineTwo: Line Two + lineFive: Line Five + lineSix: Line Six + lineOne: Line One + lineFour: Line Four + lineThree: Line Three + senderSupplied: + testProductionCode: T + messageDuplicationCode: R + userRequestCorrelation: TESTDATA + formatVersion: "30" + beneficiary: + personal: + identificationCode: B + identifier: "123456789" + address: + addressLineTwo: Address Two + addressLineOne: Address One + addressLineThree: Address Three + name: John Doe + businessFunctionCode: + businessFunctionCode: BTR + transactionTypeCode: transactionTypeCode + beneficiaryFI: + identificationCode: B + identifier: "123456789" + address: + addressLineTwo: Address Two + addressLineOne: Address One + addressLineThree: Address Three + name: FI Name + intermediaryInstitution: + swiftLineFour: Swift Line Four + swiftLineOne: Swift Line One + swiftLineFive: Swift Line Five + swiftLineThree: Swift Line Three + swiftLineSix: Swift Line Six + swiftLineTwo: Swift Line Two + swiftFieldTag: SWIFT + receiptTimeStamp: + receiptTime: "1305" + receiptDate: "0401" + receiptApplicationIdentification: RB11 + previousMessageIdentifier: + previousMessageIdentifier: Identifier + adjustment: + amount: "1234.56789" + additionalInfo: Additional Info + creditDebitIndicator: DBIT + currencyCode: USD + adjustmentReasonCode: CM + inputMessageAccountabilityData: + inputSource: 'XYZ ABC ' + inputCycleDate: "20191201" + inputSequenceNumber: "000001" + fiPaymentMethodToBeneficiary: + AdditionalInformation: For goods and services + paymentMethod: CHECK + currencyInstructedAmount: + amount: 1234,56 + swiftFieldTag: SWIFT + instructingFI: + identificationCode: B + identifier: "123456789" + address: + addressLineTwo: Address Two + addressLineOne: Address One + addressLineThree: Address Three + name: FI Name + relatedRemittance: + remittanceData: + country: US + townName: Any Town + addressType: ADDR + addressLineOne: AddressLineOne + addressLineFive: AddressLineFive + subDepartment: Service + addressLineSix: AddressLineSix + countryOfResidence: US + streetName: Street Way Boulevard + addressLineTwo: AddressLineTwo + countrySubDivisionState: PA + name: Wade Arnold + buildingNumber: 1A + postCode: "19465" + department: Buildings + addressLineThree: AddressLineThree + addressLineFour: AddressLineFour + addressLineSeven: AddressLineSeven + remittanceIdentification: Remittance Identification + remittanceLocationMethod: EDIC + remittanceLocationElectronicAddress: https://moov.io + beneficiaryIntermediaryFI: + identificationCode: B + identifier: "123456789" + address: + addressLineTwo: Address Two + addressLineOne: Address One + addressLineThree: Address Three + name: FI Name + originator: + personal: + identificationCode: B + identifier: "123456789" + address: + addressLineTwo: Address Two + addressLineOne: Address One + addressLineThree: Address Three + name: John Doe + senderDepositoryInstitution: + senderABANumber: "091905114" + senderShortName: MIDWESTONE B&T + beneficiaryReference: + beneficiaryReference: Test Data + fiIntermediaryFI: + lineTwo: Line Two + lineFive: Line Five + lineSix: Line Six + lineOne: Line One + lineFour: Line Four + lineThree: Line Three + institutionAccount: + swiftLineFour: Swift Line Four + swiftLineOne: Swift Line One + swiftLineFive: Swift Line Five + swiftLineThree: Swift Line Three + swiftLineSix: Swift Line Six + swiftLineTwo: Swift Line Two + swiftFieldTag: SWIFT + originatorFI: + identificationCode: B + identifier: "123456789" + address: + addressLineTwo: Address Two + addressLineOne: Address One + addressLineThree: Address Three + name: FI Name + unstructuredAddenda: + addenda: Payment for goods + addendaLength: "0987" + amountNegotiatedDiscount: + amount: "1234.56789" + currencyCode: USD + originatorToBeneficiary: + lineTwo: Line Two + lineOne: Line One + lineFour: Line Four + lineThree: Line Three + originatorOptionF: + lineTwo: 3/US/NEW YORK, NY 10000 + name: 1/SMITH JOHN + lineOne: 2/123 MAIN STREET + lineThree: 7/111-22-3456 + partyIdentifier: /123456 + remittanceOriginator: + identificationCode: BANK + remittanceData: + country: US + townName: Any Town + addressType: ADDR + addressLineOne: AddressLineOne + addressLineFive: AddressLineFive + subDepartment: Service + addressLineSix: AddressLineSix + countryOfResidence: US + streetName: Street Way Boulevard + addressLineTwo: AddressLineTwo + countrySubDivisionState: PA + name: Wade Arnold + buildingNumber: 1A + postCode: "19465" + department: Buildings + addressLineThree: AddressLineThree + addressLineFour: AddressLineFour + addressLineSeven: AddressLineSeven + contactName: Wade Arnold + identificationType: OI + countryOfResidence: US + contactMobileNumber: 555.555.5555 + contactFaxNumber: 555.555.5555 + identificationNumber: "192827828" + contactOther: Contact Other + contactPhoneNumber: 555.555.5555 + contactElectronicAddress: https://moov.io + dateBirthPlace: 03062013 Chester + identificationNumberIssuer: Identification Number Issuer + actualAmountPaid: + amount: "1234.56789" + currencyCode: USD + remittanceFreeText: + lineTwo: Line Two Text + lineOne: Line One Text + lineThree: Line Three Text + fiDrawdownDebitAccountAdvice: + adviceCode: HLD + lineTwo: Line Two + lineFive: Line Five + lineSix: Line Six + lineOne: Line One + lineFour: Line Four + lineThree: Line Three + amount: + amount: "000000100000" + accountDebitedDrawdown: + identificationCode: D + identifier: "123456789" + addressLineTwo: Address Two + name: John Doe + addressLineOne: Address One + addressLineThree: Address Three + secondaryRemittanceDocument: + documentIdentificationNumber: Document2 + documentTypeCode: AROI + proprietaryDocumentTypeCode: Proprietary + issuer: Issuer + senderReference: + senderReference: Reference + fiBeneficiaryFIAdvice: + adviceCode: HLD + lineTwo: Line Two + lineFive: Line Five + lineSix: Line Six + lineOne: Line One + lineFour: Line Four + lineThree: Line Three + primaryRemittanceDocument: + documentIdentificationNumber: DOCUMENT 292828 + documentTypeCode: AROI + proprietaryDocumentTypeCode: Proprietary Code + issuer: Remittance Issuer + fiBeneficiary: + lineTwo: Line Two + lineFive: Line Five + lineSix: Line Six + lineOne: Line One + lineFour: Line Four + lineThree: Line Three + senderToReceiver: + swiftLineFour: Swift Line Four + swiftLineOne: Swift Line One + swiftLineFive: Swift Line Five + swiftLineThree: Swift Line Three + swiftLineSix: Swift Line Six + swiftLineTwo: Swift Line Two + swiftFieldTag: SWIFT + fiReceiverFI: + lineTwo: Line Two + lineFive: Line Five + lineSix: Line Six + lineOne: Line One + lineFour: Line Four + lineThree: Line Three + grossAmountRemittanceDocument: + amount: "1234.56789" + currencyCode: USD + typeSubType: + subTypeCode: "00" + typeCode: "10" + serviceMessage: + lineNine: Line Nine Text + lineTen: Line Ten Text + lineTwo: Line Two Text + lineFive: Line Five Text + lineSix: Line Six Text + lineEight: Line Eight Text + lineEleven: Line Eleven Text + lineOne: Line One Text + lineFour: Line Four Text + lineTwelve: Line Twelve Text + lineThree: Line Three Text + lineSeven: Line Seven Text + receiverDepositoryInstitution: + receiverShortName: PREMIER BANK + receiverABANumber: "091905664" + - orderingInstitution: swiftLineFour: Swift Line Four swiftLineOne: Swift Line One swiftLineFive: Swift Line Five @@ -528,9 +929,6 @@ components: receiptTime: "1305" receiptDate: "0401" receiptApplicationIdentification: RB11 - validateOptions: - allowMissingSenderSupplied: true - skipMandatoryIMAD: true previousMessageIdentifier: previousMessageIdentifier: Identifier adjustment: @@ -767,10 +1165,14 @@ components: description: File ID example: 3f2d23ee214 type: string - fedWireMessage: - $ref: '#/components/schemas/FEDWireMessage' + fedWireMessages: + items: + $ref: '#/components/schemas/FEDWireMessage' + type: array + validateOptions: + $ref: '#/components/schemas/ValidateOptions' required: - - fedWireMessage + - fedWireMessages WireFiles: items: $ref: '#/components/schemas/WireFile' @@ -948,9 +1350,6 @@ components: receiptTime: "1305" receiptDate: "0401" receiptApplicationIdentification: RB11 - validateOptions: - allowMissingSenderSupplied: true - skipMandatoryIMAD: true previousMessageIdentifier: previousMessageIdentifier: Identifier adjustment: @@ -1307,8 +1706,6 @@ components: $ref: '#/components/schemas/RemittanceFreeText' serviceMessage: $ref: '#/components/schemas/ServiceMessage' - validateOptions: - $ref: '#/components/schemas/ValidateOptions' required: - amount - businessFunctionCode diff --git a/client/docs/FedWireMessage.md b/client/docs/FedWireMessage.md index 97afc5ab..393c8b5a 100644 --- a/client/docs/FedWireMessage.md +++ b/client/docs/FedWireMessage.md @@ -65,7 +65,6 @@ Name | Type | Description | Notes **SecondaryRemittanceDocument** | [**SecondaryRemittanceDocument**](SecondaryRemittanceDocument.md) | | [optional] **RemittanceFreeText** | [**RemittanceFreeText**](RemittanceFreeText.md) | | [optional] **ServiceMessage** | [**ServiceMessage**](ServiceMessage.md) | | [optional] -**ValidateOptions** | Pointer to [**ValidateOptions**](ValidateOptions.md) | | [optional] [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/client/docs/WireFile.md b/client/docs/WireFile.md index d6760b32..ed57a2e4 100644 --- a/client/docs/WireFile.md +++ b/client/docs/WireFile.md @@ -5,7 +5,8 @@ Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- **ID** | **string** | File ID | [optional] -**FedWireMessage** | [**FedWireMessage**](FEDWireMessage.md) | | +**FedWireMessages** | [**[]FedWireMessage**](FEDWireMessage.md) | | +**ValidateOptions** | Pointer to [**ValidateOptions**](ValidateOptions.md) | | [optional] [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/client/model_fed_wire_message.go b/client/model_fed_wire_message.go index efe29f98..0038871e 100644 --- a/client/model_fed_wire_message.go +++ b/client/model_fed_wire_message.go @@ -73,5 +73,4 @@ type FedWireMessage struct { SecondaryRemittanceDocument SecondaryRemittanceDocument `json:"secondaryRemittanceDocument,omitempty"` RemittanceFreeText RemittanceFreeText `json:"remittanceFreeText,omitempty"` ServiceMessage ServiceMessage `json:"serviceMessage,omitempty"` - ValidateOptions *ValidateOptions `json:"validateOptions,omitempty"` } diff --git a/client/model_wire_file.go b/client/model_wire_file.go index e782df6a..3a3a8390 100644 --- a/client/model_wire_file.go +++ b/client/model_wire_file.go @@ -12,6 +12,7 @@ package openapi // WireFile struct for WireFile type WireFile struct { // File ID - ID string `json:"ID,omitempty"` - FedWireMessage FedWireMessage `json:"fedWireMessage"` + ID string `json:"ID,omitempty"` + FedWireMessages []FedWireMessage `json:"fedWireMessages"` + ValidateOptions *ValidateOptions `json:"validateOptions,omitempty"` } diff --git a/cmd/server/files.go b/cmd/server/files.go index 3b5c3102..74b40fbf 100644 --- a/cmd/server/files.go +++ b/cmd/server/files.go @@ -34,8 +34,7 @@ var ( Help: "The number of WIRE files deleted", }, nil) - errNoFileId = errors.New("no File ID found") - errNoFEDWireMessageID = errors.New("no FEDWireMessage ID found") + errNoFileId = errors.New("no File ID found") ) func addFileRoutes(logger log.Logger, r *mux.Router, repo WireFileRepository) { @@ -45,7 +44,7 @@ func addFileRoutes(logger log.Logger, r *mux.Router, repo WireFileRepository) { r.Methods("DELETE").Path("/files/{fileId}").HandlerFunc(deleteFile(logger, repo)) r.Methods("GET").Path("/files/{fileId}/contents").HandlerFunc(getFileContents(logger, repo)) r.Methods("GET").Path("/files/{fileId}/validate").HandlerFunc(validateFile(logger, repo)) - r.Methods("POST").Path("/files/{fileId}/FEDWireMessage").HandlerFunc(addFEDWireMessageToFile(logger, repo)) + r.Methods("POST").Path("/files/{fileId}/messages").HandlerFunc(addFEDWireMessageToFile(logger, repo)) } func getFileId(w http.ResponseWriter, r *http.Request) string { @@ -57,15 +56,6 @@ func getFileId(w http.ResponseWriter, r *http.Request) string { return v } -func getFEDWireMessageID(w http.ResponseWriter, r *http.Request) string { - v, ok := mux.Vars(r)["FEDWireMessageID"] - if !ok || v == "" { - moovhttp.Problem(w, errNoFEDWireMessageID) - return "" - } - return v -} - func getFiles(logger log.Logger, repo WireFileRepository) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if requestID := moovhttp.GetRequestID(r); requestID != "" { @@ -117,7 +107,7 @@ func createFile(logger log.Logger, repo WireFileRepository) http.HandlerFunc { moovhttp.Problem(w, err) return } - file = &f + file = f } if file.ID == "" { @@ -284,7 +274,7 @@ func validateFile(logger log.Logger, repo WireFileRepository) http.HandlerFunc { return } - if err := file.Create(); err != nil { // Create calls Validate + if err := file.Validate(); err != nil { err = logger.LogErrorf("file was invalid: %v", err).Err() moovhttp.Problem(w, err) return @@ -337,7 +327,7 @@ func addFEDWireMessageToFile(logger log.Logger, repo WireFileRepository) http.Ha return } - file.FEDWireMessage = file.AddFEDWireMessage(req) + file.AddFEDWireMessage(req) if err := repo.saveFile(file); err != nil { err = logger.LogErrorf("error saving file: %v", err).Err() moovhttp.Problem(w, err) diff --git a/cmd/server/files_test.go b/cmd/server/files_test.go index 4e1d9fb1..353ff1aa 100644 --- a/cmd/server/files_test.go +++ b/cmd/server/files_test.go @@ -34,14 +34,6 @@ func TestFileId(t *testing.T) { assert.Equal(t, http.StatusBadRequest, w.Code, w.Body) } -func TestFEDWireMessageID(t *testing.T) { - w := httptest.NewRecorder() - req := httptest.NewRequest("GET", "/foo", nil) - - assert.Empty(t, getFEDWireMessageID(w, req)) - assert.Equal(t, http.StatusBadRequest, w.Code, w.Body) -} - func TestFiles_getFiles(t *testing.T) { repo := &testWireFileRepository{ file: &wire.File{ @@ -81,7 +73,7 @@ func readFile(filename string) (*wire.File, error) { return nil, err } f, err := wire.NewReader(fd).Read() - return &f, err + return f, err } func TestFiles_createWithInterfaceData(t *testing.T) { @@ -138,11 +130,11 @@ func TestFiles_createFile(t *testing.T) { router.ServeHTTP(w, req) w.Flush() - assert.Equal(t, http.StatusCreated, w.Code, w.Body) + require.Equal(t, http.StatusCreated, w.Code, w.Body) var resp wire.File require.NoError(t, json.NewDecoder(w.Body).Decode(&resp)) assert.NotEmpty(t, resp.ID) - assert.NotNil(t, resp.FEDWireMessage.FIAdditionalFIToFI) + assert.NotNil(t, resp.FEDWireMessages[0].FIAdditionalFIToFI) }) t.Run("repo error", func(t *testing.T) { @@ -176,7 +168,7 @@ func TestFiles_createFileJSON(t *testing.T) { var resp wire.File require.NoError(t, json.NewDecoder(w.Body).Decode(&resp)) assert.NotEmpty(t, resp.ID) - assert.NotNil(t, resp.FEDWireMessage.FIAdditionalFIToFI) + assert.NotNil(t, resp.FEDWireMessages[0].FIAdditionalFIToFI) }) t.Run("creates file from JSON", func(t *testing.T) { @@ -194,8 +186,8 @@ func TestFiles_createFileJSON(t *testing.T) { var resp wire.File require.NoError(t, json.NewDecoder(w.Body).Decode(&resp)) assert.NotEmpty(t, resp.ID) - assert.NotEmpty(t, resp.FEDWireMessage) - assert.Nil(t, resp.FEDWireMessage.ValidateOptions) + assert.NotEmpty(t, resp.FEDWireMessages) + assert.Empty(t, resp.ValidateOptions) }) t.Run("invalid JSON", func(t *testing.T) { @@ -217,7 +209,6 @@ func TestFiles_createFile_missingSenderSupplied(t *testing.T) { // set up a message with no SenderSupplied field fwm := mockFEDWireMessage() - fwm.ValidateOptions = nil fwm.SenderSupplied = nil file := wire.NewFile() file.AddFEDWireMessage(fwm) @@ -228,21 +219,20 @@ func TestFiles_createFile_missingSenderSupplied(t *testing.T) { require.Contains(t, resp.Body.String(), "SenderSupplied") // create from JSON, using validation options, should succeed without sender supplied - file.FEDWireMessage.ValidateOptions = &wire.ValidateOpts{ + file.ValidateOptions = wire.ValidateOpts{ AllowMissingSenderSupplied: true, } resp, uploaded := routerUploadJSON(t, router, file) require.Equal(t, http.StatusCreated, resp.Code, resp.Body) assert.NotEmpty(t, uploaded.ID) - assert.Nil(t, uploaded.FEDWireMessage.SenderSupplied) + assert.Nil(t, uploaded.FEDWireMessages[0].SenderSupplied) // make sure the file was saved resp, found := routerGetFile(t, router, uploaded.ID) require.Equal(t, http.StatusOK, resp.Code, resp.Body) assert.Equal(t, uploaded.ID, found.ID) - assert.Nil(t, found.FEDWireMessage.SenderSupplied) - assert.NotNil(t, found.FEDWireMessage.ValidateOptions) - assert.True(t, found.FEDWireMessage.ValidateOptions.AllowMissingSenderSupplied) + assert.Nil(t, found.FEDWireMessages[0].SenderSupplied) + assert.True(t, found.ValidateOptions.AllowMissingSenderSupplied) // get file contents calls Validate() // if isIncoming was passed properly, then the file should be valid @@ -256,15 +246,14 @@ func TestFiles_createFile_missingSenderSupplied(t *testing.T) { ) require.Equal(t, http.StatusCreated, resp.Code, resp.Body) assert.NotEmpty(t, rawUpload.ID) - assert.Nil(t, rawUpload.FEDWireMessage.SenderSupplied) - assert.NotNil(t, rawUpload.FEDWireMessage.ValidateOptions) - assert.True(t, rawUpload.FEDWireMessage.ValidateOptions.AllowMissingSenderSupplied) + assert.Nil(t, rawUpload.FEDWireMessages[0].SenderSupplied) + assert.True(t, rawUpload.ValidateOptions.AllowMissingSenderSupplied) // get new file resp, found = routerGetFile(t, router, rawUpload.ID) require.Equal(t, http.StatusOK, resp.Code, resp.Body) assert.Equal(t, rawUpload.ID, found.ID) - assert.Nil(t, found.FEDWireMessage.SenderSupplied) + assert.Nil(t, found.FEDWireMessages[0].SenderSupplied) // get new file contents resp = routerGetFileContents(t, router, rawUpload.ID) @@ -418,8 +407,8 @@ func TestFiles_getFileContents(t *testing.T) { fwm := mockFEDWireMessage() repo := &testWireFileRepository{ file: &wire.File{ - ID: base.ID(), - FEDWireMessage: fwm, + ID: base.ID(), + FEDWireMessages: []wire.FEDWireMessage{fwm}, }, } router := mux.NewRouter() @@ -461,8 +450,8 @@ func TestFiles_getFileContentsWithFormatAndNewLineQueryParams(t *testing.T) { fwm := mockFEDWireMessage() repo := &testWireFileRepository{ file: &wire.File{ - ID: base.ID(), - FEDWireMessage: fwm, + ID: base.ID(), + FEDWireMessages: []wire.FEDWireMessage{fwm}, }, } router := mux.NewRouter() @@ -574,7 +563,7 @@ func TestFiles_validateFile(t *testing.T) { func TestFiles_addFEDWireMessageToFile(t *testing.T) { f, err := readFile("fedWireMessage-NoMessage.txt") - require.Contains(t, err.Error(), "file validation failed") + require.ErrorContains(t, err, "file validation failed") fwm := mockFEDWireMessage() repo := &testWireFileRepository{file: f} router := mux.NewRouter() @@ -584,7 +573,7 @@ func TestFiles_addFEDWireMessageToFile(t *testing.T) { w := httptest.NewRecorder() var buf bytes.Buffer require.NoError(t, json.NewEncoder(&buf).Encode(fwm)) - req := httptest.NewRequest("POST", "/files/foo/FEDWireMessage", &buf) + req := httptest.NewRequest("POST", "/files/foo/messages", &buf) router.ServeHTTP(w, req) w.Flush() @@ -592,7 +581,7 @@ func TestFiles_addFEDWireMessageToFile(t *testing.T) { assert.Equal(t, http.StatusOK, w.Code, w.Body) var out wire.File require.NoError(t, json.NewDecoder(w.Body).Decode(&out)) - assert.NotNil(t, out.FEDWireMessage.SenderSupplied) + assert.NotNil(t, out.FEDWireMessages[0].SenderSupplied) }) t.Run("repo error", func(t *testing.T) { @@ -600,7 +589,7 @@ func TestFiles_addFEDWireMessageToFile(t *testing.T) { repo.err = errors.New("bad error") var buf bytes.Buffer require.NoError(t, json.NewEncoder(&buf).Encode(fwm)) - req := httptest.NewRequest("POST", "/files/foo/FEDWireMessage", &buf) + req := httptest.NewRequest("POST", "/files/foo/messages", &buf) router.ServeHTTP(w, req) w.Flush() @@ -614,7 +603,7 @@ func TestFiles_addFEDWireMessageToFile(t *testing.T) { repo.err = nil var buf bytes.Buffer require.NoError(t, json.NewEncoder(&buf).Encode(fwm)) - req := httptest.NewRequest("POST", "/files/foo/FEDWireMessage", &buf) + req := httptest.NewRequest("POST", "/files/foo/messages", &buf) router.ServeHTTP(w, req) w.Flush() @@ -623,36 +612,36 @@ func TestFiles_addFEDWireMessageToFile(t *testing.T) { }) } -/*func TestFiles_removeFEDWireMessageFromFile(t *testing.T) { - f, err := readFile("fedWireMessage-CustomerTransfer.txt") - if err != nil { - t.Fatal(err) - } - repo := &testWireFileRepository{file: f} - - FEDWireMessageID := base.ID() - repo.file.FEDWireMessage.ID = FedWireMessageID - - w := httptest.NewRecorder() - req := httptest.NewRequest("DELETE", fmt.Sprintf("/files/foo/FEDWireMessage/%s", FEDWireMessageID), nil) - - router := mux.NewRouter() - addFileRoutes(log.NewNopLogger(), router, repo) - router.ServeHTTP(w, req) - w.Flush() - - if w.Code != http.StatusOK { - t.Errorf("bogus HTTP status: %d: %v", w.Code, w.Body.String()) - } - - // error case - repo.err = errors.New("bad error") - - w = httptest.NewRecorder() - router.ServeHTTP(w, req) - w.Flush() - - if w.Code != http.StatusBadRequest { - t.Errorf("bogus HTTP status: %d: %v", w.Code, w.Body.String()) - } -}*/ +// func TestFiles_removeFEDWireMessageFromFile(t *testing.T) { +// f, err := readFile("fedWireMessage-CustomerTransfer.txt") +// if err != nil { +// t.Fatal(err) +// } +// repo := &testWireFileRepository{file: f} +// +// FEDWireMessageID := base.ID() +// repo.file.FEDWireMessage[0].ID = FEDWireMessageID +// +// w := httptest.NewRecorder() +// req := httptest.NewRequest("DELETE", fmt.Sprintf("/files/foo/FEDWireMessage/%s", FEDWireMessageID), nil) +// +// router := mux.NewRouter() +// addFileRoutes(log.NewNopLogger(), router, repo) +// router.ServeHTTP(w, req) +// w.Flush() +// +// if w.Code != http.StatusOK { +// t.Errorf("bogus HTTP status: %d: %v", w.Code, w.Body.String()) +// } +// +// // error case +// repo.err = errors.New("bad error") +// +// w = httptest.NewRecorder() +// router.ServeHTTP(w, req) +// w.Flush() +// +// if w.Code != http.StatusBadRequest { +// t.Errorf("bogus HTTP status: %d: %v", w.Code, w.Body.String()) +// } +// } diff --git a/cmd/webui/wire/wire_js.go b/cmd/webui/wire/wire_js.go index 222df28a..107d582b 100644 --- a/cmd/webui/wire/wire_js.go +++ b/cmd/webui/wire/wire_js.go @@ -18,7 +18,7 @@ func isJSON(input string) bool { func parseContents(input string) (*wire.File, error) { - var file wire.File + var file *wire.File var err error if isJSON(input) { @@ -32,7 +32,7 @@ func parseContents(input string) (*wire.File, error) { } } - return &file, nil + return file, nil } func prettyJson(file *wire.File) (string, error) { diff --git a/docs/usage-docker.md b/docs/usage-docker.md index 1e0fec78..af50a5b8 100644 --- a/docs/usage-docker.md +++ b/docs/usage-docker.md @@ -38,7 +38,7 @@ Create a file on the HTTP server: curl -X POST --data-binary "@./test/testdata/fedWireMessage-CustomerTransfer.txt" http://localhost:8088/files/create ``` ``` -{"id":"","fedWireMessage":{"id":"","senderSupplied":{"formatVersion":"30", ..... +{"id":"","fedWireMessages":[{"id":"","senderSupplied":{"formatVersion":"30", ..... ``` Get the file in its original format: diff --git a/examples/bankDrawDownRequest-read-skip-imad/main.go b/examples/bankDrawDownRequest-read-skip-imad/main.go index 3dd7bbba..6f2c20da 100644 --- a/examples/bankDrawDownRequest-read-skip-imad/main.go +++ b/examples/bankDrawDownRequest-read-skip-imad/main.go @@ -35,15 +35,15 @@ func main() { log.Fatalf("Could not validate FEDWireMessage: %s\n", err) } - if fwmFile.FEDWireMessage.InputMessageAccountabilityData != nil { + if fwmFile.FEDWireMessages[0].InputMessageAccountabilityData != nil { log.Fatalf("IMAD doesn't existed in FEDWireMessage") } - fmt.Printf("Sender Supplied: %v \n", fwmFile.FEDWireMessage.SenderSupplied) - fmt.Printf("Type and Subtype: %v \n", fwmFile.FEDWireMessage.TypeSubType) - fmt.Printf("Input Message Accountability Data: %v \n", fwmFile.FEDWireMessage.InputMessageAccountabilityData) - fmt.Printf("Amount: %v \n", fwmFile.FEDWireMessage.Amount) - fmt.Printf("Sender Depository Institution: %v \n", fwmFile.FEDWireMessage.SenderDepositoryInstitution) - fmt.Printf("Receiver Depository Institution: %v \n", fwmFile.FEDWireMessage.ReceiverDepositoryInstitution) - fmt.Printf("Business Function Code: %v \n", fwmFile.FEDWireMessage.BusinessFunctionCode) + fmt.Printf("Sender Supplied: %v \n", fwmFile.FEDWireMessages[0].SenderSupplied) + fmt.Printf("Type and Subtype: %v \n", fwmFile.FEDWireMessages[0].TypeSubType) + fmt.Printf("Input Message Accountability Data: %v \n", fwmFile.FEDWireMessages[0].InputMessageAccountabilityData) + fmt.Printf("Amount: %v \n", fwmFile.FEDWireMessages[0].Amount) + fmt.Printf("Sender Depository Institution: %v \n", fwmFile.FEDWireMessages[0].SenderDepositoryInstitution) + fmt.Printf("Receiver Depository Institution: %v \n", fwmFile.FEDWireMessages[0].ReceiverDepositoryInstitution) + fmt.Printf("Business Function Code: %v \n", fwmFile.FEDWireMessages[0].BusinessFunctionCode) } diff --git a/examples/bankDrawDownRequest-read/main.go b/examples/bankDrawDownRequest-read/main.go index 868427b6..ef6b6464 100644 --- a/examples/bankDrawDownRequest-read/main.go +++ b/examples/bankDrawDownRequest-read/main.go @@ -31,11 +31,11 @@ func main() { log.Fatalf("Could not validate FEDWireMessage: %s\n", err) } - fmt.Printf("Sender Supplied: %v \n", fwmFile.FEDWireMessage.SenderSupplied) - fmt.Printf("Type and Subtype: %v \n", fwmFile.FEDWireMessage.TypeSubType) - fmt.Printf("Input Message Accountability Data: %v \n", fwmFile.FEDWireMessage.InputMessageAccountabilityData) - fmt.Printf("Amount: %v \n", fwmFile.FEDWireMessage.Amount) - fmt.Printf("Sender Depository Institution: %v \n", fwmFile.FEDWireMessage.SenderDepositoryInstitution) - fmt.Printf("Receiver Depository Institution: %v \n", fwmFile.FEDWireMessage.ReceiverDepositoryInstitution) - fmt.Printf("Business Function Code: %v \n", fwmFile.FEDWireMessage.BusinessFunctionCode) + fmt.Printf("Sender Supplied: %v \n", fwmFile.FEDWireMessages[0].SenderSupplied) + fmt.Printf("Type and Subtype: %v \n", fwmFile.FEDWireMessages[0].TypeSubType) + fmt.Printf("Input Message Accountability Data: %v \n", fwmFile.FEDWireMessages[0].InputMessageAccountabilityData) + fmt.Printf("Amount: %v \n", fwmFile.FEDWireMessages[0].Amount) + fmt.Printf("Sender Depository Institution: %v \n", fwmFile.FEDWireMessages[0].SenderDepositoryInstitution) + fmt.Printf("Receiver Depository Institution: %v \n", fwmFile.FEDWireMessages[0].ReceiverDepositoryInstitution) + fmt.Printf("Business Function Code: %v \n", fwmFile.FEDWireMessages[0].BusinessFunctionCode) } diff --git a/examples/bankDrawDownRequest-write/main.go b/examples/bankDrawDownRequest-write/main.go index 5e81de94..3ca187e1 100644 --- a/examples/bankDrawDownRequest-write/main.go +++ b/examples/bankDrawDownRequest-write/main.go @@ -224,9 +224,6 @@ func main() { file.AddFEDWireMessage(fwm) - if err := file.Create(); err != nil { - log.Fatalf("Could not create FEDWireMessage: %s\n", err) - } if err := file.Validate(); err != nil { log.Fatalf("Could not validate FEDWireMessage: %s\n", err) } diff --git a/examples/bankTransfer-read/main.go b/examples/bankTransfer-read/main.go index bd70669f..1e299649 100644 --- a/examples/bankTransfer-read/main.go +++ b/examples/bankTransfer-read/main.go @@ -31,11 +31,11 @@ func main() { log.Fatalf("Could not validate FEDWireMessage: %s\n", err) } - fmt.Printf("Sender Supplied: %v \n", fwmFile.FEDWireMessage.SenderSupplied) - fmt.Printf("Type and Subtype: %v \n", fwmFile.FEDWireMessage.TypeSubType) - fmt.Printf("Input Message Accountability Data: %v \n", fwmFile.FEDWireMessage.InputMessageAccountabilityData) - fmt.Printf("Amount: %v \n", fwmFile.FEDWireMessage.Amount) - fmt.Printf("Sender Depository Institution: %v \n", fwmFile.FEDWireMessage.SenderDepositoryInstitution) - fmt.Printf("Receiver Depository Institution: %v \n", fwmFile.FEDWireMessage.ReceiverDepositoryInstitution) - fmt.Printf("Business Function Code: %v \n", fwmFile.FEDWireMessage.BusinessFunctionCode) + fmt.Printf("Sender Supplied: %v \n", fwmFile.FEDWireMessages[0].SenderSupplied) + fmt.Printf("Type and Subtype: %v \n", fwmFile.FEDWireMessages[0].TypeSubType) + fmt.Printf("Input Message Accountability Data: %v \n", fwmFile.FEDWireMessages[0].InputMessageAccountabilityData) + fmt.Printf("Amount: %v \n", fwmFile.FEDWireMessages[0].Amount) + fmt.Printf("Sender Depository Institution: %v \n", fwmFile.FEDWireMessages[0].SenderDepositoryInstitution) + fmt.Printf("Receiver Depository Institution: %v \n", fwmFile.FEDWireMessages[0].ReceiverDepositoryInstitution) + fmt.Printf("Business Function Code: %v \n", fwmFile.FEDWireMessages[0].BusinessFunctionCode) } diff --git a/examples/bankTransfer-write/main.go b/examples/bankTransfer-write/main.go index e2f933f6..55ea4926 100644 --- a/examples/bankTransfer-write/main.go +++ b/examples/bankTransfer-write/main.go @@ -233,10 +233,6 @@ func main() { fwm.FIAdditionalFIToFI = fifi file.AddFEDWireMessage(fwm) - - if err := file.Create(); err != nil { - log.Fatalf("Could not create FEDWireMessage: %s\n", err) - } if err := file.Validate(); err != nil { log.Fatalf("Could not validate FEDWireMessage: %s\n", err) } diff --git a/examples/checkSameDaySettlement-read/main.go b/examples/checkSameDaySettlement-read/main.go index 3f047b53..4f25b044 100644 --- a/examples/checkSameDaySettlement-read/main.go +++ b/examples/checkSameDaySettlement-read/main.go @@ -31,11 +31,11 @@ func main() { log.Fatalf("Could not validate FEDWireMessage: %s\n", err) } - fmt.Printf("Sender Supplied: %v \n", fwmFile.FEDWireMessage.SenderSupplied) - fmt.Printf("Type and Subtype: %v \n", fwmFile.FEDWireMessage.TypeSubType) - fmt.Printf("Input Message Accountability Data: %v \n", fwmFile.FEDWireMessage.InputMessageAccountabilityData) - fmt.Printf("Amount: %v \n", fwmFile.FEDWireMessage.Amount) - fmt.Printf("Sender Depository Institution: %v \n", fwmFile.FEDWireMessage.SenderDepositoryInstitution) - fmt.Printf("Receiver Depository Institution: %v \n", fwmFile.FEDWireMessage.ReceiverDepositoryInstitution) - fmt.Printf("Business Function Code: %v \n", fwmFile.FEDWireMessage.BusinessFunctionCode) + fmt.Printf("Sender Supplied: %v \n", fwmFile.FEDWireMessages[0].SenderSupplied) + fmt.Printf("Type and Subtype: %v \n", fwmFile.FEDWireMessages[0].TypeSubType) + fmt.Printf("Input Message Accountability Data: %v \n", fwmFile.FEDWireMessages[0].InputMessageAccountabilityData) + fmt.Printf("Amount: %v \n", fwmFile.FEDWireMessages[0].Amount) + fmt.Printf("Sender Depository Institution: %v \n", fwmFile.FEDWireMessages[0].SenderDepositoryInstitution) + fmt.Printf("Receiver Depository Institution: %v \n", fwmFile.FEDWireMessages[0].ReceiverDepositoryInstitution) + fmt.Printf("Business Function Code: %v \n", fwmFile.FEDWireMessages[0].BusinessFunctionCode) } diff --git a/examples/checkSameDaySettlement-write/main.go b/examples/checkSameDaySettlement-write/main.go index 38847755..8e095f9f 100644 --- a/examples/checkSameDaySettlement-write/main.go +++ b/examples/checkSameDaySettlement-write/main.go @@ -211,9 +211,6 @@ func main() { file.AddFEDWireMessage(fwm) - if err := file.Create(); err != nil { - log.Fatalf("Could not create FEDWireMessage: %s\n", err) - } if err := file.Validate(); err != nil { log.Fatalf("Could not validate FEDWireMessage: %s\n", err) } diff --git a/examples/customerCorporateDrawDownRequest-read/main.go b/examples/customerCorporateDrawDownRequest-read/main.go index 8bf51d40..7ccc3e24 100644 --- a/examples/customerCorporateDrawDownRequest-read/main.go +++ b/examples/customerCorporateDrawDownRequest-read/main.go @@ -31,11 +31,11 @@ func main() { log.Fatalf("Could not validate FEDWireMessage: %s\n", err) } - fmt.Printf("Sender Supplied: %v \n", fwmFile.FEDWireMessage.SenderSupplied) - fmt.Printf("Type and Subtype: %v \n", fwmFile.FEDWireMessage.TypeSubType) - fmt.Printf("Input Message Accountability Data: %v \n", fwmFile.FEDWireMessage.InputMessageAccountabilityData) - fmt.Printf("Amount: %v \n", fwmFile.FEDWireMessage.Amount) - fmt.Printf("Sender Depository Institution: %v \n", fwmFile.FEDWireMessage.SenderDepositoryInstitution) - fmt.Printf("Receiver Depository Institution: %v \n", fwmFile.FEDWireMessage.ReceiverDepositoryInstitution) - fmt.Printf("Business Function Code: %v \n", fwmFile.FEDWireMessage.BusinessFunctionCode) + fmt.Printf("Sender Supplied: %v \n", fwmFile.FEDWireMessages[0].SenderSupplied) + fmt.Printf("Type and Subtype: %v \n", fwmFile.FEDWireMessages[0].TypeSubType) + fmt.Printf("Input Message Accountability Data: %v \n", fwmFile.FEDWireMessages[0].InputMessageAccountabilityData) + fmt.Printf("Amount: %v \n", fwmFile.FEDWireMessages[0].Amount) + fmt.Printf("Sender Depository Institution: %v \n", fwmFile.FEDWireMessages[0].SenderDepositoryInstitution) + fmt.Printf("Receiver Depository Institution: %v \n", fwmFile.FEDWireMessages[0].ReceiverDepositoryInstitution) + fmt.Printf("Business Function Code: %v \n", fwmFile.FEDWireMessages[0].BusinessFunctionCode) } diff --git a/examples/customerCorporateDrawDownRequest-write/main.go b/examples/customerCorporateDrawDownRequest-write/main.go index 14363ac9..c144d45c 100644 --- a/examples/customerCorporateDrawDownRequest-write/main.go +++ b/examples/customerCorporateDrawDownRequest-write/main.go @@ -225,9 +225,6 @@ func main() { file.AddFEDWireMessage(fwm) - if err := file.Create(); err != nil { - log.Fatalf("Could not create FEDWireMessage: %s\n", err) - } if err := file.Validate(); err != nil { log.Fatalf("Could not validate FEDWireMessage: %s\n", err) } diff --git a/examples/customerTransfer-read/main.go b/examples/customerTransfer-read/main.go index 11e284af..28697bae 100644 --- a/examples/customerTransfer-read/main.go +++ b/examples/customerTransfer-read/main.go @@ -31,11 +31,11 @@ func main() { log.Fatalf("Could not validate FEDWireMessage: %s\n", err) } - fmt.Printf("Sender Supplied: %v \n", fwmFile.FEDWireMessage.SenderSupplied) - fmt.Printf("Type and Subtype: %v \n", fwmFile.FEDWireMessage.TypeSubType) - fmt.Printf("Input Message Accountability Data: %v \n", fwmFile.FEDWireMessage.InputMessageAccountabilityData) - fmt.Printf("Amount: %v \n", fwmFile.FEDWireMessage.Amount) - fmt.Printf("Sender Depository Institution: %v \n", fwmFile.FEDWireMessage.SenderDepositoryInstitution) - fmt.Printf("Receiver Depository Institution: %v \n", fwmFile.FEDWireMessage.ReceiverDepositoryInstitution) - fmt.Printf("Business Function Code: %v \n", fwmFile.FEDWireMessage.BusinessFunctionCode) + fmt.Printf("Sender Supplied: %v \n", fwmFile.FEDWireMessages[0].SenderSupplied) + fmt.Printf("Type and Subtype: %v \n", fwmFile.FEDWireMessages[0].TypeSubType) + fmt.Printf("Input Message Accountability Data: %v \n", fwmFile.FEDWireMessages[0].InputMessageAccountabilityData) + fmt.Printf("Amount: %v \n", fwmFile.FEDWireMessages[0].Amount) + fmt.Printf("Sender Depository Institution: %v \n", fwmFile.FEDWireMessages[0].SenderDepositoryInstitution) + fmt.Printf("Receiver Depository Institution: %v \n", fwmFile.FEDWireMessages[0].ReceiverDepositoryInstitution) + fmt.Printf("Business Function Code: %v \n", fwmFile.FEDWireMessages[0].BusinessFunctionCode) } diff --git a/examples/customerTransfer-write/main.go b/examples/customerTransfer-write/main.go index 167c03ba..5e4d38ff 100644 --- a/examples/customerTransfer-write/main.go +++ b/examples/customerTransfer-write/main.go @@ -228,9 +228,6 @@ func main() { file.AddFEDWireMessage(fwm) - if err := file.Create(); err != nil { - log.Fatalf("Could not create FEDWireMessage: %s\n", err) - } if err := file.Validate(); err != nil { log.Fatalf("Could not validate FEDWireMessage: %s\n", err) } diff --git a/examples/customerTransferPlus-read/main.go b/examples/customerTransferPlus-read/main.go index 46436db5..7af00639 100644 --- a/examples/customerTransferPlus-read/main.go +++ b/examples/customerTransferPlus-read/main.go @@ -31,11 +31,11 @@ func main() { log.Fatalf("Could not validate FEDWireMessage: %s\n", err) } - fmt.Printf("Sender Supplied: %v \n", fwmFile.FEDWireMessage.SenderSupplied) - fmt.Printf("Type and Subtype: %v \n", fwmFile.FEDWireMessage.TypeSubType) - fmt.Printf("Input Message Accountability Data: %v \n", fwmFile.FEDWireMessage.InputMessageAccountabilityData) - fmt.Printf("Amount: %v \n", fwmFile.FEDWireMessage.Amount) - fmt.Printf("Sender Depository Institution: %v \n", fwmFile.FEDWireMessage.SenderDepositoryInstitution) - fmt.Printf("Receiver Depository Institution: %v \n", fwmFile.FEDWireMessage.ReceiverDepositoryInstitution) - fmt.Printf("Business Function Code: %v \n", fwmFile.FEDWireMessage.BusinessFunctionCode) + fmt.Printf("Sender Supplied: %v \n", fwmFile.FEDWireMessages[0].SenderSupplied) + fmt.Printf("Type and Subtype: %v \n", fwmFile.FEDWireMessages[0].TypeSubType) + fmt.Printf("Input Message Accountability Data: %v \n", fwmFile.FEDWireMessages[0].InputMessageAccountabilityData) + fmt.Printf("Amount: %v \n", fwmFile.FEDWireMessages[0].Amount) + fmt.Printf("Sender Depository Institution: %v \n", fwmFile.FEDWireMessages[0].SenderDepositoryInstitution) + fmt.Printf("Receiver Depository Institution: %v \n", fwmFile.FEDWireMessages[0].ReceiverDepositoryInstitution) + fmt.Printf("Business Function Code: %v \n", fwmFile.FEDWireMessages[0].BusinessFunctionCode) } diff --git a/examples/customerTransferPlus-write/main.go b/examples/customerTransferPlus-write/main.go index 34cccb1c..472f5aaa 100644 --- a/examples/customerTransferPlus-write/main.go +++ b/examples/customerTransferPlus-write/main.go @@ -256,9 +256,6 @@ func main() { file.AddFEDWireMessage(fwm) - if err := file.Create(); err != nil { - log.Fatalf("Could not create FEDWireMessage: %s\n", err) - } if err := file.Validate(); err != nil { log.Fatalf("Could not validate FEDWireMessage: %s\n", err) } diff --git a/examples/customerTransferPlusCOVS-read/main.go b/examples/customerTransferPlusCOVS-read/main.go index 1f2af392..d6db993e 100644 --- a/examples/customerTransferPlusCOVS-read/main.go +++ b/examples/customerTransferPlusCOVS-read/main.go @@ -31,11 +31,11 @@ func main() { log.Fatalf("Could not validate FEDWireMessage: %s\n", err) } - fmt.Printf("Sender Supplied: %v \n", fwmFile.FEDWireMessage.SenderSupplied) - fmt.Printf("Type and Subtype: %v \n", fwmFile.FEDWireMessage.TypeSubType) - fmt.Printf("Input Message Accountability Data: %v \n", fwmFile.FEDWireMessage.InputMessageAccountabilityData) - fmt.Printf("Amount: %v \n", fwmFile.FEDWireMessage.Amount) - fmt.Printf("Sender Depository Institution: %v \n", fwmFile.FEDWireMessage.SenderDepositoryInstitution) - fmt.Printf("Receiver Depository Institution: %v \n", fwmFile.FEDWireMessage.ReceiverDepositoryInstitution) - fmt.Printf("Business Function Code: %v \n", fwmFile.FEDWireMessage.BusinessFunctionCode) + fmt.Printf("Sender Supplied: %v \n", fwmFile.FEDWireMessages[0].SenderSupplied) + fmt.Printf("Type and Subtype: %v \n", fwmFile.FEDWireMessages[0].TypeSubType) + fmt.Printf("Input Message Accountability Data: %v \n", fwmFile.FEDWireMessages[0].InputMessageAccountabilityData) + fmt.Printf("Amount: %v \n", fwmFile.FEDWireMessages[0].Amount) + fmt.Printf("Sender Depository Institution: %v \n", fwmFile.FEDWireMessages[0].SenderDepositoryInstitution) + fmt.Printf("Receiver Depository Institution: %v \n", fwmFile.FEDWireMessages[0].ReceiverDepositoryInstitution) + fmt.Printf("Business Function Code: %v \n", fwmFile.FEDWireMessages[0].BusinessFunctionCode) } diff --git a/examples/customerTransferPlusCOVS-write/main.go b/examples/customerTransferPlusCOVS-write/main.go index 05c02da6..29a663a9 100644 --- a/examples/customerTransferPlusCOVS-write/main.go +++ b/examples/customerTransferPlusCOVS-write/main.go @@ -285,9 +285,6 @@ func main() { file.AddFEDWireMessage(fwm) - if err := file.Create(); err != nil { - log.Fatalf("Could not create FEDWireMessage: %s\n", err) - } if err := file.Validate(); err != nil { log.Fatalf("Could not validate FEDWireMessage: %s\n", err) } diff --git a/examples/depositSendersAccount-read/main.go b/examples/depositSendersAccount-read/main.go index a4198c38..dac32796 100644 --- a/examples/depositSendersAccount-read/main.go +++ b/examples/depositSendersAccount-read/main.go @@ -31,11 +31,11 @@ func main() { log.Fatalf("Could not validate FEDWireMessage: %s\n", err) } - fmt.Printf("Sender Supplied: %v \n", fwmFile.FEDWireMessage.SenderSupplied) - fmt.Printf("Type and Subtype: %v \n", fwmFile.FEDWireMessage.TypeSubType) - fmt.Printf("Input Message Accountability Data: %v \n", fwmFile.FEDWireMessage.InputMessageAccountabilityData) - fmt.Printf("Amount: %v \n", fwmFile.FEDWireMessage.Amount) - fmt.Printf("Sender Depository Institution: %v \n", fwmFile.FEDWireMessage.SenderDepositoryInstitution) - fmt.Printf("Receiver Depository Institution: %v \n", fwmFile.FEDWireMessage.ReceiverDepositoryInstitution) - fmt.Printf("Business Function Code: %v \n", fwmFile.FEDWireMessage.BusinessFunctionCode) + fmt.Printf("Sender Supplied: %v \n", fwmFile.FEDWireMessages[0].SenderSupplied) + fmt.Printf("Type and Subtype: %v \n", fwmFile.FEDWireMessages[0].TypeSubType) + fmt.Printf("Input Message Accountability Data: %v \n", fwmFile.FEDWireMessages[0].InputMessageAccountabilityData) + fmt.Printf("Amount: %v \n", fwmFile.FEDWireMessages[0].Amount) + fmt.Printf("Sender Depository Institution: %v \n", fwmFile.FEDWireMessages[0].SenderDepositoryInstitution) + fmt.Printf("Receiver Depository Institution: %v \n", fwmFile.FEDWireMessages[0].ReceiverDepositoryInstitution) + fmt.Printf("Business Function Code: %v \n", fwmFile.FEDWireMessages[0].BusinessFunctionCode) } diff --git a/examples/depositSendersAccount-write/main.go b/examples/depositSendersAccount-write/main.go index a8a15bd0..7581120a 100644 --- a/examples/depositSendersAccount-write/main.go +++ b/examples/depositSendersAccount-write/main.go @@ -232,9 +232,6 @@ func main() { file.AddFEDWireMessage(fwm) - if err := file.Create(); err != nil { - log.Fatalf("Could not create FEDWireMessage: %s\n", err) - } if err := file.Validate(); err != nil { log.Fatalf("Could not validate FEDWireMessage: %s\n", err) } diff --git a/examples/drawDownRequest-read/main.go b/examples/drawDownRequest-read/main.go index 9ea3ae05..a6bc2aa0 100644 --- a/examples/drawDownRequest-read/main.go +++ b/examples/drawDownRequest-read/main.go @@ -31,11 +31,11 @@ func main() { log.Fatalf("Could not validate FEDWireMessage: %s\n", err) } - fmt.Printf("Sender Supplied: %v \n", fwmFile.FEDWireMessage.SenderSupplied) - fmt.Printf("Type and Subtype: %v \n", fwmFile.FEDWireMessage.TypeSubType) - fmt.Printf("Input Message Accountability Data: %v \n", fwmFile.FEDWireMessage.InputMessageAccountabilityData) - fmt.Printf("Amount: %v \n", fwmFile.FEDWireMessage.Amount) - fmt.Printf("Sender Depository Institution: %v \n", fwmFile.FEDWireMessage.SenderDepositoryInstitution) - fmt.Printf("Receiver Depository Institution: %v \n", fwmFile.FEDWireMessage.ReceiverDepositoryInstitution) - fmt.Printf("Business Function Code: %v \n", fwmFile.FEDWireMessage.BusinessFunctionCode) + fmt.Printf("Sender Supplied: %v \n", fwmFile.FEDWireMessages[0].SenderSupplied) + fmt.Printf("Type and Subtype: %v \n", fwmFile.FEDWireMessages[0].TypeSubType) + fmt.Printf("Input Message Accountability Data: %v \n", fwmFile.FEDWireMessages[0].InputMessageAccountabilityData) + fmt.Printf("Amount: %v \n", fwmFile.FEDWireMessages[0].Amount) + fmt.Printf("Sender Depository Institution: %v \n", fwmFile.FEDWireMessages[0].SenderDepositoryInstitution) + fmt.Printf("Receiver Depository Institution: %v \n", fwmFile.FEDWireMessages[0].ReceiverDepositoryInstitution) + fmt.Printf("Business Function Code: %v \n", fwmFile.FEDWireMessages[0].BusinessFunctionCode) } diff --git a/examples/drawDownRequest-write/main.go b/examples/drawDownRequest-write/main.go index cd0b56ab..0aacf894 100644 --- a/examples/drawDownRequest-write/main.go +++ b/examples/drawDownRequest-write/main.go @@ -224,9 +224,6 @@ func main() { file.AddFEDWireMessage(fwm) - if err := file.Create(); err != nil { - log.Fatalf("Could not create FEDWireMessage: %s\n", err) - } if err := file.Validate(); err != nil { log.Fatalf("Could not validate FEDWireMessage: %s\n", err) } diff --git a/examples/fedFundsReturned-read/main.go b/examples/fedFundsReturned-read/main.go index 6e8ac2c5..57cd79dc 100644 --- a/examples/fedFundsReturned-read/main.go +++ b/examples/fedFundsReturned-read/main.go @@ -31,11 +31,11 @@ func main() { log.Fatalf("Could not validate FEDWireMessage: %s\n", err) } - fmt.Printf("Sender Supplied: %v \n", fwmFile.FEDWireMessage.SenderSupplied) - fmt.Printf("Type and Subtype: %v \n", fwmFile.FEDWireMessage.TypeSubType) - fmt.Printf("Input Message Accountability Data: %v \n", fwmFile.FEDWireMessage.InputMessageAccountabilityData) - fmt.Printf("Amount: %v \n", fwmFile.FEDWireMessage.Amount) - fmt.Printf("Sender Depository Institution: %v \n", fwmFile.FEDWireMessage.SenderDepositoryInstitution) - fmt.Printf("Receiver Depository Institution: %v \n", fwmFile.FEDWireMessage.ReceiverDepositoryInstitution) - fmt.Printf("Business Function Code: %v \n", fwmFile.FEDWireMessage.BusinessFunctionCode) + fmt.Printf("Sender Supplied: %v \n", fwmFile.FEDWireMessages[0].SenderSupplied) + fmt.Printf("Type and Subtype: %v \n", fwmFile.FEDWireMessages[0].TypeSubType) + fmt.Printf("Input Message Accountability Data: %v \n", fwmFile.FEDWireMessages[0].InputMessageAccountabilityData) + fmt.Printf("Amount: %v \n", fwmFile.FEDWireMessages[0].Amount) + fmt.Printf("Sender Depository Institution: %v \n", fwmFile.FEDWireMessages[0].SenderDepositoryInstitution) + fmt.Printf("Receiver Depository Institution: %v \n", fwmFile.FEDWireMessages[0].ReceiverDepositoryInstitution) + fmt.Printf("Business Function Code: %v \n", fwmFile.FEDWireMessages[0].BusinessFunctionCode) } diff --git a/examples/fedFundsReturned-write/main.go b/examples/fedFundsReturned-write/main.go index f7671998..73990d26 100644 --- a/examples/fedFundsReturned-write/main.go +++ b/examples/fedFundsReturned-write/main.go @@ -233,9 +233,6 @@ func main() { file.AddFEDWireMessage(fwm) - if err := file.Create(); err != nil { - log.Fatalf("Could not create FEDWireMessage: %s\n", err) - } if err := file.Validate(); err != nil { log.Fatalf("Could not validate FEDWireMessage: %s\n", err) } diff --git a/examples/fedFundsSold-read/main.go b/examples/fedFundsSold-read/main.go index 0126900f..2e125498 100644 --- a/examples/fedFundsSold-read/main.go +++ b/examples/fedFundsSold-read/main.go @@ -31,11 +31,11 @@ func main() { log.Fatalf("Could not validate FEDWireMessage: %s\n", err) } - fmt.Printf("Sender Supplied: %v \n", fwmFile.FEDWireMessage.SenderSupplied) - fmt.Printf("Type and Subtype: %v \n", fwmFile.FEDWireMessage.TypeSubType) - fmt.Printf("Input Message Accountability Data: %v \n", fwmFile.FEDWireMessage.InputMessageAccountabilityData) - fmt.Printf("Amount: %v \n", fwmFile.FEDWireMessage.Amount) - fmt.Printf("Sender Depository Institution: %v \n", fwmFile.FEDWireMessage.SenderDepositoryInstitution) - fmt.Printf("Receiver Depository Institution: %v \n", fwmFile.FEDWireMessage.ReceiverDepositoryInstitution) - fmt.Printf("Business Function Code: %v \n", fwmFile.FEDWireMessage.BusinessFunctionCode) + fmt.Printf("Sender Supplied: %v \n", fwmFile.FEDWireMessages[0].SenderSupplied) + fmt.Printf("Type and Subtype: %v \n", fwmFile.FEDWireMessages[0].TypeSubType) + fmt.Printf("Input Message Accountability Data: %v \n", fwmFile.FEDWireMessages[0].InputMessageAccountabilityData) + fmt.Printf("Amount: %v \n", fwmFile.FEDWireMessages[0].Amount) + fmt.Printf("Sender Depository Institution: %v \n", fwmFile.FEDWireMessages[0].SenderDepositoryInstitution) + fmt.Printf("Receiver Depository Institution: %v \n", fwmFile.FEDWireMessages[0].ReceiverDepositoryInstitution) + fmt.Printf("Business Function Code: %v \n", fwmFile.FEDWireMessages[0].BusinessFunctionCode) } diff --git a/examples/fedFundsSold-write/main.go b/examples/fedFundsSold-write/main.go index 1029088d..fb74306d 100644 --- a/examples/fedFundsSold-write/main.go +++ b/examples/fedFundsSold-write/main.go @@ -234,9 +234,6 @@ func main() { file.AddFEDWireMessage(fwm) - if err := file.Create(); err != nil { - log.Fatalf("Could not create FEDWireMessage: %s\n", err) - } if err := file.Validate(); err != nil { log.Fatalf("Could not validate FEDWireMessage: %s\n", err) } diff --git a/examples/serviceMessage-read/main.go b/examples/serviceMessage-read/main.go index d96bf433..95daa0db 100644 --- a/examples/serviceMessage-read/main.go +++ b/examples/serviceMessage-read/main.go @@ -31,11 +31,11 @@ func main() { log.Fatalf("Could not validate FEDWireMessage: %s\n", err) } - fmt.Printf("Sender Supplied: %v \n", fwmFile.FEDWireMessage.SenderSupplied) - fmt.Printf("Type and Subtype: %v \n", fwmFile.FEDWireMessage.TypeSubType) - fmt.Printf("Input Message Accountability Data: %v \n", fwmFile.FEDWireMessage.InputMessageAccountabilityData) - fmt.Printf("Amount: %v \n", fwmFile.FEDWireMessage.Amount) - fmt.Printf("Sender Depository Institution: %v \n", fwmFile.FEDWireMessage.SenderDepositoryInstitution) - fmt.Printf("Receiver Depository Institution: %v \n", fwmFile.FEDWireMessage.ReceiverDepositoryInstitution) - fmt.Printf("Business Function Code: %v \n", fwmFile.FEDWireMessage.BusinessFunctionCode) + fmt.Printf("Sender Supplied: %v \n", fwmFile.FEDWireMessages[0].SenderSupplied) + fmt.Printf("Type and Subtype: %v \n", fwmFile.FEDWireMessages[0].TypeSubType) + fmt.Printf("Input Message Accountability Data: %v \n", fwmFile.FEDWireMessages[0].InputMessageAccountabilityData) + fmt.Printf("Amount: %v \n", fwmFile.FEDWireMessages[0].Amount) + fmt.Printf("Sender Depository Institution: %v \n", fwmFile.FEDWireMessages[0].SenderDepositoryInstitution) + fmt.Printf("Receiver Depository Institution: %v \n", fwmFile.FEDWireMessages[0].ReceiverDepositoryInstitution) + fmt.Printf("Business Function Code: %v \n", fwmFile.FEDWireMessages[0].BusinessFunctionCode) } diff --git a/examples/serviceMessage-write/main.go b/examples/serviceMessage-write/main.go index 1e843f84..d8d1656c 100644 --- a/examples/serviceMessage-write/main.go +++ b/examples/serviceMessage-write/main.go @@ -236,9 +236,6 @@ func main() { file.AddFEDWireMessage(fwm) - if err := file.Create(); err != nil { - log.Fatalf("Could not create FEDWireMessage: %s\n", err) - } if err := file.Validate(); err != nil { log.Fatalf("Could not validate FEDWireMessage: %s\n", err) } diff --git a/fedWireMessage.go b/fedWireMessage.go index 10f9bee0..ca9b147a 100644 --- a/fedWireMessage.go +++ b/fedWireMessage.go @@ -131,23 +131,13 @@ type FEDWireMessage struct { RemittanceFreeText *RemittanceFreeText `json:"remittanceFreeText,omitempty"` // ServiceMessage ServiceMessage *ServiceMessage `json:"serviceMessage,omitempty"` - // ValidateOpts - ValidateOptions *ValidateOpts `json:"validateOptions,omitempty"` -} - -func (fwm *FEDWireMessage) requireSenderSupplied() bool { - opts := &ValidateOpts{} - if fwm != nil && fwm.ValidateOptions != nil { - opts = fwm.ValidateOptions - } - return !opts.AllowMissingSenderSupplied } // verify checks basic WIRE rules. Assumes properly parsed records. Each validation func should // check for the expected relationships between fields within a FedWireMessage. -func (fwm *FEDWireMessage) verify() error { +func (fwm *FEDWireMessage) verifyWithOpts(opts ValidateOpts) error { - if err := fwm.mandatoryFields(); err != nil { + if err := fwm.mandatoryFields(opts); err != nil { return err } @@ -220,8 +210,8 @@ func (fwm *FEDWireMessage) verify() error { // // NOTE: Not specified mandatory elements in each incoming message // Need to specify mandatory elements in this case -func (fwm *FEDWireMessage) mandatoryFields() error { - if fwm.requireSenderSupplied() { +func (fwm *FEDWireMessage) mandatoryFields(opts ValidateOpts) error { + if !opts.AllowMissingSenderSupplied { if err := fwm.validateSenderSupplied(); err != nil { return err } @@ -230,7 +220,7 @@ func (fwm *FEDWireMessage) mandatoryFields() error { return err } - if fwm.ValidateOptions == nil || !fwm.ValidateOptions.SkipMandatoryIMAD { + if !opts.SkipMandatoryIMAD { if err := fwm.validateIMAD(); err != nil { return err } diff --git a/fedWiremessage_test.go b/fedWiremessage_test.go index f57f62b1..bfa3df16 100644 --- a/fedWiremessage_test.go +++ b/fedWiremessage_test.go @@ -35,10 +35,6 @@ func TestFEDWireMessage_invalidAmount(t *testing.T) { // Originator fwm.Originator = mockOriginator() file.AddFEDWireMessage(fwm) - // Create file - if err := file.Create(); err != nil { - t.Fatalf("%T: %s", err, err) - } // Validate File err := file.Validate() @@ -1267,21 +1263,17 @@ func TestFEDWireMessage_skipIMAD(t *testing.T) { // Originator fwm.Originator = mockOriginator() file.AddFEDWireMessage(fwm) - // Create file - if err := file.Create(); err != nil { - t.Fatalf("%T: %s", err, err) - } err := file.Validate() require.NoError(t, err) - file.FEDWireMessage.InputMessageAccountabilityData = nil + file.FEDWireMessages[0].InputMessageAccountabilityData = nil err = file.Validate() expected := fieldError("InputMessageAccountabilityData", ErrFieldRequired).Error() require.EqualError(t, err, expected) - file.SetValidation(&ValidateOpts{SkipMandatoryIMAD: true}) + file.ValidateOptions = ValidateOpts{SkipMandatoryIMAD: true} err = file.Validate() require.NoError(t, err) @@ -1291,9 +1283,9 @@ func TestFEDWireMessage_skipIMAD(t *testing.T) { newFile, err := FileFromJSON(bs) require.NoError(t, err) require.NotNil(t, newFile, "Created file shouldn't be nil") - require.Nil(t, newFile.FEDWireMessage.InputMessageAccountabilityData) + require.Nil(t, newFile.FEDWireMessages[0].InputMessageAccountabilityData) err = newFile.Validate() require.NoError(t, err) - require.Equal(t, true, newFile.GetValidation().SkipMandatoryIMAD) + require.Equal(t, true, newFile.ValidateOptions.SkipMandatoryIMAD) } diff --git a/file.go b/file.go index 3bc9178d..63c8dfd6 100644 --- a/file.go +++ b/file.go @@ -12,8 +12,9 @@ import ( // File contains the structures of a parsed WIRE File. type File struct { - ID string `json:"id"` - FEDWireMessage FEDWireMessage `json:"fedWireMessage"` + ID string `json:"id"` + FEDWireMessages []FEDWireMessage `json:"fedWireMessages"` + ValidateOptions ValidateOpts `json:"validateOptions,omitempty"` } // NewFile constructs a file template @@ -27,41 +28,29 @@ func NewFile(opts ...FilePropertyFunc) *File { return f } -// SetValidation stores ValidateOpts on the FEDWireMessage's validation rules -func (f *File) SetValidation(opts *ValidateOpts) { - if f == nil || opts == nil { - return +// AddFEDWireMessage appends a FEDWireMessage to the File +func (f *File) AddFEDWireMessage(fwm FEDWireMessage) { + if f != nil { + f.FEDWireMessages = append(f.FEDWireMessages, fwm) } - f.FEDWireMessage.ValidateOptions = opts } -// GetValidation returns validation rules of FEDWireMessage -func (f *File) GetValidation() *ValidateOpts { - if f == nil || f.FEDWireMessage.ValidateOptions == nil { +// Validate will never modify the file. +func (f *File) Validate() error { + if f == nil { return nil } - return f.FEDWireMessage.ValidateOptions -} -// AddFEDWireMessage appends a FEDWireMessage to the File -func (f *File) AddFEDWireMessage(fwm FEDWireMessage) FEDWireMessage { - f.FEDWireMessage = fwm - return f.FEDWireMessage -} - -// Create will tabulate and assemble an WIRE file into a valid state. -// -// Create implementations are free to modify computable fields in a file and should -// call the Validate() function at the end of their execution. -func (f *File) Create() error { - return nil -} + if len(f.FEDWireMessages) == 0 { + return fmt.Errorf("no FEDWireMessages") + } -// Validate will never modify the file. -func (f *File) Validate() error { - if err := f.FEDWireMessage.verify(); err != nil { - return err + for i := range f.FEDWireMessages { + if err := f.FEDWireMessages[i].verifyWithOpts(f.ValidateOptions); err != nil { + return err + } } + return nil } @@ -90,10 +79,7 @@ type FilePropertyFunc func(*File) func OutgoingFile() FilePropertyFunc { return func(f *File) { if f != nil { - if f.FEDWireMessage.ValidateOptions == nil { - f.FEDWireMessage.ValidateOptions = &ValidateOpts{} - } - f.FEDWireMessage.ValidateOptions.AllowMissingSenderSupplied = false + f.ValidateOptions.AllowMissingSenderSupplied = false } } } @@ -102,10 +88,7 @@ func OutgoingFile() FilePropertyFunc { func IncomingFile() FilePropertyFunc { return func(f *File) { if f != nil { - if f.FEDWireMessage.ValidateOptions == nil { - f.FEDWireMessage.ValidateOptions = &ValidateOpts{} - } - f.FEDWireMessage.ValidateOptions.AllowMissingSenderSupplied = true + f.ValidateOptions.AllowMissingSenderSupplied = true } } } diff --git a/file_test.go b/file_test.go index c18025ec..d1b83980 100644 --- a/file_test.go +++ b/file_test.go @@ -5,6 +5,7 @@ package wire import ( + "bytes" "os" "path/filepath" "testing" @@ -14,16 +15,57 @@ import ( func TestFile__FileFromJSON(t *testing.T) { bs, err := os.ReadFile(filepath.Join("test", "testdata", "fedWireMessage-BankTransfer.json")) - if err != nil { - t.Fatal(err) - } - if len(bs) == 0 { - t.Fatal("no bytes read") - } + require.NoError(t, err) file, err := FileFromJSON(bs) - require.NoError(t, err) require.Empty(t, file.ID, "id should not have been set") - require.NotNil(t, file.FEDWireMessage.FIAdditionalFIToFI, "FIAdditionalFIToFI shouldn't be nil") + require.NotNil(t, file.FEDWireMessages[0].FIAdditionalFIToFI, "FIAdditionalFIToFI shouldn't be nil") +} + +func TestFile_readAndWriteBatch(t *testing.T) { + bs, err := os.ReadFile(filepath.Join("test", "testdata", "batchFile.txt")) + require.NoError(t, err) + + // Read the file in + file, err := NewReader(bytes.NewReader(bs)).Read() + require.NoError(t, err) + + require.Equal(t, 2, len(file.FEDWireMessages), "expected 2 FEDWireMessages") + require.Equal(t, "BTR", file.FEDWireMessages[0].BusinessFunctionCode.BusinessFunctionCode) + require.Equal(t, "CTR", file.FEDWireMessages[1].BusinessFunctionCode.BusinessFunctionCode) + + // Write the file back out + var buf bytes.Buffer + wOpts := []OptionFunc{ + VariableLengthFields(true), + NewlineCharacter(""), + MessageDelimiter("\n"), + } + require.NoError(t, NewWriter(&buf, wOpts...).Write(file)) + require.Equal(t, string(bs), buf.String()) +} + +func TestFile_readAndWriteBatch_incomingWithNewlines(t *testing.T) { + bs, err := os.ReadFile(filepath.Join("test", "testdata", "batchFile_incomingWithNewlines.txt")) + require.NoError(t, err) + + // Read the file in + file, err := NewReader(bytes.NewReader(bs), IncomingFile()).Read() + require.NoError(t, err) + + require.Equal(t, 3, len(file.FEDWireMessages), "expected 3 FEDWireMessages") + require.Equal(t, "000000000001", file.FEDWireMessages[0].Amount.Amount) + require.Equal(t, "000000000002", file.FEDWireMessages[1].Amount.Amount) + require.Equal(t, "000000000003", file.FEDWireMessages[2].Amount.Amount) + + // Write the file back out + var buf bytes.Buffer + wOpts := []OptionFunc{ + VariableLengthFields(true), + NewlineCharacter("\n"), + MessageDelimiter("\n"), + } + require.NoError(t, NewWriter(&buf, wOpts...).Write(file)) + require.Equal(t, string(bs), buf.String()) } diff --git a/format_options.go b/format_options.go index 2034880f..ab33548d 100644 --- a/format_options.go +++ b/format_options.go @@ -4,4 +4,5 @@ package wire type FormatOptions struct { VariableLengthFields bool // set to true to use variable length fields instead of fixed-width NewlineCharacter string // determines line endings - "\n" by default + MessageDelimiter string // separates messages within a file - "\n" by default } diff --git a/issue104_test.go b/issue104_test.go index 96866a0a..3e55f47f 100644 --- a/issue104_test.go +++ b/issue104_test.go @@ -10,41 +10,33 @@ import ( "path/filepath" "strings" "testing" + + "github.com/stretchr/testify/require" ) func TestIssue104(t *testing.T) { bs, err := os.ReadFile(filepath.Join("test", "testdata", "fedWireMessage-BankTransfer.json")) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) file, err := FileFromJSON(bs) - if err != nil { - t.Fatal(err) - } - if file == nil { - t.Fatal("nil File") - } + require.NoError(t, err) + require.NotNil(t, file) var buf bytes.Buffer - if err := NewWriter(&buf).Write(file); err != nil { - t.Fatal(err) - } + require.NoError(t, NewWriter(&buf).Write(file)) // verify the output lines := strings.Split(buf.String(), "\n") - if n := len(lines); n != 27 { - t.Errorf("got %d lines", n) - } + require.Len(t, lines, 26) + for i := range lines { if lines[i] == "" { continue } - prefix := string(lines[i][0:6]) + prefix := lines[i][0:6] // tags are 4 digits surrounded by {..} - example: {1500} - if prefix[0] != '{' || prefix[5] != '}' { - t.Errorf("index #%d - missing tag: %q", i, prefix) - } + require.Equal(t, "{", string(prefix[0]), "tag missing opening bracket") + require.Equal(t, "}", string(prefix[5]), "tag missing closing bracket") } } diff --git a/issue92_test.go b/issue92_test.go index 4734253c..8d738994 100644 --- a/issue92_test.go +++ b/issue92_test.go @@ -8,7 +8,7 @@ import ( func TestFedWireMessage_verifyIssue92(t *testing.T) { fwm := issue92FedWireMessage() - require.NoError(t, fwm.verify()) + require.NoError(t, fwm.verifyWithOpts(ValidateOpts{})) } // this is the payload reported in issue 92 (bug in fwm validation) diff --git a/openapi.yaml b/openapi.yaml index afc2e902..d34a5162 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -351,10 +351,14 @@ components: type: string description: File ID example: 3f2d23ee214 - fedWireMessage: - $ref: '#/components/schemas/FEDWireMessage' + fedWireMessages: + type: array + items: + $ref: '#/components/schemas/FEDWireMessage' + validateOptions: + $ref: '#/components/schemas/ValidateOptions' required: - - fedWireMessage + - fedWireMessages WireFiles: type: array items: @@ -490,8 +494,6 @@ components: $ref: '#/components/schemas/RemittanceFreeText' serviceMessage: $ref: '#/components/schemas/ServiceMessage' - validateOptions: - $ref: '#/components/schemas/ValidateOptions' required: - senderSupplied - typeSubType diff --git a/reader.go b/reader.go index 0eb31131..541ee4ec 100644 --- a/reader.go +++ b/reader.go @@ -6,6 +6,7 @@ package wire import ( "bufio" + "errors" "fmt" "io" "regexp" @@ -20,7 +21,7 @@ type Reader struct { // r handles the IO.Reader sent to be parser. scanner *bufio.Scanner // file is ach.file model being built as r is parsed. - File File + File *File // line is the current line being parsed from the input r line string // ToDo: Do we need a current FEDWireMessage, just use FEDWireMessage @@ -45,9 +46,11 @@ func (r *Reader) parseError(err error) error { if err == nil { return nil } - if _, ok := err.(*base.ParseError); ok { + var parseError *base.ParseError + if errors.As(err, &parseError) { return err } + return &base.ParseError{ Line: r.lineNum, Record: r.tagName, @@ -59,7 +62,7 @@ func (r *Reader) parseError(err error) error { func NewReader(r io.Reader, opts ...FilePropertyFunc) *Reader { reader := &Reader{ scanner: bufio.NewScanner(r), - File: *NewFile(opts...), + File: NewFile(opts...), } reader.scanner.Split(scanLinesWithSegmentFormat) @@ -76,15 +79,15 @@ func NewReader(r io.Reader, opts ...FilePropertyFunc) *Reader { // Read reads each line of the FED Wire file and defines which parser to use based // on the first character of each line. It also enforces FED Wire formatting rules and returns // the appropriate error if issues are found. -func (r *Reader) Read() (File, error) { +func (r *Reader) Read() (*File, error) { return r.read(nil) } -func (r *Reader) ReadWithOpts(opts *ValidateOpts) (File, error) { +func (r *Reader) ReadWithOpts(opts *ValidateOpts) (*File, error) { return r.read(opts) } -func (r *Reader) read(opts *ValidateOpts) (File, error) { +func (r *Reader) read(opts *ValidateOpts) (*File, error) { spiltString := func(line string) []string { // strip new lines @@ -103,24 +106,40 @@ func (r *Reader) read(opts *ValidateOpts) (File, error) { } r.lineNum = 0 + // read through the entire file for r.scanner.Scan() { line := r.scanner.Text() for _, subLine := range spiltString(line) { r.lineNum++ r.line = subLine + + // Check if this is the start of a new FEDWireMessage + if r.startOfMessage(r.line[:6]) { + if r.currentFEDWireMessage != (FEDWireMessage{}) { + r.File.AddFEDWireMessage(r.currentFEDWireMessage) + r.currentFEDWireMessage = FEDWireMessage{} + } + } + if err := r.parseLine(); err != nil { r.errors.Add(err) } } } - r.File.AddFEDWireMessage(r.currentFEDWireMessage) - r.currentFEDWireMessage = FEDWireMessage{} + if err := r.scanner.Err(); err != nil { + r.errors.Add(err) + } + + if r.currentFEDWireMessage != (FEDWireMessage{}) { + r.File.AddFEDWireMessage(r.currentFEDWireMessage) + r.currentFEDWireMessage = FEDWireMessage{} + } if r.errors.Empty() { if opts != nil { - r.File.SetValidation(opts) + r.File.ValidateOptions = *opts } err := r.File.Validate() if err == nil { @@ -131,6 +150,20 @@ func (r *Reader) read(opts *ValidateOpts) (File, error) { return r.File, r.errors } +func (r *Reader) startOfMessage(currTag string) bool { + // This is a new incoming message if it starts with {1100} (MessageDisposition) + if currTag == TagMessageDisposition { + return true + } + + // This is a new outgoing message if there was no {1100} (MessageDisposition) tag + // and current tag is {1500} (SenderSupplied) + if currTag == TagSenderSupplied && r.currentFEDWireMessage.MessageDisposition == nil { + return true + } + return false +} + func (r *Reader) parseLine() error { //nolint:gocyclo if n := utf8.RuneCountInString(r.line); n < 6 { return fmt.Errorf("line %q is too short for tag", r.line) diff --git a/reader_test.go b/reader_test.go index a61f5487..c553483b 100644 --- a/reader_test.go +++ b/reader_test.go @@ -1,7 +1,6 @@ package wire import ( - "bytes" "os" "path" "path/filepath" @@ -82,32 +81,3 @@ func TestRead_missingTag(t *testing.T) { _, err = r.Read() require.ErrorContains(t, err, ErrRequireDelimiter.Error()) } - -func TestReadWithValidateOpts(t *testing.T) { - f, err := os.Open("./test/testdata/fedWireMessage-BankTransfer.txt") - if err != nil { - t.Fatalf("%T: %s", err, err) - } - defer f.Close() - r := NewReader(f) - - file, err := r.Read() - require.NoError(t, err) - require.NotNil(t, file) - - file.FEDWireMessage.InputMessageAccountabilityData = nil - - b := &bytes.Buffer{} - w := NewWriter(b) - err = w.Write(&file) - require.Error(t, err) - - r1 := NewReader(b) - file, err = r1.ReadWithOpts(&ValidateOpts{ - SkipMandatoryIMAD: true, - }) - - require.Error(t, err) - require.NotNil(t, file) - require.Nil(t, file.FEDWireMessage.InputMessageAccountabilityData) -} diff --git a/test/fuzz/fuzz_test.go b/test/fuzz/fuzz_test.go index 79032eef..02e3a518 100644 --- a/test/fuzz/fuzz_test.go +++ b/test/fuzz/fuzz_test.go @@ -26,7 +26,7 @@ func FuzzReaderWriterWire(f *testing.F) { } // Write the file - wire.NewWriter(io.Discard).Write(&file) + wire.NewWriter(io.Discard).Write(file) // Remove Validation override // file.SetValidation(&ach.ValidateOpts{ diff --git a/test/testdata/batchFile.txt b/test/testdata/batchFile.txt new file mode 100644 index 00000000..32aa15e1 --- /dev/null +++ b/test/testdata/batchFile.txt @@ -0,0 +1,2 @@ +{1500}30User ReqT {1510}1000{1520}20190410Source08000001{2000}000001234567{3100}121042882Wells Fargo NA*{3320}Sender Reference*{3400}231380104Citadel*{3500}Previous Message Ident{3600}BTR{4000}D123456789*FI Name*Address One*Address Two*Address Three*{4100}D123456789*FI Name*Address One*Address Two*Address Three*{4200}31234*Name*Address One*Address Two*Address Three*{4320}Reference*{5000}11234*Name*Address One**Address Three*{5100}D123456789*FI Name*Address One*Address Two*Address Three*{5200}D123456789*FI Name*Address One*Address Two*Address Three*{6000}LineOne*LineTwo*LineThree*LineFour*{6100}Line Six*{6200}Line Six*{6210}LTRLine One*Line Two*Line Three*Line Four*Line Five*Line Six*{6300}Line One*Line Two*Line Three*Line Four*Line Five*Line Six*{6310}TLXLine One*Line Two*Line Three*Line Four*Line Five*Line Six*{6400}Line One*Line Two*Line Three*Line Four*Line Five*Line Six*{6410}LTRLine One*Line Two*Line Three*Line Four*Line Five*Line Six*{6420}CHECKAdditional Information*{6500}Line One*Line Two*Line Three*Line Four*Line Five*Line Six* +{1500}30User ReqT {1510}1000{1520}20190410Source08000001{2000}000001234567{3100}121042882Wells Fargo NA*{3320}Sender Reference*{3400}231380104Citadel*{3500}Previous Message Ident{3600}CTR{3700}BUSD0,99*USD2,99*USD3,99*USD1,00*{3710}USD4567,89*{3720}1,2345*{4000}D123456789*FI Name*Address One*Address Two*Address Three*{4100}D123456789*FI Name*Address One*Address Two*Address Three*{4200}31234*Name*Address One*Address Two*Address Three*{4320}Reference*{5000}11234*Name*Address One**Address Three*{5100}D123456789*FI Name*Address One*Address Two*Address Three*{5200}D123456789*FI Name*Address One*Address Two*Address Three*{6000}LineOne*LineTwo*LineThree*LineFour*{6100}Line Six*{6200}Line Six*{6210}LTRLine One*Line Two*Line Three*Line Four*Line Five*Line Six*{6300}Line One*Line Two*Line Three*Line Four*Line Five*Line Six*{6310}TLXLine One*Line Two*Line Three*Line Four*Line Five*Line Six*{6400}Line One*Line Two*Line Three*Line Four*Line Five*Line Six*{6410}LTRLine One*Line Two*Line Three*Line Four*Line Five*Line Six*{6420}CHECKAdditional Information*{6500}Line One*Line Two*Line Three*Line Four*Line Five*Line Six* \ No newline at end of file diff --git a/test/testdata/batchFile_incomingWithNewlines.txt b/test/testdata/batchFile_incomingWithNewlines.txt new file mode 100644 index 00000000..ee063254 --- /dev/null +++ b/test/testdata/batchFile_incomingWithNewlines.txt @@ -0,0 +1,89 @@ +{1100}30P 2 +{1110}05021230A123 +{1120}20190502Source0800000105021230B123 +{1130}EXYZData Error* +{1510}1000 +{1520}20190410Source08000001 +{2000}000000000001 +{3100}121042882Wells Fargo NA* +{3320}Sender Reference* +{3400}231380104Citadel* +{3500}Previous Message Ident +{3600}BTR +{4000}D123456789*FI Name*Address One*Address Two*Address Three* +{4100}D123456789*FI Name*Address One*Address Two*Address Three* +{4200}31234*Name*Address One*Address Two*Address Three* +{4320}Reference* +{5000}11234*Name*Address One*Address Two*Address Three* +{5100}D123456789*FI Name*Address One*Address Two*Address Three* +{5200}D123456789*FI Name*Address One*Address Two*Address Three* +{6000}LineOne*LineTwo*LineThree*LineFour* +{6100}Line Six* +{6200}Line Six* +{6210}LTRLine One*Line Two*Line Three*Line Four*Line Five*Line Six* +{6300}Line One*Line Two*Line Three*Line Four*Line Five*Line Six* +{6310}TLXLine One*Line Two*Line Three*Line Four*Line Five*Line Six* +{6400}Line One*Line Two*Line Three*Line Four*Line Five*Line Six* +{6410}LTRLine One*Line Two*Line Three*Line Four*Line Five*Line Six* +{6420}CHECKAdditional Information* +{6500}Line One*Line Two*Line Three*Line Four*Line Five*Line Six* +{1100}30P 2 +{1110}05021230A123 +{1120}20190502Source0800000105021230B123 +{1130}EXYZData Error* +{1500}30User ReqT +{1510}1000 +{1520}20190410Source08000001 +{2000}000000000002 +{3100}121042882Wells Fargo NA* +{3320}Sender Reference* +{3400}231380104Citadel* +{3500}Previous Message Ident +{3600}BTR +{4000}D123456789*FI Name*Address One*Address Two*Address Three* +{4100}D123456789*FI Name*Address One*Address Two*Address Three* +{4200}31234*Name*Address One*Address Two*Address Three* +{4320}Reference* +{5000}11234*Name*Address One*Address Two*Address Three* +{5100}D123456789*FI Name*Address One*Address Two*Address Three* +{5200}D123456789*FI Name*Address One*Address Two*Address Three* +{6000}LineOne*LineTwo*LineThree*LineFour* +{6100}Line Six* +{6200}Line Six* +{6210}LTRLine One*Line Two*Line Three*Line Four*Line Five*Line Six* +{6300}Line One*Line Two*Line Three*Line Four*Line Five*Line Six* +{6310}TLXLine One*Line Two*Line Three*Line Four*Line Five*Line Six* +{6400}Line One*Line Two*Line Three*Line Four*Line Five*Line Six* +{6410}LTRLine One*Line Two*Line Three*Line Four*Line Five*Line Six* +{6420}CHECKAdditional Information* +{6500}Line One*Line Two*Line Three*Line Four*Line Five*Line Six* +{1100}30P 2 +{1110}05021230A123 +{1120}20190502Source0800000105021230B123 +{1130}EXYZData Error* +{1500}30User ReqT +{1510}1000 +{1520}20190410Source08000001 +{2000}000000000003 +{3100}121042882Wells Fargo NA* +{3320}Sender Reference* +{3400}231380104Citadel* +{3500}Previous Message Ident +{3600}BTR +{4000}D123456789*FI Name*Address One*Address Two*Address Three* +{4100}D123456789*FI Name*Address One*Address Two*Address Three* +{4200}31234*Name*Address One*Address Two*Address Three* +{4320}Reference* +{5000}11234*Name*Address One*Address Two*Address Three* +{5100}D123456789*FI Name*Address One*Address Two*Address Three* +{5200}D123456789*FI Name*Address One*Address Two*Address Three* +{6000}LineOne*LineTwo*LineThree*LineFour* +{6100}Line Six* +{6200}Line Six* +{6210}LTRLine One*Line Two*Line Three*Line Four*Line Five*Line Six* +{6300}Line One*Line Two*Line Three*Line Four*Line Five*Line Six* +{6310}TLXLine One*Line Two*Line Three*Line Four*Line Five*Line Six* +{6400}Line One*Line Two*Line Three*Line Four*Line Five*Line Six* +{6410}LTRLine One*Line Two*Line Three*Line Four*Line Five*Line Six* +{6420}CHECKAdditional Information* +{6500}Line One*Line Two*Line Three*Line Four*Line Five*Line Six* \ No newline at end of file diff --git a/test/testdata/fedWireMessage-BankDrawDownRequest.json b/test/testdata/fedWireMessage-BankDrawDownRequest.json index b9b4ceb9..7407e7e3 100644 --- a/test/testdata/fedWireMessage-BankDrawDownRequest.json +++ b/test/testdata/fedWireMessage-BankDrawDownRequest.json @@ -1,6 +1,6 @@ { "id": "55146ab7286029084ff1b2689cbb12bb2e3f4d5c", - "fedWireMessage": { + "fedWireMessages": [{ "id": "", "senderSupplied": { "formatVersion": "30", @@ -208,5 +208,5 @@ "lineSix": "Line Six" } } - } + }] } \ No newline at end of file diff --git a/test/testdata/fedWireMessage-BankTransfer.json b/test/testdata/fedWireMessage-BankTransfer.json index eda2dbc1..13d8afec 100644 --- a/test/testdata/fedWireMessage-BankTransfer.json +++ b/test/testdata/fedWireMessage-BankTransfer.json @@ -1,5 +1,5 @@ { - "fedWireMessage": { + "fedWireMessages": [{ "fiAdditionalFiToFi": { "additionalFiToFi": { "lineSix": "Line Six", @@ -194,6 +194,6 @@ "formatVersion": "30" }, "id": "" - }, + }], "id": "" } diff --git a/test/testdata/fedWireMessage-CheckSameDaySettlement.json b/test/testdata/fedWireMessage-CheckSameDaySettlement.json index 278ec119..f9f3ab6b 100644 --- a/test/testdata/fedWireMessage-CheckSameDaySettlement.json +++ b/test/testdata/fedWireMessage-CheckSameDaySettlement.json @@ -1,6 +1,6 @@ { "id": "7dfc0eb17e1a74a3d66f34a351bdf405db5abb2d", - "fedWireMessage": { + "fedWireMessages": [{ "id": "", "senderSupplied": { "formatVersion": "30", @@ -195,5 +195,5 @@ "lineSix": "Line Six" } } - } + }] } \ No newline at end of file diff --git a/test/testdata/fedWireMessage-CustomerCorporateDrawDownRequest.json b/test/testdata/fedWireMessage-CustomerCorporateDrawDownRequest.json index 17f002fc..0566753b 100644 --- a/test/testdata/fedWireMessage-CustomerCorporateDrawDownRequest.json +++ b/test/testdata/fedWireMessage-CustomerCorporateDrawDownRequest.json @@ -1,6 +1,6 @@ { "id": "b9f4a6d184004a1731b61524d0b8fc4845a5da46", - "fedWireMessage": { + "fedWireMessages": [{ "id": "", "senderSupplied": { "formatVersion": "30", @@ -219,5 +219,5 @@ "lineSix": "Line Six" } } - } + }] } \ No newline at end of file diff --git a/test/testdata/fedWireMessage-CustomerTransfer.json b/test/testdata/fedWireMessage-CustomerTransfer.json index 77a6adc3..689f0f6b 100644 --- a/test/testdata/fedWireMessage-CustomerTransfer.json +++ b/test/testdata/fedWireMessage-CustomerTransfer.json @@ -1,6 +1,6 @@ { "id": "bde343fae3e29e139693a539a5e0aabf7a78fddd", - "fedWireMessage": { + "fedWireMessages": [{ "id": "", "senderSupplied": { "formatVersion": "30", @@ -209,5 +209,5 @@ "lineSix": "Line Six" } } - } + }] } \ No newline at end of file diff --git a/test/testdata/fedWireMessage-CustomerTransfer.txt b/test/testdata/fedWireMessage-CustomerTransfer.txt index 0d93abb5..b2d305be 100644 --- a/test/testdata/fedWireMessage-CustomerTransfer.txt +++ b/test/testdata/fedWireMessage-CustomerTransfer.txt @@ -3,10 +3,10 @@ {1520}20190410Source08000001 {2000}000001234567 {3100}121042882Wells Fargo NA* -{3400}231380104Citadel* -{3600}CTR * {3320}Sender Reference* +{3400}231380104Citadel* {3500}Previous Message Ident +{3600}CTR {3700}BUSD0,99*USD2,99*USD3,99*USD1,00* {3710}USD4567,89* {3720}1,2345* diff --git a/test/testdata/fedWireMessage-CustomerTransferPlus.json b/test/testdata/fedWireMessage-CustomerTransferPlus.json index 16dadd6f..f54e5834 100644 --- a/test/testdata/fedWireMessage-CustomerTransferPlus.json +++ b/test/testdata/fedWireMessage-CustomerTransferPlus.json @@ -1,6 +1,6 @@ { "id": "c6a4521316885d92a40845e67162a35ce8cc9d15", - "fedWireMessage": { + "fedWireMessages": [{ "id": "", "senderSupplied": { "formatVersion": "30", @@ -238,5 +238,5 @@ "lineEleven": "Line Eleven", "lineTwelve": "line Twelve" } - } + }] } \ No newline at end of file diff --git a/test/testdata/fedWireMessage-CustomerTransferPlusCOVS.json b/test/testdata/fedWireMessage-CustomerTransferPlusCOVS.json index 870b4368..c4967861 100644 --- a/test/testdata/fedWireMessage-CustomerTransferPlusCOVS.json +++ b/test/testdata/fedWireMessage-CustomerTransferPlusCOVS.json @@ -1,6 +1,6 @@ { "id": "7902a562d654fee194b70cfb14487d4bff01ad85", - "fedWireMessage": { + "fedWireMessages": [{ "id": "", "senderSupplied": { "formatVersion": "30", @@ -283,5 +283,5 @@ "swiftLineSix": "Swift Line Six" } } - } + }] } \ No newline at end of file diff --git a/test/testdata/fedWireMessage-CustomerTransferPlusRelatedRemittance.json b/test/testdata/fedWireMessage-CustomerTransferPlusRelatedRemittance.json index b4e5389a..f545e5e1 100644 --- a/test/testdata/fedWireMessage-CustomerTransferPlusRelatedRemittance.json +++ b/test/testdata/fedWireMessage-CustomerTransferPlusRelatedRemittance.json @@ -1,6 +1,6 @@ { "id": "e76adcdc9afb368b5b1387b33fe0b7fc6c9aedd0", - "fedWireMessage": { + "fedWireMessages": [{ "id": "", "senderSupplied": { "formatVersion": "30", @@ -232,5 +232,5 @@ "addressLineSeven": "Address Line Seven" } } - } + }] } \ No newline at end of file diff --git a/test/testdata/fedWireMessage-CustomerTransferPlusStructuredRemittance.json b/test/testdata/fedWireMessage-CustomerTransferPlusStructuredRemittance.json index 29f8e063..8c99ea1e 100644 --- a/test/testdata/fedWireMessage-CustomerTransferPlusStructuredRemittance.json +++ b/test/testdata/fedWireMessage-CustomerTransferPlusStructuredRemittance.json @@ -1,6 +1,6 @@ { "id": "3fb14b9b12ccfe4662c56b61f4296eb286c91835", - "fedWireMessage": { + "fedWireMessages": [{ "id": "", "senderSupplied": { "formatVersion": "30", @@ -312,5 +312,5 @@ "lineTwo": "Remittance Free Text Line Two", "lineThree": "Remittance Free Text Line Three" } - } + }] } \ No newline at end of file diff --git a/test/testdata/fedWireMessage-CustomerTransferPlusUnstructuredAddenda.json b/test/testdata/fedWireMessage-CustomerTransferPlusUnstructuredAddenda.json index a7fec197..5c3fdf3d 100644 --- a/test/testdata/fedWireMessage-CustomerTransferPlusUnstructuredAddenda.json +++ b/test/testdata/fedWireMessage-CustomerTransferPlusUnstructuredAddenda.json @@ -1,6 +1,6 @@ { "id": "5bf2233c6321e4c6a2ff896610b77b1011f2a22b", - "fedWireMessage": { + "fedWireMessages": [{ "id": "", "senderSupplied": { "formatVersion": "30", @@ -213,5 +213,5 @@ "addendaLength": "0020", "addenda": "Unstructured Addenda" } - } + }] } \ No newline at end of file diff --git a/test/testdata/fedWireMessage-DepositSendersAccount.json b/test/testdata/fedWireMessage-DepositSendersAccount.json index 81c6f6c2..cf84045e 100644 --- a/test/testdata/fedWireMessage-DepositSendersAccount.json +++ b/test/testdata/fedWireMessage-DepositSendersAccount.json @@ -1,6 +1,6 @@ { "id": "a21c9d9b675e4952db05dc0621bf4ef9363bebd7", - "fedWireMessage": { + "fedWireMessages": [{ "id": "", "senderSupplied": { "formatVersion": "30", @@ -195,5 +195,5 @@ "lineSix": "Line Six" } } - } + }] } \ No newline at end of file diff --git a/test/testdata/fedWireMessage-DrawdownResponse.json b/test/testdata/fedWireMessage-DrawdownResponse.json index 76e29e49..fe7bfb6b 100644 --- a/test/testdata/fedWireMessage-DrawdownResponse.json +++ b/test/testdata/fedWireMessage-DrawdownResponse.json @@ -1,6 +1,6 @@ { "id": "ff36029d5c5c096d0105961409ffd13a2b929249", - "fedWireMessage": { + "fedWireMessages": [{ "id": "", "senderSupplied": { "formatVersion": "30", @@ -195,5 +195,5 @@ "lineSix": "Line Six" } } - } + }] } \ No newline at end of file diff --git a/test/testdata/fedWireMessage-FEDFundsReturned.json b/test/testdata/fedWireMessage-FEDFundsReturned.json index a2c49ed3..63473e48 100644 --- a/test/testdata/fedWireMessage-FEDFundsReturned.json +++ b/test/testdata/fedWireMessage-FEDFundsReturned.json @@ -1,6 +1,6 @@ { "id": "731fa2eae87b63dc9e5c5ea79e8767006fe92205", - "fedWireMessage": { + "fedWireMessages": [{ "id": "", "senderSupplied": { "formatVersion": "30", @@ -195,5 +195,5 @@ "lineSix": "Line Six" } } - } + }] } \ No newline at end of file diff --git a/test/testdata/fedWireMessage-FEDFundsSold.json b/test/testdata/fedWireMessage-FEDFundsSold.json index 8ddb79ff..ccc4cbbd 100644 --- a/test/testdata/fedWireMessage-FEDFundsSold.json +++ b/test/testdata/fedWireMessage-FEDFundsSold.json @@ -1,6 +1,6 @@ { "id": "77eddf041a23959d5ad975e525f9a16d2d73c75a", - "fedWireMessage": { + "fedWireMessages": [{ "id": "", "senderSupplied": { "formatVersion": "30", @@ -195,5 +195,5 @@ "lineSix": "Line Six" } } - } + }] } \ No newline at end of file diff --git a/test/testdata/fedWireMessage-FedAppendedTags.json b/test/testdata/fedWireMessage-FedAppendedTags.json index 019d052a..41982579 100644 --- a/test/testdata/fedWireMessage-FedAppendedTags.json +++ b/test/testdata/fedWireMessage-FedAppendedTags.json @@ -1,6 +1,6 @@ { "id": "77ed843dd88e3ace155ef0ea17b59aec349c6218", - "fedWireMessage": { + "fedWireMessages": [{ "id": "", "messageDisposition": { "formatVersion": "30", @@ -218,5 +218,5 @@ "lineSix": "Line Six" } } - } + }] } \ No newline at end of file diff --git a/test/testdata/fedWireMessage-FedAppendedTags.txt b/test/testdata/fedWireMessage-FedAppendedTags.txt index 8f13fb11..d54022a4 100644 --- a/test/testdata/fedWireMessage-FedAppendedTags.txt +++ b/test/testdata/fedWireMessage-FedAppendedTags.txt @@ -1,12 +1,16 @@ +{1100}30P 2 +{1110}05021230A123 +{1120}20190502Source0800000105021230B123 +{1130}EXYZData Error* {1500}30User ReqT {1510}1000 {1520}20190410Source08000001 {2000}000001234567 {3100}121042882Wells Fargo NA* -{3400}231380104Citadel* -{3600}BTR* {3320}Sender Reference* +{3400}231380104Citadel* {3500}Previous Message Ident +{3600}BTR {4000}D123456789*FI Name*Address One*Address Two*Address Three* {4100}D123456789*FI Name*Address One*Address Two*Address Three* {4200}31234*Name*Address One*Address Two*Address Three* @@ -23,8 +27,4 @@ {6400}Line One*Line Two*Line Three*Line Four*Line Five*Line Six* {6410}LTRLine One*Line Two*Line Three*Line Four*Line Five*Line Six* {6420}CHECKAdditional Information* -{6500}Line One*Line Two*Line Three*Line Four*Line Five*Line Six* -{1100}30P 2 -{1110}05021230A123 -{1120}20190502Source0800000105021230B123 -{1130}EXYZData Error* \ No newline at end of file +{6500}Line One*Line Two*Line Three*Line Four*Line Five*Line Six* \ No newline at end of file diff --git a/test/testdata/fedWireMessage-ServiceMessage.json b/test/testdata/fedWireMessage-ServiceMessage.json index f53ad618..d31e6b46 100644 --- a/test/testdata/fedWireMessage-ServiceMessage.json +++ b/test/testdata/fedWireMessage-ServiceMessage.json @@ -1,6 +1,6 @@ { "id": "9f56472e4395265af50156990114032dc7233dc5", - "fedWireMessage": { + "fedWireMessages": [{ "id": "", "senderSupplied": { "formatVersion": "30", @@ -233,5 +233,5 @@ "lineEleven": "Line Eleven", "lineTwelve": "line Twelve" } - } + }] } \ No newline at end of file diff --git a/test/testdata/fedWireMessage-fiserv.json b/test/testdata/fedWireMessage-fiserv.json index cd411f87..0f7ab3c1 100644 --- a/test/testdata/fedWireMessage-fiserv.json +++ b/test/testdata/fedWireMessage-fiserv.json @@ -1,6 +1,6 @@ { "id": "accb403bb445aa1fcf63d6e24b0a06f10c3d9fca", - "fedWireMessage": { + "fedWireMessages": [{ "id": "", "senderSupplied": { "formatVersion": "30", @@ -99,5 +99,5 @@ "lineOne": "Test" } } - } + }] } \ No newline at end of file diff --git a/writer.go b/writer.go index 878dfbe1..c279c91d 100644 --- a/writer.go +++ b/writer.go @@ -22,6 +22,7 @@ type Writer struct { w *bufio.Writer lineNum int // current line being written FormatOptions + validateOpts ValidateOpts } type OptionFunc func(*Writer) @@ -40,13 +41,24 @@ func NewlineCharacter(newline string) OptionFunc { } } -// NewWriter returns a new Writer that writes to w. -// If no opts are provided, the writer will default to fixed-length fields and use "\n" for newlines. +// MessageDelimiter specify message delimiter +func MessageDelimiter(delimiter string) OptionFunc { + return func(w *Writer) { + w.MessageDelimiter = delimiter + } +} + +// NewWriter returns a new Writer that writes to w. If no opts are provided, the write will use the +// following defaults: +// - Fixed-length elements within tags +// - "\n" between tags in a message +// - "\n" between messages within a file func NewWriter(w io.Writer, opts ...OptionFunc) *Writer { writer := &Writer{ w: bufio.NewWriter(w), FormatOptions: FormatOptions{ NewlineCharacter: "\n", + MessageDelimiter: "\n", }, } @@ -67,8 +79,10 @@ func (w *Writer) Write(file *File) error { return err } w.lineNum = 0 + // Iterate over all records in the file - if err := w.writeFEDWireMessage(file); err != nil { + w.validateOpts = file.ValidateOptions + if err := w.writeFEDWireMessages(file); err != nil { return err } w.lineNum++ @@ -83,70 +97,78 @@ func (w *Writer) Flush() error { return w.w.Flush() } -func (w *Writer) writeFEDWireMessage(file *File) error { - fwm := file.FEDWireMessage +func (w *Writer) writeFEDWireMessages(file *File) error { + if file == nil { + return nil + } - var outputLines []string + for i, fwm := range file.FEDWireMessages { + var outputLines []string - mandatoryLines, err := w.writeMandatory(fwm) - if err != nil { - return err - } - outputLines = append(outputLines, mandatoryLines...) + mandatoryLines, err := w.writeMandatory(fwm) + if err != nil { + return err + } + outputLines = append(outputLines, mandatoryLines...) - otherTransferLines, err := w.writeOtherTransferInfo(fwm) - if err != nil { - return err - } - outputLines = append(outputLines, otherTransferLines...) + otherTransferLines, err := w.writeOtherTransferInfo(fwm) + if err != nil { + return err + } + outputLines = append(outputLines, otherTransferLines...) - beneficiaryLines, err := w.writeBeneficiary(fwm) - if err != nil { - return err - } - outputLines = append(outputLines, beneficiaryLines...) + beneficiaryLines, err := w.writeBeneficiary(fwm) + if err != nil { + return err + } + outputLines = append(outputLines, beneficiaryLines...) - originatorLines, err := w.writeOriginator(fwm) - if err != nil { - return err - } - outputLines = append(outputLines, originatorLines...) + originatorLines, err := w.writeOriginator(fwm) + if err != nil { + return err + } + outputLines = append(outputLines, originatorLines...) - financialInstitutionLines, err := w.writeFinancialInstitution(fwm) - if err != nil { - return err - } - outputLines = append(outputLines, financialInstitutionLines...) + financialInstitutionLines, err := w.writeFinancialInstitution(fwm) + if err != nil { + return err + } + outputLines = append(outputLines, financialInstitutionLines...) - coverPaymentLines, err := w.writeCoverPayment(fwm) - if err != nil { - return err - } - outputLines = append(outputLines, coverPaymentLines...) + coverPaymentLines, err := w.writeCoverPayment(fwm) + if err != nil { + return err + } + outputLines = append(outputLines, coverPaymentLines...) - if fwm.UnstructuredAddenda != nil { - outputLines = append(outputLines, fwm.UnstructuredAddenda.String()) - } + if fwm.UnstructuredAddenda != nil { + outputLines = append(outputLines, fwm.UnstructuredAddenda.String()) + } - remittanceLines, err := w.writeRemittance(fwm) - if err != nil { - return err - } - outputLines = append(outputLines, remittanceLines...) + remittanceLines, err := w.writeRemittance(fwm) + if err != nil { + return err + } + outputLines = append(outputLines, remittanceLines...) - if fwm.ServiceMessage != nil { - outputLines = append(outputLines, fwm.ServiceMessage.Format(w.FormatOptions)) - } + if fwm.ServiceMessage != nil { + outputLines = append(outputLines, fwm.ServiceMessage.Format(w.FormatOptions)) + } - fedAppendedLines, err := w.writeFedAppended(fwm) - if err != nil { - return err - } - outputLines = append(outputLines, fedAppendedLines...) + fedAppendedLines, err := w.writeFedAppended(fwm) + if err != nil { + return err + } + outputLines = append(outputLines, fedAppendedLines...) + + slices.Sort(outputLines) + w.w.WriteString(strings.Join(outputLines, w.NewlineCharacter)) - slices.Sort(outputLines) - w.w.WriteString(strings.Join(outputLines, w.NewlineCharacter)) - w.w.WriteString(w.NewlineCharacter) + // write a message delimiter if not the last message + if len(file.FEDWireMessages) > i+1 { + w.w.WriteString(w.MessageDelimiter) + } + } return nil } @@ -179,7 +201,7 @@ func (w *Writer) writeMandatory(fwm FEDWireMessage) ([]string, error) { if fwm.SenderSupplied != nil { lines = append(lines, fwm.SenderSupplied.Format(w.FormatOptions)) } else { - if fwm.requireSenderSupplied() { + if !w.validateOpts.AllowMissingSenderSupplied { return nil, fieldError("SenderSupplied", ErrFieldRequired) } } diff --git a/writer_test.go b/writer_test.go index 312b1be0..fd1714ac 100644 --- a/writer_test.go +++ b/writer_test.go @@ -28,9 +28,6 @@ func TestSenderSupplied_Mandatory(t *testing.T) { file.AddFEDWireMessage(fwm) - // Create file - require.NoError(t, file.Create()) - err := file.Validate() require.EqualError(t, err, fieldError("SenderSupplied", ErrFieldRequired).Error()) @@ -56,9 +53,6 @@ func TestTypeSubType_Mandatory(t *testing.T) { file.AddFEDWireMessage(fwm) - // Create file - require.NoError(t, file.Create()) - err := file.Validate() require.EqualError(t, err, fieldError("TypeSubType", ErrFieldRequired).Error()) @@ -84,9 +78,6 @@ func TestInputMessageAccountabilityData_Mandatory(t *testing.T) { file.AddFEDWireMessage(fwm) - // Create file - require.NoError(t, file.Create()) - err := file.Validate() require.EqualError(t, err, fieldError("InputMessageAccountabilityData", ErrFieldRequired).Error()) @@ -112,9 +103,6 @@ func TestAmount_Mandatory(t *testing.T) { file.AddFEDWireMessage(fwm) - // Create file - require.NoError(t, file.Create()) - err := file.Validate() require.EqualError(t, err, fieldError("Amount", ErrFieldRequired).Error()) @@ -140,9 +128,6 @@ func TestSenderDepositoryInstitution_Mandatory(t *testing.T) { file.AddFEDWireMessage(fwm) - // Create file - require.NoError(t, file.Create()) - err := file.Validate() require.EqualError(t, err, fieldError("SenderDepositoryInstitution", ErrFieldRequired).Error()) @@ -168,9 +153,6 @@ func TestReceiverDepositoryInstitution_Mandatory(t *testing.T) { file.AddFEDWireMessage(fwm) - // Create file - require.NoError(t, file.Create()) - err := file.Validate() require.EqualError(t, err, fieldError("ReceiverDepositoryInstitution", ErrFieldRequired).Error()) @@ -196,9 +178,6 @@ func TestBusinessFunctionCode_Mandatory(t *testing.T) { file.AddFEDWireMessage(fwm) - // Create file - require.NoError(t, file.Create()) - err := file.Validate() require.EqualError(t, err, fieldError("BusinessFunctionCode", ErrFieldRequired).Error()) @@ -304,8 +283,8 @@ func TestFEDWireMessageWriteCustomerTransfer(t *testing.T) { fwm.SenderReference = sr pmi := mockPreviousMessageIdentifier() fwm.PreviousMessageIdentifier = pmi - //li := mockLocalInstrument() - //fwm.LocalInstrument = li + // li := mockLocalInstrument() + // fwm.LocalInstrument = li c := mockCharges() fwm.Charges = c ia := mockInstructedAmount() @@ -1020,12 +999,7 @@ func TestFEDWireMessageWriteServiceMessage(t *testing.T) { func getTagsFromContents(t *testing.T, file *File) []string { t.Helper() - err := file.Create() - if err != nil { - t.Fatal(err) - } - - err = file.Validate() + err := file.Validate() if err != nil { t.Fatal(err) } @@ -1048,9 +1022,6 @@ func getTagsFromContents(t *testing.T, file *File) []string { // writeFile writes a FEDWireMessage File and ensures the File can be read func writeFile(file *File) error { - if err := file.Create(); err != nil { - return err - } if err := file.Validate(); err != nil { return err }