diff --git a/examples/example_stp_820/example.go b/examples/example_stp_820/example.go new file mode 100644 index 0000000..4fb4a3c --- /dev/null +++ b/examples/example_stp_820/example.go @@ -0,0 +1,50 @@ +// Copyright 2020 The Moov Authors +// Use of this source code is governed by an Apache License +// license that can be found in the LICENSE file. + +package main + +import ( + "fmt" + "log" + "os" + "path" + "strings" + + "github.com/moov-io/x12/pkg/file" + . "github.com/moov-io/x12/rules/rule_stp_820" +) + +func main() { + + reader, err := os.Open(path.Join("examples", "example_stp_820", "sample.txt")) + if err != nil { + log.Fatal(err) + } + defer reader.Close() + + copyRule := InterchangeRule + + eRule := copyRule.Group.GS.Elements["08"] + eRule.AcceptValues = []string{"004010STP820", "004010"} + copyRule.Group.GS.Elements["08"] = eRule + + segmentTerminator := "\\" + + newChange := file.NewFile(&InterchangeRule, segmentTerminator) + + if err = newChange.Parse(file.NewScanner(reader, segmentTerminator)); err != nil { + log.Fatal(err.Error()) + return + } + + if err = newChange.Validate(); err != nil { + log.Fatal(err.Error()) + return + } + + fmt.Println(" REGENERATED FILE ") + fmt.Println(strings.ReplaceAll(newChange.String(), segmentTerminator, segmentTerminator+"\n")) + + newChange.Print(os.Stdout) +} diff --git a/examples/example_stp_820/sample.txt b/examples/example_stp_820/sample.txt new file mode 100644 index 0000000..c7a033b --- /dev/null +++ b/examples/example_stp_820/sample.txt @@ -0,0 +1,15 @@ +ISA*00* *00* *30*227777777 *14*577777777 *120530*1144*U*00401*000000001*0*P*~\ +GS*RA*227777777*577777777*20120530*1144*1*X*004010\ +ST*820*0001\ +BPR*C*7989.73*C*ACH*CTX*****1657777777**01*148529553*DA*92283334*20120531\ +TRN*1*12053011440000192\ +N1*PR*YOUR COMPANY*91*227777777\ +N1*PE*WALMART\ +ENT*1\ +RMR*IV*7321239**953.19\ +REF*PO*24305\ +RMR*IV*7321511**7036.54\ +REF*PO*24333\ +SE*11*0001\ +GE*1*1\ +IEA*1*000000001\ \ No newline at end of file diff --git a/pkg/segments/ent.go b/pkg/segments/ent.go new file mode 100644 index 0000000..20724a2 --- /dev/null +++ b/pkg/segments/ent.go @@ -0,0 +1,146 @@ +// Copyright 2020 The Moov Authors +// Use of this source code is governed by an Apache License +// license that can be found in the LICENSE file. + +package segments + +import ( + "errors" + "fmt" + "github.com/moov-io/x12/pkg/rules" + "github.com/moov-io/x12/pkg/util" +) + +func NewENT(rule *rules.ElementSetRule) SegmentInterface { + + newSegment := ENT{} + + if rule == nil { + newRule := make(rules.ElementSetRule) + newSegment.SetRule(&newRule) + } else { + newSegment.SetRule(rule) + } + + return &newSegment +} + +type ENT struct { + Field01 string `index:"01" json:"01" xml:"01"` + Field02 string `index:"02" json:"02" xml:"02"` + Field03 string `index:"03" json:"03,omitempty" xml:"03,omitempty"` + Field04 string `index:"04" json:"04,omitempty" xml:"04,omitempty"` + + Element +} + +func (r ENT) defaultMask(index int) string { + mask := rules.MASK_REQUIRED + if index >= 3 { + mask = rules.MASK_OPTIONAL + } + return mask +} + +func (r ENT) fieldCount() int { + return 4 +} + +func (r ENT) Name() string { + return "ENT" +} + +func (r *ENT) SetFieldByIndex(index string, data any) error { + return util.SetFieldByIndex(r, index, data) +} + +func (r ENT) GetFieldByIndex(index string) any { + return util.GetFieldByIndex(r, index) +} + +func (r *ENT) Validate(rule *rules.ElementSetRule) error { + + if rule == nil { + rule = r.GetRule() + } + + for i := 1; i <= r.fieldCount(); i++ { + + idx := fmt.Sprintf("%02d", i) + if err := util.ValidateField(r.GetFieldByIndex(idx), rule.Get(idx), r.defaultMask(i)); err != nil { + return fmt.Errorf("ent's element (%s) has invalid value, %s", idx, err.Error()) + } + } + + return nil +} + +func (r *ENT) Parse(data string, args ...string) (int, error) { + + var line string + var err error + var size int + + length := util.GetRecordSize(data, args...) + codeLen := len(r.Name()) + read := codeLen + 1 + + if length < int64(read) { + return 0, errors.New("ent segment has not enough input data") + } else { + line = data[:length] + } + + if r.Name() != data[:codeLen] { + return 0, errors.New("ent segment contains invalid code") + } + + for i := 1; i <= r.fieldCount(); i++ { + + var value string + idx := fmt.Sprintf("%02d", i) + + if value, size, err = util.ReadField(line, read, r.GetRule().Get(idx), r.defaultMask(i), args...); err != nil { + return 0, fmt.Errorf("unable to parse ent's element (%s), %s", idx, err.Error()) + } else { + read += size + r.SetFieldByIndex(idx, value) + } + } + + return read, nil +} + +func (r ENT) String(args ...string) string { + var buf string + + for i := r.fieldCount(); i > 0; i-- { + + idx := fmt.Sprintf("%02d", i) + value := r.GetFieldByIndex(idx) + + if buf == "" { + mask := r.GetRule().GetMask(idx, r.defaultMask(i)) + if mask == rules.MASK_NOTUSED { + continue + } + if mask == rules.MASK_OPTIONAL && (value == nil || fmt.Sprintf("%v", value) == "") { + continue + } + } + + if buf == "" { + buf = fmt.Sprintf("%v%s", value, util.GetSegmentTerminator(args...)) + } else { + buf = fmt.Sprintf("%v%s", value, util.DataElementSeparator) + buf + } + } + + if buf == "" { + buf = fmt.Sprintf("%s%s", r.Name(), util.GetSegmentTerminator(args...)) + } else { + buf = fmt.Sprintf("%s%s", r.Name(), util.DataElementSeparator) + buf + } + + return buf +} diff --git a/pkg/segments/ent_test.go b/pkg/segments/ent_test.go new file mode 100644 index 0000000..52b08d1 --- /dev/null +++ b/pkg/segments/ent_test.go @@ -0,0 +1,90 @@ +// Copyright 2020 The Moov Authors +// Use of this source code is governed by an Apache License +// license that can be found in the LICENSE file. + +package segments + +import ( + "testing" + + "github.com/moov-io/x12/pkg/rules" + "github.com/stretchr/testify/require" +) + +func TestForENT(t *testing.T) { + + t.Run("parsing of ent segment", func(t *testing.T) { + + seg := NewENT(nil) + + in := "ENT*85*2*INDIAN HEALTH HOSPITAL*~" + read, err := seg.Parse(in) + require.NoError(t, err) + require.Equal(t, len(in), read) + + in = "ENT*85*2*INDIAN HEALTH HOSPITAL**~" + read, err = seg.Parse(in) + require.NoError(t, err) + require.Equal(t, len(in)-1, read) + + in = "ENT*85*2*INDIAN HEALTH HOSPITAL~" + read, err = seg.Parse(in) + require.NoError(t, err) + require.Equal(t, len(in), read) + + in = "ENT" + read, err = seg.Parse(in) + require.Error(t, err) + require.Equal(t, "ent segment has not enough input data", err.Error()) + require.Equal(t, 0, read) + + in = "NMN~" + read, err = seg.Parse(in) + require.Error(t, err) + require.Equal(t, "ent segment contains invalid code", err.Error()) + require.Equal(t, 0, read) + }) + + t.Run("encoding of ent segment", func(t *testing.T) { + + seg := NewENT(nil) + + require.Equal(t, "ENT**~", seg.String()) + + in := "ENT*85*2~" + read, err := seg.Parse(in) + require.NoError(t, err) + require.Equal(t, len(in), read) + require.Equal(t, in, seg.String()) + + require.NoError(t, seg.Validate(nil)) + }) + + t.Run("parsing and encoding of ent segment with specified rule", func(t *testing.T) { + + rule := rules.ElementSetRule{ + "01": {AcceptValues: []string{"85"}}, + "03": {Mask: rules.MASK_OPTIONAL}, + "04": {Mask: rules.MASK_NOTUSED}, + } + + seg := NewENT(&rule) + + in := "ENT*85*2*INDIAN HEALTH HOSPITAL*~" + read, err := seg.Parse(in) + require.NoError(t, err) + require.Equal(t, len(in), read) + require.Equal(t, "ENT*85*2*INDIAN HEALTH HOSPITAL~", seg.String()) + + seg.SetFieldByIndex("01", "86") + err = seg.Validate(nil) + require.Error(t, err) + require.Equal(t, "ent's element (01) has invalid value, the element contains unexpected value", err.Error()) + + in = "ENT*86*2*INDIAN HEALTH HOSPITAL~" + read, err = seg.Parse(in) + require.Error(t, err) + require.Equal(t, "unable to parse ent's element (01), the element contains unexpected value", err.Error()) + require.Equal(t, 0, read) + }) +} diff --git a/pkg/segments/n1.go b/pkg/segments/n1.go new file mode 100644 index 0000000..1e80b4e --- /dev/null +++ b/pkg/segments/n1.go @@ -0,0 +1,145 @@ +// Copyright 2020 The Moov Authors +// Use of this source code is governed by an Apache License +// license that can be found in the LICENSE file. + +package segments + +import ( + "errors" + "fmt" + + "github.com/moov-io/x12/pkg/rules" + "github.com/moov-io/x12/pkg/util" +) + +func NewN1(rule *rules.ElementSetRule) SegmentInterface { + + newSegment := N1{} + + if rule == nil { + newRule := make(rules.ElementSetRule) + newSegment.SetRule(&newRule) + } else { + newSegment.SetRule(rule) + } + + return &newSegment +} + +type N1 struct { + EntityIdentifierCode string `index:"01" json:"01" xml:"01"` + OriginalName string `index:"02" json:"02,omitempty" xml:"02,omitempty"` + IdentificationCodeQualifier string `index:"03" json:"03,omitempty" xml:"03,omitempty"` + IdentificationCodeUniqueID string `index:"04" json:"04,omitempty" xml:"04,omitempty"` + Field05 string `index:"05" json:"05,omitempty" xml:"05,omitempty"` + Field06 string `index:"06" json:"06,omitempty" xml:"06,omitempty"` + + Element +} + +func (r N1) defaultMask(index int) string { + if index > 2 { + return rules.MASK_OPTIONAL + } + return rules.MASK_REQUIRED +} + +func (r N1) fieldCount() int { + return 6 +} + +func (r N1) Name() string { + return "N1" +} + +func (r *N1) SetFieldByIndex(index string, data any) error { + return util.SetFieldByIndex(r, index, data) +} + +func (r N1) GetFieldByIndex(index string) any { + return util.GetFieldByIndex(r, index) +} + +func (r *N1) Validate(rule *rules.ElementSetRule) error { + + if rule == nil { + rule = r.GetRule() + } + + for i := 1; i <= r.fieldCount(); i++ { + + idx := fmt.Sprintf("%02d", i) + + if err := util.ValidateField(r.GetFieldByIndex(idx), rule.Get(idx), r.defaultMask(i)); err != nil { + return fmt.Errorf("n1's element (%s) has invalid value, %s", idx, err.Error()) + } + } + + return nil +} + +func (r *N1) Parse(data string, args ...string) (int, error) { + + var line string + var err error + var size int + + length := util.GetRecordSize(data, args...) + codeLen := len(r.Name()) + read := codeLen + 1 + + if length < int64(read) { + return 0, errors.New("n1 segment has not enough input data") + } else { + line = data[:length] + } + + if r.Name() != data[:codeLen] { + return 0, errors.New("n1 segment contains invalid code") + } + + for i := 1; i <= r.fieldCount(); i++ { + + var value string + idx := fmt.Sprintf("%02d", i) + + if value, size, err = util.ReadField(line, read, r.GetRule().Get(idx), r.defaultMask(i), args...); err != nil { + return 0, fmt.Errorf("unable to parse n1's element (%s), %s", idx, err.Error()) + } else { + read += size + r.SetFieldByIndex(idx, value) + } + } + + return read, nil +} + +func (r N1) String(args ...string) string { + var buf string + + for i := r.fieldCount(); i > 0; i-- { + + idx := fmt.Sprintf("%02d", i) + value := r.GetFieldByIndex(idx) + + if buf == "" { + mask := r.GetRule().GetMask(idx, r.defaultMask(i)) + if mask == rules.MASK_NOTUSED { + continue + } + if mask == rules.MASK_OPTIONAL && (value == nil || fmt.Sprintf("%v", value) == "") { + continue + } + } + + if buf == "" { + buf = fmt.Sprintf("%v%s", value, util.GetSegmentTerminator(args...)) + } else { + buf = fmt.Sprintf("%v%s", value, util.DataElementSeparator) + buf + } + } + + buf = fmt.Sprintf("%s%s", r.Name(), util.DataElementSeparator) + buf + + return buf +} diff --git a/pkg/segments/n1_test.go b/pkg/segments/n1_test.go new file mode 100644 index 0000000..caf0c5a --- /dev/null +++ b/pkg/segments/n1_test.go @@ -0,0 +1,89 @@ +// Copyright 2020 The Moov Authors +// Use of this source code is governed by an Apache License +// license that can be found in the LICENSE file. + +package segments + +import ( + "testing" + + "github.com/moov-io/x12/pkg/rules" + "github.com/stretchr/testify/require" +) + +func TestForN1(t *testing.T) { + + t.Run("parsing of n1 segment", func(t *testing.T) { + + seg := NewN1(nil) + + in := "N1*3*8~" + read, err := seg.Parse(in) + require.NoError(t, err) + require.Equal(t, len(in), read) + + in = "N1*3*8*~" + read, err = seg.Parse(in) + require.NoError(t, err) + require.Equal(t, len(in), read) + + in = "N1*3*~" + read, err = seg.Parse(in) + require.NoError(t, err) + require.Equal(t, len(in), read) + + in = "N1" + read, err = seg.Parse(in) + require.Error(t, err) + require.Equal(t, "n1 segment has not enough input data", err.Error()) + require.Equal(t, 0, read) + + in = "GT~" + read, err = seg.Parse(in) + require.Error(t, err) + require.Equal(t, "n1 segment contains invalid code", err.Error()) + require.Equal(t, 0, read) + }) + + t.Run("encoding of n1 segment", func(t *testing.T) { + + seg := NewN1(nil) + + require.Equal(t, "N1**~", seg.String()) + + in := "N1*3*~" + read, err := seg.Parse(in) + require.NoError(t, err) + require.Equal(t, len(in), read) + require.Equal(t, in, seg.String()) + + require.NoError(t, seg.Validate(nil)) + }) + + t.Run("parsing and encoding of n1 segment with specified rule", func(t *testing.T) { + + rule := rules.ElementSetRule{ + "01": {AcceptValues: []string{"5"}, Mask: rules.MASK_OPTIONAL}, + "02": {Mask: rules.MASK_NOTUSED}, + } + + seg := NewN1(&rule) + + in := "N1*5*8~" + read, err := seg.Parse(in) + require.NoError(t, err) + require.Equal(t, len(in), read) + require.Equal(t, "N1*5~", seg.String()) + + seg.SetFieldByIndex("01", "6") + err = seg.Validate(nil) + require.Error(t, err) + require.Equal(t, "n1's element (01) has invalid value, the element contains unexpected value", err.Error()) + + in = "N1*6*8~" + read, err = seg.Parse(in) + require.Error(t, err) + require.Equal(t, "unable to parse n1's element (01), the element contains unexpected value", err.Error()) + require.Equal(t, 0, read) + }) +} diff --git a/pkg/segments/n2.go b/pkg/segments/n2.go new file mode 100644 index 0000000..4b6c70f --- /dev/null +++ b/pkg/segments/n2.go @@ -0,0 +1,141 @@ +// Copyright 2020 The Moov Authors +// Use of this source code is governed by an Apache License +// license that can be found in the LICENSE file. + +package segments + +import ( + "errors" + "fmt" + + "github.com/moov-io/x12/pkg/rules" + "github.com/moov-io/x12/pkg/util" +) + +func NewN2(rule *rules.ElementSetRule) SegmentInterface { + + newSegment := N2{} + + if rule == nil { + newRule := make(rules.ElementSetRule) + newSegment.SetRule(&newRule) + } else { + newSegment.SetRule(rule) + } + + return &newSegment +} + +type N2 struct { + Field01 string `index:"01" json:"01" xml:"01"` + Field02 string `index:"02" json:"02,omitempty" xml:"02,omitempty"` + + Element +} + +func (r N2) defaultMask(index int) string { + if index > 2 { + return rules.MASK_OPTIONAL + } + return rules.MASK_REQUIRED +} + +func (r N2) fieldCount() int { + return 2 +} + +func (r N2) Name() string { + return "N2" +} + +func (r *N2) SetFieldByIndex(index string, data any) error { + return util.SetFieldByIndex(r, index, data) +} + +func (r N2) GetFieldByIndex(index string) any { + return util.GetFieldByIndex(r, index) +} + +func (r *N2) Validate(rule *rules.ElementSetRule) error { + + if rule == nil { + rule = r.GetRule() + } + + for i := 1; i <= r.fieldCount(); i++ { + + idx := fmt.Sprintf("%02d", i) + + if err := util.ValidateField(r.GetFieldByIndex(idx), rule.Get(idx), r.defaultMask(i)); err != nil { + return fmt.Errorf("n2's element (%s) has invalid value, %s", idx, err.Error()) + } + } + + return nil +} + +func (r *N2) Parse(data string, args ...string) (int, error) { + + var line string + var err error + var size int + + length := util.GetRecordSize(data, args...) + codeLen := len(r.Name()) + read := codeLen + 1 + + if length < int64(read) { + return 0, errors.New("n2 segment has not enough input data") + } else { + line = data[:length] + } + + if r.Name() != data[:codeLen] { + return 0, errors.New("n2 segment contains invalid code") + } + + for i := 1; i <= r.fieldCount(); i++ { + + var value string + idx := fmt.Sprintf("%02d", i) + + if value, size, err = util.ReadField(line, read, r.GetRule().Get(idx), r.defaultMask(i), args...); err != nil { + return 0, fmt.Errorf("unable to parse n2's element (%s), %s", idx, err.Error()) + } else { + read += size + r.SetFieldByIndex(idx, value) + } + } + + return read, nil +} + +func (r N2) String(args ...string) string { + var buf string + + for i := r.fieldCount(); i > 0; i-- { + + idx := fmt.Sprintf("%02d", i) + value := r.GetFieldByIndex(idx) + + if buf == "" { + mask := r.GetRule().GetMask(idx, r.defaultMask(i)) + if mask == rules.MASK_NOTUSED { + continue + } + if mask == rules.MASK_OPTIONAL && (value == nil || fmt.Sprintf("%v", value) == "") { + continue + } + } + + if buf == "" { + buf = fmt.Sprintf("%v%s", value, util.GetSegmentTerminator(args...)) + } else { + buf = fmt.Sprintf("%v%s", value, util.DataElementSeparator) + buf + } + } + + buf = fmt.Sprintf("%s%s", r.Name(), util.DataElementSeparator) + buf + + return buf +} diff --git a/pkg/segments/nm1_test.go b/pkg/segments/nm1_test.go index 748d289..302f6c6 100644 --- a/pkg/segments/nm1_test.go +++ b/pkg/segments/nm1_test.go @@ -51,7 +51,7 @@ func TestForNM1(t *testing.T) { require.Equal(t, 0, read) }) - t.Run("encoding of n4 segment", func(t *testing.T) { + t.Run("encoding of nm1 segment", func(t *testing.T) { seg := NewNM1(nil) diff --git a/pkg/segments/rmr.go b/pkg/segments/rmr.go new file mode 100644 index 0000000..d9ed25b --- /dev/null +++ b/pkg/segments/rmr.go @@ -0,0 +1,148 @@ +// Copyright 2020 The Moov Authors +// Use of this source code is governed by an Apache License +// license that can be found in the LICENSE file. + +package segments + +import ( + "errors" + "fmt" + "github.com/moov-io/x12/pkg/rules" + "github.com/moov-io/x12/pkg/util" +) + +func NewRMR(rule *rules.ElementSetRule) SegmentInterface { + + newSegment := RMR{} + + if rule == nil { + newRule := make(rules.ElementSetRule) + newSegment.SetRule(&newRule) + } else { + newSegment.SetRule(rule) + } + + return &newSegment +} + +type RMR struct { + ReferenceIdentificationQualifier string `index:"01" json:"01" xml:"01"` + ReferenceIdentification string `index:"02" json:"02" xml:"02"` + PaymentActionCode string `index:"03" json:"03,omitempty" xml:"03,omitempty"` + MonetaryAmount1 string `index:"04" json:"04" xml:"04"` + MonetaryAmount2 string `index:"05" json:"05,omitempty" xml:"05,omitempty"` + MonetaryAmount3 string `index:"06" json:"06,omitempty" xml:"06,omitempty"` + + Element +} + +func (r RMR) defaultMask(index int) string { + mask := rules.MASK_REQUIRED + if index >= 3 { + mask = rules.MASK_OPTIONAL + } + return mask +} + +func (r RMR) fieldCount() int { + return 4 +} + +func (r RMR) Name() string { + return "RMR" +} + +func (r *RMR) SetFieldByIndex(index string, data any) error { + return util.SetFieldByIndex(r, index, data) +} + +func (r RMR) GetFieldByIndex(index string) any { + return util.GetFieldByIndex(r, index) +} + +func (r *RMR) Validate(rule *rules.ElementSetRule) error { + + if rule == nil { + rule = r.GetRule() + } + + for i := 1; i <= r.fieldCount(); i++ { + + idx := fmt.Sprintf("%02d", i) + if err := util.ValidateField(r.GetFieldByIndex(idx), rule.Get(idx), r.defaultMask(i)); err != nil { + return fmt.Errorf("rmr's element (%s) has invalid value, %s", idx, err.Error()) + } + } + + return nil +} + +func (r *RMR) Parse(data string, args ...string) (int, error) { + + var line string + var err error + var size int + + length := util.GetRecordSize(data, args...) + codeLen := len(r.Name()) + read := codeLen + 1 + + if length < int64(read) { + return 0, errors.New("rmr segment has not enough input data") + } else { + line = data[:length] + } + + if r.Name() != data[:codeLen] { + return 0, errors.New("rmr segment contains invalid code") + } + + for i := 1; i <= r.fieldCount(); i++ { + + var value string + idx := fmt.Sprintf("%02d", i) + + if value, size, err = util.ReadField(line, read, r.GetRule().Get(idx), r.defaultMask(i), args...); err != nil { + return 0, fmt.Errorf("unable to parse rmr's element (%s), %s", idx, err.Error()) + } else { + read += size + r.SetFieldByIndex(idx, value) + } + } + + return read, nil +} + +func (r RMR) String(args ...string) string { + var buf string + + for i := r.fieldCount(); i > 0; i-- { + + idx := fmt.Sprintf("%02d", i) + value := r.GetFieldByIndex(idx) + + if buf == "" { + mask := r.GetRule().GetMask(idx, r.defaultMask(i)) + if mask == rules.MASK_NOTUSED { + continue + } + if mask == rules.MASK_OPTIONAL && (value == nil || fmt.Sprintf("%v", value) == "") { + continue + } + } + + if buf == "" { + buf = fmt.Sprintf("%v%s", value, util.GetSegmentTerminator(args...)) + } else { + buf = fmt.Sprintf("%v%s", value, util.DataElementSeparator) + buf + } + } + + if buf == "" { + buf = fmt.Sprintf("%s%s", r.Name(), util.GetSegmentTerminator(args...)) + } else { + buf = fmt.Sprintf("%s%s", r.Name(), util.DataElementSeparator) + buf + } + + return buf +} diff --git a/pkg/segments/rmr_test.go b/pkg/segments/rmr_test.go new file mode 100644 index 0000000..ca17196 --- /dev/null +++ b/pkg/segments/rmr_test.go @@ -0,0 +1,90 @@ +// Copyright 2020 The Moov Authors +// Use of this source code is governed by an Apache License +// license that can be found in the LICENSE file. + +package segments + +import ( + "testing" + + "github.com/moov-io/x12/pkg/rules" + "github.com/stretchr/testify/require" +) + +func TestForRMR(t *testing.T) { + + t.Run("parsing of rmr segment", func(t *testing.T) { + + seg := NewRMR(nil) + + in := "RMR*85*2*INDIAN HEALTH HOSPITAL*~" + read, err := seg.Parse(in) + require.NoError(t, err) + require.Equal(t, len(in), read) + + in = "RMR*85*2*INDIAN HEALTH HOSPITAL**~" + read, err = seg.Parse(in) + require.NoError(t, err) + require.Equal(t, len(in)-1, read) + + in = "RMR*85*2*INDIAN HEALTH HOSPITAL~" + read, err = seg.Parse(in) + require.NoError(t, err) + require.Equal(t, len(in), read) + + in = "RMR" + read, err = seg.Parse(in) + require.Error(t, err) + require.Equal(t, "rmr segment has not enough input data", err.Error()) + require.Equal(t, 0, read) + + in = "NMN~" + read, err = seg.Parse(in) + require.Error(t, err) + require.Equal(t, "rmr segment contains invalid code", err.Error()) + require.Equal(t, 0, read) + }) + + t.Run("encoding of rmr segment", func(t *testing.T) { + + seg := NewRMR(nil) + + require.Equal(t, "RMR**~", seg.String()) + + in := "RMR*85*2~" + read, err := seg.Parse(in) + require.NoError(t, err) + require.Equal(t, len(in), read) + require.Equal(t, in, seg.String()) + + require.NoError(t, seg.Validate(nil)) + }) + + t.Run("parsing and encoding of rmr segment with specified rule", func(t *testing.T) { + + rule := rules.ElementSetRule{ + "01": {AcceptValues: []string{"85"}}, + "03": {Mask: rules.MASK_OPTIONAL}, + "04": {Mask: rules.MASK_NOTUSED}, + } + + seg := NewRMR(&rule) + + in := "RMR*85*2*INDIAN HEALTH HOSPITAL*~" + read, err := seg.Parse(in) + require.NoError(t, err) + require.Equal(t, len(in), read) + require.Equal(t, "RMR*85*2*INDIAN HEALTH HOSPITAL~", seg.String()) + + seg.SetFieldByIndex("01", "86") + err = seg.Validate(nil) + require.Error(t, err) + require.Equal(t, "rmr's element (01) has invalid value, the element contains unexpected value", err.Error()) + + in = "RMR*86*2*INDIAN HEALTH HOSPITAL~" + read, err = seg.Parse(in) + require.Error(t, err) + require.Equal(t, "unable to parse rmr's element (01), the element contains unexpected value", err.Error()) + require.Equal(t, 0, read) + }) +} diff --git a/pkg/segments/trn.go b/pkg/segments/trn.go new file mode 100644 index 0000000..4308bae --- /dev/null +++ b/pkg/segments/trn.go @@ -0,0 +1,146 @@ +// Copyright 2020 The Moov Authors +// Use of this source code is governed by an Apache License +// license that can be found in the LICENSE file. + +package segments + +import ( + "errors" + "fmt" + "github.com/moov-io/x12/pkg/rules" + "github.com/moov-io/x12/pkg/util" +) + +func NewTRN(rule *rules.ElementSetRule) SegmentInterface { + + newSegment := TRN{} + + if rule == nil { + newRule := make(rules.ElementSetRule) + newSegment.SetRule(&newRule) + } else { + newSegment.SetRule(rule) + } + + return &newSegment +} + +type TRN struct { + TraceTypeCode string `index:"01" json:"01" xml:"01"` + ReferenceIdentificationNumber string `index:"02" json:"02" xml:"02"` + Field03 string `index:"03" json:"03,omitempty" xml:"03,omitempty"` + Field04 string `index:"04" json:"04,omitempty" xml:"04,omitempty"` + + Element +} + +func (r TRN) defaultMask(index int) string { + mask := rules.MASK_REQUIRED + if index >= 3 { + mask = rules.MASK_OPTIONAL + } + return mask +} + +func (r TRN) fieldCount() int { + return 4 +} + +func (r TRN) Name() string { + return "TRN" +} + +func (r *TRN) SetFieldByIndex(index string, data any) error { + return util.SetFieldByIndex(r, index, data) +} + +func (r TRN) GetFieldByIndex(index string) any { + return util.GetFieldByIndex(r, index) +} + +func (r *TRN) Validate(rule *rules.ElementSetRule) error { + + if rule == nil { + rule = r.GetRule() + } + + for i := 1; i <= r.fieldCount(); i++ { + + idx := fmt.Sprintf("%02d", i) + if err := util.ValidateField(r.GetFieldByIndex(idx), rule.Get(idx), r.defaultMask(i)); err != nil { + return fmt.Errorf("trn's element (%s) has invalid value, %s", idx, err.Error()) + } + } + + return nil +} + +func (r *TRN) Parse(data string, args ...string) (int, error) { + + var line string + var err error + var size int + + length := util.GetRecordSize(data, args...) + codeLen := len(r.Name()) + read := codeLen + 1 + + if length < int64(read) { + return 0, errors.New("trn segment has not enough input data") + } else { + line = data[:length] + } + + if r.Name() != data[:codeLen] { + return 0, errors.New("trn segment contains invalid code") + } + + for i := 1; i <= r.fieldCount(); i++ { + + var value string + idx := fmt.Sprintf("%02d", i) + + if value, size, err = util.ReadField(line, read, r.GetRule().Get(idx), r.defaultMask(i), args...); err != nil { + return 0, fmt.Errorf("unable to parse trn's element (%s), %s", idx, err.Error()) + } else { + read += size + r.SetFieldByIndex(idx, value) + } + } + + return read, nil +} + +func (r TRN) String(args ...string) string { + var buf string + + for i := r.fieldCount(); i > 0; i-- { + + idx := fmt.Sprintf("%02d", i) + value := r.GetFieldByIndex(idx) + + if buf == "" { + mask := r.GetRule().GetMask(idx, r.defaultMask(i)) + if mask == rules.MASK_NOTUSED { + continue + } + if mask == rules.MASK_OPTIONAL && (value == nil || fmt.Sprintf("%v", value) == "") { + continue + } + } + + if buf == "" { + buf = fmt.Sprintf("%v%s", value, util.GetSegmentTerminator(args...)) + } else { + buf = fmt.Sprintf("%v%s", value, util.DataElementSeparator) + buf + } + } + + if buf == "" { + buf = fmt.Sprintf("%s%s", r.Name(), util.GetSegmentTerminator(args...)) + } else { + buf = fmt.Sprintf("%s%s", r.Name(), util.DataElementSeparator) + buf + } + + return buf +} diff --git a/pkg/segments/trn_test.go b/pkg/segments/trn_test.go new file mode 100644 index 0000000..4d49ab8 --- /dev/null +++ b/pkg/segments/trn_test.go @@ -0,0 +1,90 @@ +// Copyright 2020 The Moov Authors +// Use of this source code is governed by an Apache License +// license that can be found in the LICENSE file. + +package segments + +import ( + "testing" + + "github.com/moov-io/x12/pkg/rules" + "github.com/stretchr/testify/require" +) + +func TestForTRN(t *testing.T) { + + t.Run("parsing of trn segment", func(t *testing.T) { + + seg := NewTRN(nil) + + in := "TRN*85*2*INDIAN HEALTH HOSPITAL*~" + read, err := seg.Parse(in) + require.NoError(t, err) + require.Equal(t, len(in), read) + + in = "TRN*85*2*INDIAN HEALTH HOSPITAL**~" + read, err = seg.Parse(in) + require.NoError(t, err) + require.Equal(t, len(in)-1, read) + + in = "TRN*85*2*INDIAN HEALTH HOSPITAL~" + read, err = seg.Parse(in) + require.NoError(t, err) + require.Equal(t, len(in), read) + + in = "TRN" + read, err = seg.Parse(in) + require.Error(t, err) + require.Equal(t, "trn segment has not enough input data", err.Error()) + require.Equal(t, 0, read) + + in = "NMN~" + read, err = seg.Parse(in) + require.Error(t, err) + require.Equal(t, "trn segment contains invalid code", err.Error()) + require.Equal(t, 0, read) + }) + + t.Run("encoding of trn segment", func(t *testing.T) { + + seg := NewTRN(nil) + + require.Equal(t, "TRN**~", seg.String()) + + in := "TRN*85*2~" + read, err := seg.Parse(in) + require.NoError(t, err) + require.Equal(t, len(in), read) + require.Equal(t, in, seg.String()) + + require.NoError(t, seg.Validate(nil)) + }) + + t.Run("parsing and encoding of trn segment with specified rule", func(t *testing.T) { + + rule := rules.ElementSetRule{ + "01": {AcceptValues: []string{"85"}}, + "03": {Mask: rules.MASK_OPTIONAL}, + "04": {Mask: rules.MASK_NOTUSED}, + } + + seg := NewTRN(&rule) + + in := "TRN*85*2*INDIAN HEALTH HOSPITAL*~" + read, err := seg.Parse(in) + require.NoError(t, err) + require.Equal(t, len(in), read) + require.Equal(t, "TRN*85*2*INDIAN HEALTH HOSPITAL~", seg.String()) + + seg.SetFieldByIndex("01", "86") + err = seg.Validate(nil) + require.Error(t, err) + require.Equal(t, "trn's element (01) has invalid value, the element contains unexpected value", err.Error()) + + in = "TRN*86*2*INDIAN HEALTH HOSPITAL~" + read, err = seg.Parse(in) + require.Error(t, err) + require.Equal(t, "unable to parse trn's element (01), the element contains unexpected value", err.Error()) + require.Equal(t, 0, read) + }) +} diff --git a/pkg/segments/types.go b/pkg/segments/types.go index 0f00bec..36990cf 100644 --- a/pkg/segments/types.go +++ b/pkg/segments/types.go @@ -68,6 +68,7 @@ var ( _ SegmentInterface = (*DN1)(nil) _ SegmentInterface = (*DN2)(nil) _ SegmentInterface = (*DTP)(nil) + _ SegmentInterface = (*ENT)(nil) _ SegmentInterface = (*GE)(nil) _ SegmentInterface = (*GS)(nil) _ SegmentInterface = (*HCP)(nil) @@ -77,6 +78,8 @@ var ( _ SegmentInterface = (*ISA)(nil) _ SegmentInterface = (*LX)(nil) _ SegmentInterface = (*MOA)(nil) + _ SegmentInterface = (*N1)(nil) + _ SegmentInterface = (*N2)(nil) _ SegmentInterface = (*N3)(nil) _ SegmentInterface = (*N4)(nil) _ SegmentInterface = (*NM1)(nil) @@ -87,6 +90,7 @@ var ( _ SegmentInterface = (*PRV)(nil) _ SegmentInterface = (*PWK)(nil) _ SegmentInterface = (*REF)(nil) + _ SegmentInterface = (*RMR)(nil) _ SegmentInterface = (*SBR)(nil) _ SegmentInterface = (*SE)(nil) _ SegmentInterface = (*ST)(nil) @@ -95,6 +99,7 @@ var ( _ SegmentInterface = (*SV5)(nil) _ SegmentInterface = (*SVD)(nil) _ SegmentInterface = (*TOO)(nil) + _ SegmentInterface = (*TRN)(nil) ) type constructorFunc func(rule *rules.ElementSetRule) SegmentInterface @@ -113,6 +118,7 @@ var ( "DN1": func(rule *rules.ElementSetRule) SegmentInterface { return NewDN1(rule) }, "DN2": func(rule *rules.ElementSetRule) SegmentInterface { return NewDN2(rule) }, "DTP": func(rule *rules.ElementSetRule) SegmentInterface { return NewDTP(rule) }, + "ENT": func(rule *rules.ElementSetRule) SegmentInterface { return NewENT(rule) }, "GE": func(rule *rules.ElementSetRule) SegmentInterface { return NewGE(rule) }, "GS": func(rule *rules.ElementSetRule) SegmentInterface { return NewGS(rule) }, "HCP": func(rule *rules.ElementSetRule) SegmentInterface { return NewHCP(rule) }, @@ -122,6 +128,8 @@ var ( "ISA": func(rule *rules.ElementSetRule) SegmentInterface { return NewISA(rule) }, "LX": func(rule *rules.ElementSetRule) SegmentInterface { return NewLX(rule) }, "MOA": func(rule *rules.ElementSetRule) SegmentInterface { return NewMOA(rule) }, + "N1": func(rule *rules.ElementSetRule) SegmentInterface { return NewN1(rule) }, + "N2": func(rule *rules.ElementSetRule) SegmentInterface { return NewN2(rule) }, "N3": func(rule *rules.ElementSetRule) SegmentInterface { return NewN3(rule) }, "N4": func(rule *rules.ElementSetRule) SegmentInterface { return NewN4(rule) }, "NM1": func(rule *rules.ElementSetRule) SegmentInterface { return NewNM1(rule) }, @@ -132,6 +140,7 @@ var ( "PRV": func(rule *rules.ElementSetRule) SegmentInterface { return NewPRV(rule) }, "PWK": func(rule *rules.ElementSetRule) SegmentInterface { return NewPWK(rule) }, "REF": func(rule *rules.ElementSetRule) SegmentInterface { return NewREF(rule) }, + "RMR": func(rule *rules.ElementSetRule) SegmentInterface { return NewRMR(rule) }, "SBR": func(rule *rules.ElementSetRule) SegmentInterface { return NewSBR(rule) }, "SE": func(rule *rules.ElementSetRule) SegmentInterface { return NewSE(rule) }, "ST": func(rule *rules.ElementSetRule) SegmentInterface { return NewST(rule) }, @@ -140,6 +149,7 @@ var ( "SV5": func(rule *rules.ElementSetRule) SegmentInterface { return NewSV5(rule) }, "SVD": func(rule *rules.ElementSetRule) SegmentInterface { return NewSVD(rule) }, "TOO": func(rule *rules.ElementSetRule) SegmentInterface { return NewTOO(rule) }, + "TRN": func(rule *rules.ElementSetRule) SegmentInterface { return NewTRN(rule) }, } ) diff --git a/pkg/segments/types_test.go b/pkg/segments/types_test.go index e92b3fd..1db9933 100644 --- a/pkg/segments/types_test.go +++ b/pkg/segments/types_test.go @@ -87,6 +87,16 @@ func TestForCreateSegment(t *testing.T) { require.NotNil(t, seg) require.Equal(t, "MOA", seg.Name()) + seg, err = CreateSegment("N1", nil) + require.NoError(t, err) + require.NotNil(t, seg) + require.Equal(t, "N1", seg.Name()) + + seg, err = CreateSegment("N2", nil) + require.NoError(t, err) + require.NotNil(t, seg) + require.Equal(t, "N2", seg.Name()) + seg, err = CreateSegment("N3", nil) require.NoError(t, err) require.NotNil(t, seg) @@ -137,6 +147,11 @@ func TestForCreateSegment(t *testing.T) { require.NotNil(t, seg) require.Equal(t, "REF", seg.Name()) + seg, err = CreateSegment("RMR", nil) + require.NoError(t, err) + require.NotNil(t, seg) + require.Equal(t, "RMR", seg.Name()) + seg, err = CreateSegment("SBR", nil) require.NoError(t, err) require.NotNil(t, seg) @@ -201,4 +216,9 @@ func TestForCreateSegment(t *testing.T) { require.NoError(t, err) require.NotNil(t, seg) require.Equal(t, "TOO", seg.Name()) + + seg, err = CreateSegment("TRN", nil) + require.NoError(t, err) + require.NotNil(t, seg) + require.Equal(t, "TRN", seg.Name()) } diff --git a/rules/rule_4010_820/rule.go b/rules/rule_4010_820/rule.go index 44e6d0b..49b9de1 100644 --- a/rules/rule_4010_820/rule.go +++ b/rules/rule_4010_820/rule.go @@ -10,7 +10,7 @@ import "github.com/moov-io/x12/pkg/rules" var L1000ARule = rules.SegmentSetRule{ 0: rules.SegmentRule{ - Name: "NM1", + Name: "N1", Description: "PREMIUM RECEIVER'S NAME", Mask: rules.MASK_REQUIRED, Elements: rules.ElementSetRule{ @@ -20,13 +20,10 @@ var L1000ARule = rules.SegmentSetRule{ "04": {Mask: rules.MASK_OPTIONAL}, "05": {Mask: rules.MASK_NOTUSED}, "06": {Mask: rules.MASK_NOTUSED}, - "07": {Mask: rules.MASK_NOTUSED}, - "08": {Mask: rules.MASK_NOTUSED}, - "09": {Mask: rules.MASK_NOTUSED}, }, }, 1: rules.SegmentRule{ - Name: "NM2", + Name: "N2", Description: "PREMIUM RECEIVER ADDITIONAL NAME", Mask: rules.MASK_OPTIONAL, Elements: rules.ElementSetRule{ @@ -35,7 +32,7 @@ var L1000ARule = rules.SegmentSetRule{ }, }, 2: rules.SegmentRule{ - Name: "NM3", + Name: "N3", Description: "PREMIUM RECEIVER'S ADDRESS", Mask: rules.MASK_OPTIONAL, Elements: rules.ElementSetRule{ @@ -44,7 +41,7 @@ var L1000ARule = rules.SegmentSetRule{ }, }, 3: rules.SegmentRule{ - Name: "NM4", + Name: "N4", Description: "PREMIUM RECEIVER'S CITY", Mask: rules.MASK_OPTIONAL, Elements: rules.ElementSetRule{ @@ -60,7 +57,7 @@ var L1000ARule = rules.SegmentSetRule{ var L1000BRule = rules.SegmentSetRule{ 0: rules.SegmentRule{ - Name: "NM1", + Name: "N1", Description: "PREMIUM PAYER'S NAME", Mask: rules.MASK_REQUIRED, Elements: rules.ElementSetRule{ @@ -70,13 +67,10 @@ var L1000BRule = rules.SegmentSetRule{ "04": {Mask: rules.MASK_OPTIONAL}, "05": {Mask: rules.MASK_NOTUSED}, "06": {Mask: rules.MASK_NOTUSED}, - "07": {Mask: rules.MASK_NOTUSED}, - "08": {Mask: rules.MASK_NOTUSED}, - "09": {Mask: rules.MASK_NOTUSED}, }, }, 1: rules.SegmentRule{ - Name: "NM2", + Name: "N2", Description: "PREMIUM PAYER ADDITIONAL NAME", Mask: rules.MASK_OPTIONAL, Elements: rules.ElementSetRule{ @@ -85,7 +79,7 @@ var L1000BRule = rules.SegmentSetRule{ }, }, 2: rules.SegmentRule{ - Name: "NM3", + Name: "N3", Description: "PREMIUM PAYER'S ADDRESS", Mask: rules.MASK_OPTIONAL, Elements: rules.ElementSetRule{ @@ -94,7 +88,7 @@ var L1000BRule = rules.SegmentSetRule{ }, }, 3: rules.SegmentRule{ - Name: "NM4", + Name: "N4", Description: "PREMIUM PAYER'S CITY", Mask: rules.MASK_OPTIONAL, Elements: rules.ElementSetRule{ diff --git a/rules/rule_stp_820/rule.go b/rules/rule_stp_820/rule.go index 5660e84..e9f150e 100644 --- a/rules/rule_stp_820/rule.go +++ b/rules/rule_stp_820/rule.go @@ -60,7 +60,7 @@ var TransactionSetRule = rules.TransactionRule{ }, }, 2: rules.SegmentRule{ - Name: "NM1", + Name: "N1", Description: "Originator Name Identification", Mask: rules.MASK_REQUIRED, Elements: rules.ElementSetRule{ @@ -70,13 +70,10 @@ var TransactionSetRule = rules.TransactionRule{ "04": {Mask: rules.MASK_OPTIONAL}, "05": {Mask: rules.MASK_NOTUSED}, "06": {Mask: rules.MASK_NOTUSED}, - "07": {Mask: rules.MASK_NOTUSED}, - "08": {Mask: rules.MASK_NOTUSED}, - "09": {Mask: rules.MASK_NOTUSED}, }, }, 3: rules.SegmentRule{ - Name: "NM1", + Name: "N1", Description: "Receiver Name Identification", Mask: rules.MASK_REQUIRED, Elements: rules.ElementSetRule{ @@ -86,9 +83,6 @@ var TransactionSetRule = rules.TransactionRule{ "04": {Mask: rules.MASK_NOTUSED}, "05": {Mask: rules.MASK_NOTUSED}, "06": {Mask: rules.MASK_NOTUSED}, - "07": {Mask: rules.MASK_NOTUSED}, - "08": {Mask: rules.MASK_NOTUSED}, - "09": {Mask: rules.MASK_NOTUSED}, }, }, },