Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit f003f99

Browse filesBrowse files
authored
Adding csv2 decl validation (#174)
1 parent 2a466fc commit f003f99
Copy full SHA for f003f99

File tree

Expand file treeCollapse file tree

2 files changed

+202
-0
lines changed
Filter options
Expand file treeCollapse file tree

2 files changed

+202
-0
lines changed
+72Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package csv
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/jf-tech/go-corelib/caches"
7+
"github.com/jf-tech/go-corelib/strs"
8+
)
9+
10+
type validateCtx struct {
11+
seenTarget bool
12+
}
13+
14+
func (ctx *validateCtx) validateFileDecl(fileDecl *FileDecl) error {
15+
for _, decl := range fileDecl.Records {
16+
if err := ctx.validateRecordDecl(decl.Name, decl); err != nil {
17+
return err
18+
}
19+
}
20+
if !ctx.seenTarget && len(fileDecl.Records) > 0 {
21+
// for easy of use and convenience, if no is_target=true record is specified, then
22+
// the first one will be automatically designated as target record.
23+
fileDecl.Records[0].IsTarget = true
24+
}
25+
return nil
26+
}
27+
28+
func (ctx *validateCtx) validateRecordDecl(fqdn string, decl *RecordDecl) (err error) {
29+
decl.fqdn = fqdn
30+
if decl.Header != nil {
31+
if decl.headerRegexp, err = caches.GetRegex(*decl.Header); err != nil {
32+
return fmt.Errorf(
33+
"record/record_group '%s' has an invalid 'header' regexp '%s': %s",
34+
fqdn, *decl.Header, err.Error())
35+
}
36+
}
37+
if decl.Footer != nil {
38+
if decl.footerRegexp, err = caches.GetRegex(*decl.Footer); err != nil {
39+
return fmt.Errorf(
40+
"record/record_group '%s' has an invalid 'footer' regexp '%s': %s",
41+
fqdn, *decl.Footer, err.Error())
42+
}
43+
}
44+
if decl.Group() {
45+
if len(decl.Columns) > 0 {
46+
return fmt.Errorf("record_group '%s' must not have any columns", fqdn)
47+
}
48+
if len(decl.Children) <= 0 {
49+
return fmt.Errorf(
50+
"record_group '%s' must have at least one child record/record_group", fqdn)
51+
}
52+
}
53+
if decl.Target() {
54+
if ctx.seenTarget {
55+
return fmt.Errorf(
56+
"a second record/record_group ('%s') with 'is_target' = true is not allowed",
57+
fqdn)
58+
}
59+
ctx.seenTarget = true
60+
}
61+
if decl.MinOccurs() > decl.MaxOccurs() {
62+
return fmt.Errorf("record/record_group '%s' has 'min' value %d > 'max' value %d",
63+
fqdn, decl.MinOccurs(), decl.MaxOccurs())
64+
}
65+
for _, c := range decl.Children {
66+
if err = ctx.validateRecordDecl(strs.BuildFQDN2("/", fqdn, c.Name), c); err != nil {
67+
return err
68+
}
69+
}
70+
decl.childRecDecls = toFlatFileRecDecls(decl.Children)
71+
return nil
72+
}
+130Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
package csv
2+
3+
import (
4+
"testing"
5+
6+
"github.com/jf-tech/go-corelib/strs"
7+
"github.com/jf-tech/go-corelib/testlib"
8+
"github.com/stretchr/testify/assert"
9+
)
10+
11+
func TestValidateFileDecl_AutoTargetFirstRecord(t *testing.T) {
12+
decl := &FileDecl{
13+
Records: []*RecordDecl{
14+
{Name: "A"},
15+
{Name: "B"},
16+
},
17+
}
18+
assert.False(t, decl.Records[0].Target())
19+
assert.False(t, decl.Records[1].Target())
20+
err := (&validateCtx{}).validateFileDecl(decl)
21+
assert.NoError(t, err)
22+
assert.True(t, decl.Records[0].Target())
23+
assert.False(t, decl.Records[1].Target())
24+
}
25+
26+
func TestValidateFileDecl_InvalidHeaderRegexp(t *testing.T) {
27+
err := (&validateCtx{}).validateFileDecl(&FileDecl{
28+
Records: []*RecordDecl{
29+
{Name: "A", Header: strs.StrPtr("[invalid")},
30+
},
31+
})
32+
assert.Error(t, err)
33+
assert.Equal(t,
34+
"record/record_group 'A' has an invalid 'header' regexp '[invalid': error parsing regexp: missing closing ]: `[invalid`",
35+
err.Error())
36+
}
37+
38+
func TestValidateFileDecl_InvalidFooterRegexp(t *testing.T) {
39+
err := (&validateCtx{}).validateFileDecl(&FileDecl{
40+
Records: []*RecordDecl{
41+
{Name: "A", Footer: strs.StrPtr("[invalid")},
42+
},
43+
})
44+
assert.Error(t, err)
45+
assert.Equal(t,
46+
"record/record_group 'A' has an invalid 'footer' regexp '[invalid': error parsing regexp: missing closing ]: `[invalid`",
47+
err.Error())
48+
}
49+
50+
func TestValidateFileDecl_GroupHasColumns(t *testing.T) {
51+
err := (&validateCtx{}).validateFileDecl(&FileDecl{
52+
Records: []*RecordDecl{
53+
{
54+
Name: "A",
55+
Type: strs.StrPtr(typeGroup),
56+
Columns: []*ColumnDecl{{}},
57+
Children: []*RecordDecl{{}},
58+
},
59+
},
60+
})
61+
assert.Error(t, err)
62+
assert.Equal(t, `record_group 'A' must not have any columns`, err.Error())
63+
}
64+
65+
func TestValidateFileDecl_GroupHasNoChildren(t *testing.T) {
66+
err := (&validateCtx{}).validateFileDecl(&FileDecl{
67+
Records: []*RecordDecl{
68+
{Name: "A", Type: strs.StrPtr(typeGroup), IsTarget: true},
69+
},
70+
})
71+
assert.Error(t, err)
72+
assert.Equal(t,
73+
`record_group 'A' must have at least one child record/record_group`, err.Error())
74+
}
75+
76+
func TestValidateFileDecl_TwoIsTarget(t *testing.T) {
77+
err := (&validateCtx{}).validateFileDecl(&FileDecl{
78+
Records: []*RecordDecl{
79+
{Name: "A", IsTarget: true},
80+
{Name: "B", Type: strs.StrPtr(typeGroup), Children: []*RecordDecl{
81+
{Name: "C", IsTarget: true},
82+
}},
83+
},
84+
})
85+
assert.Error(t, err)
86+
assert.Equal(t,
87+
`a second record/record_group ('B/C') with 'is_target' = true is not allowed`,
88+
err.Error())
89+
}
90+
91+
func TestValidateFileDecl_MinGreaterThanMax(t *testing.T) {
92+
err := (&validateCtx{}).validateFileDecl(&FileDecl{
93+
Records: []*RecordDecl{
94+
{Name: "A", Children: []*RecordDecl{
95+
{Name: "B", Min: testlib.IntPtr(2), Max: testlib.IntPtr(1)}}},
96+
},
97+
})
98+
assert.Error(t, err)
99+
assert.Equal(t, `record/record_group 'A/B' has 'min' value 2 > 'max' value 1`, err.Error())
100+
}
101+
102+
func TestValidateFileDecl_Success(t *testing.T) {
103+
col1 := &ColumnDecl{Name: "c1"}
104+
col2 := &ColumnDecl{Name: "c2"}
105+
col3 := &ColumnDecl{Name: "c3"}
106+
fd := &FileDecl{
107+
Records: []*RecordDecl{
108+
{
109+
Name: "A",
110+
Header: strs.StrPtr("^A_BEGIN$"),
111+
Footer: strs.StrPtr("^A_END$"),
112+
Children: []*RecordDecl{
113+
{
114+
Name: "B", IsTarget: true,
115+
Columns: []*ColumnDecl{col1, col2, col3},
116+
},
117+
},
118+
},
119+
},
120+
}
121+
err := (&validateCtx{}).validateFileDecl(fd)
122+
assert.NoError(t, err)
123+
assert.Equal(t, "A", fd.Records[0].fqdn)
124+
assert.True(t, fd.Records[0].matchHeader([]byte("A_BEGIN")))
125+
assert.True(t, fd.Records[0].matchFooter([]byte("A_END")))
126+
assert.Equal(t, 1, len(fd.Records[0].childRecDecls))
127+
assert.Same(t, fd.Records[0].Children[0], fd.Records[0].childRecDecls[0].(*RecordDecl))
128+
assert.Equal(t, "A/B", fd.Records[0].Children[0].fqdn)
129+
assert.Equal(t, []*ColumnDecl{col1, col2, col3}, fd.Records[0].Children[0].Columns)
130+
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.