Repository: moov-io/ach
Pull request #993 (2022-01-26 22:15:20)

feat: include ValidateOpts in JSON and merged files
Two changes

- merge: carry through ValidateOpts from files
- file: include ValidateOpts with JSON representations

This PR is used with https://github.com/moov-io/achgateway/pull/84 


Patch:
diff --git a/file.go b/file.go
index 9b59a87b..754909c9 100644
--- a/file.go
+++ b/file.go
@@ -189,6 +189,18 @@ func FileFromJSONWith(bs []byte, opts *ValidateOpts) (*File, error) {
 	return file, nil
 }
 
+// MarshalJSON will produce a JSON blob with the ACH file's fields and validation settings.
+func (f *File) MarshalJSON() ([]byte, error) {
+	type Aux struct {
+		File
+		ValidateOpts *ValidateOpts `json:"validateOpts"`
+	}
+	return json.Marshal(Aux{
+		File:         *f,
+		ValidateOpts: f.validateOpts,
+	})
+}
+
 // UnmarshalJSON parses a JSON blob with ach.FileFromJSON
 func (f *File) UnmarshalJSON(p []byte) error {
 	file, err := FileFromJSONWith(p, f.validateOpts)
@@ -198,9 +210,26 @@ func (f *File) UnmarshalJSON(p []byte) error {
 	if file != nil {
 		*f = *file
 	}
+	opts, err := readValidateOpts(p)
+	if err != nil {
+		return err
+	}
+	f.SetValidation(opts)
 	return nil
 }
 
+func readValidateOpts(p []byte) (*ValidateOpts, error) {
+	type Aux struct {
+		ValidateOpts *ValidateOpts `json:"validateOpts"`
+	}
+	var opts Aux
+	err := json.Unmarshal(p, &opts)
+	if err != nil {
+		return nil, err
+	}
+	return opts.ValidateOpts, nil
+}
+
 type batchesJSON struct {
 	Batches []*Batch `json:"batches"`
 }
@@ -561,6 +590,13 @@ func (f *File) Validate() error {
 	return f.ValidateWith(f.validateOpts)
 }
 
+func (f *File) GetValidation() *ValidateOpts {
+	if f == nil {
+		return nil
+	}
+	return f.validateOpts
+}
+
 // SetValidation stores ValidateOpts on the File which are to be used to override
 // the default NACHA validation rules.
 func (f *File) SetValidation(opts *ValidateOpts) {
@@ -594,7 +630,7 @@ type ValidateOpts struct {
 	BypassDestinationValidation bool `json:"bypassDestinationValidation"`
 
 	// CheckTransactionCode allows for custom validation of TransactionCode values
-	CheckTransactionCode func(code int) error
+	CheckTransactionCode func(code int) error `json:"-"`
 
 	// CustomTraceNumbers disables validation of TraceNumbers
 	CustomTraceNumbers bool `json:"customTraceNumbers"`
diff --git a/merge.go b/merge.go
index f4f46814..9b23d599 100644
--- a/merge.go
+++ b/merge.go
@@ -130,6 +130,7 @@ next:
 	out.Header = f.Header
 	out.Header.FileCreationDate = now.Format("060102") // YYMMDD
 	out.Header.FileCreationTime = now.Format("1504")   // HHmm
+	out.SetValidation(f.validateOpts)
 	out.Create()
 	fs.outfiles = append(fs.outfiles, out) // add the new outfile
 
@@ -169,6 +170,7 @@ func (fs *mergableFiles) findOutfile(f *File) *File {
 		// Record a newly mergable File/FileHeader we can use in future merge attempts
 		outf := NewFile()
 		outf.Header = f.Header
+		outf.SetValidation(f.validateOpts)
 		outf.Control = f.Control
 		fs.outfiles = append(fs.outfiles, outf)
 		return outf


Test patch:
diff --git a/file_test.go b/file_test.go
index 81c1d119..f98d7708 100644
--- a/file_test.go
+++ b/file_test.go
@@ -452,6 +452,28 @@ func BenchmarkFileReturnEntries(b *testing.B) {
 	}
 }
 
+func TestFile__ValidateOptsJSON(t *testing.T) {
+	file, err := ReadFile(filepath.Join("test", "testdata", "ppd-debit.ach"))
+	require.NoError(t, err)
+
+	file.SetValidation(&ValidateOpts{
+		BypassOriginValidation: true,
+	})
+
+	bs, err := file.MarshalJSON()
+	require.NoError(t, err)
+
+	// zero out the file and verify we can read it all back
+	file = NewFile()
+
+	err = json.Unmarshal(bs, file)
+	require.NoError(t, err)
+
+	opts := file.GetValidation()
+	require.True(t, opts.BypassOriginValidation)
+	require.False(t, opts.BypassDestinationValidation)
+}
+
 func TestFile__readFromJson(t *testing.T) {
 	path := filepath.Join("test", "testdata", "ppd-valid.json")
 	bs, err := ioutil.ReadFile(path)
@@ -463,6 +485,7 @@ func TestFile__readFromJson(t *testing.T) {
 	if err != nil {
 		t.Fatal(err)
 	}
+	require.Nil(t, file.GetValidation())
 
 	// Ensure the file is valid
 	if err := file.Create(); err != nil {