Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 2 additions & 7 deletions internal/confgen/confgen.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import (
"google.golang.org/protobuf/reflect/protoreflect"
"google.golang.org/protobuf/reflect/protoregistry"
"google.golang.org/protobuf/types/descriptorpb"
"google.golang.org/protobuf/types/dynamicpb"
)

// Error collection limits at each level.
Expand Down Expand Up @@ -98,9 +97,7 @@ func (gen *Generator) GenAll() error {
return err
}
// Create a validator with extension type resolver for custom predefined rules.
gen.validator, err = protovalidate.New(
protovalidate.WithExtensionTypeResolver(dynamicpb.NewTypes(prFiles)),
)
gen.validator, err = NewValidator(prFiles)
if err != nil {
return err
}
Expand All @@ -126,9 +123,7 @@ func (gen *Generator) GenWorkbook(bookSpecifiers ...string) error {
return err
}
// Create a validator with extension type resolver for custom predefined rules.
gen.validator, err = protovalidate.New(
protovalidate.WithExtensionTypeResolver(dynamicpb.NewTypes(prFiles)),
)
gen.validator, err = NewValidator(prFiles)
if err != nil {
return err
}
Expand Down
17 changes: 14 additions & 3 deletions internal/confgen/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"google.golang.org/protobuf/reflect/protoreflect"
"google.golang.org/protobuf/reflect/protoregistry"
"google.golang.org/protobuf/types/descriptorpb"
"google.golang.org/protobuf/types/dynamicpb"
)

var fieldOptionsPool *sync.Pool
Expand Down Expand Up @@ -314,9 +315,19 @@ func parseOutputFormats(msg proto.Message, opt *options.ConfOutputOption) []form
return format.OutputFormats
}

// validate validates a proto message using the provided validator. Each violation
// NewValidator creates a protovalidate validator whose extension type resolver
// is built from prFiles, so custom predefined rules can be recognized. It is
// shared by both the confgen (generate) and load (checker) paths to keep
// validation behavior consistent.
func NewValidator(prFiles *protoregistry.Files) (protovalidate.Validator, error) {
return protovalidate.New(
protovalidate.WithExtensionTypeResolver(dynamicpb.NewTypes(prFiles)),
)
}

// Validate validates a proto message using the provided validator. Each violation
// is wrapped as an E2027 error and all violations are joined together.
func validate(msg proto.Message, validator protovalidate.Validator) error {
func Validate(msg proto.Message, validator protovalidate.Validator) error {
err := validator.Validate(msg)
if err == nil {
return nil
Expand All @@ -342,7 +353,7 @@ func validate(msg proto.Message, validator protovalidate.Validator) error {

// storeMessage stores a message to one or multiple file formats.
func storeMessage(msg proto.Message, name, locationName, outputDir string, opt *options.ConfOutputOption, validator protovalidate.Validator) error {
if err := validate(msg, validator); err != nil {
if err := Validate(msg, validator); err != nil {
return err
}
outputDir = filepath.Join(outputDir, opt.Subdir)
Expand Down
2 changes: 1 addition & 1 deletion internal/confgen/util_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -412,7 +412,7 @@ func Test_validate(t *testing.T) {
if err != nil {
t.Fatalf("failed to create validator: %v", err)
}
err = validate(tt.args.msg, validator)
err = Validate(tt.args.msg, validator)
if (err != nil) != tt.wantErr {
t.Errorf("validate() error = %v, wantErr %v", err, tt.wantErr)
return
Expand Down
11 changes: 11 additions & 0 deletions load/load.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"context"
"path/filepath"

"buf.build/go/protovalidate"
"github.com/tableauio/tableau/format"
"github.com/tableauio/tableau/internal/confgen"
"github.com/tableauio/tableau/internal/importer"
Expand All @@ -24,6 +25,16 @@ import (

// LoadMessagerInDir loads message's content in the given dir, based on format and messager options.
func LoadMessagerInDir(msg proto.Message, dir string, fmt format.Format, opts *MessagerOptions) error {
if err := loadMessagerInDir(msg, dir, fmt, opts); err != nil {
return err
}
// protovalidate the fully-assembled message. protovalidate operates on the
// final in-memory protobuf, so both input (excel/csv/xml/yaml) and output
// (json/binpb/txtpb) formats get the same coverage as confgen's storeMessage.
return confgen.Validate(msg, protovalidate.GlobalValidator)
}

func loadMessagerInDir(msg proto.Message, dir string, fmt format.Format, opts *MessagerOptions) error {
if format.IsInputFormat(fmt) {
return loadOrigin(msg, dir, opts)
}
Expand Down
22 changes: 22 additions & 0 deletions load/load_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,28 @@ func TestLoadJSON_E0002(t *testing.T) {
t.Logf("error: %s", xerrors.NewDesc(err).String())
}

// TestLoadProtovalidate guards the protovalidate step in LoadMessagerInDir:
// protovalidate operates on the final in-memory message, so it must run for
// both input (excel/csv/xml/yaml) and output (json/binpb/txtpb) formats.
// ValidateConf constrains Name to max_len:10, and each fixture supplies a
// longer value, so a fully-assembled message must surface an E2027 violation.
// This catches regressions if the confgen.Validate call is ever dropped.
func TestLoadProtovalidate(t *testing.T) {
t.Run("input-format-csv", func(t *testing.T) {
err := LoadMessagerInDir(&unittestpb.ValidateConf{}, "../testdata/", format.CSV, &MessagerOptions{})
require.Error(t, err)
require.ErrorIs(t, err, xerrors.ErrE2027)
t.Logf("error: %s", xerrors.NewDesc(err).String())
})

t.Run("output-format-json", func(t *testing.T) {
err := LoadMessagerInDir(&unittestpb.ValidateConf{}, "../testdata/unittest/conf/", format.JSON, &MessagerOptions{})
require.Error(t, err)
require.ErrorIs(t, err, xerrors.ErrE2027)
t.Logf("error: %s", xerrors.NewDesc(err).String())
})
}

func TestLoadWithPatch(t *testing.T) {
type args struct {
msg proto.Message
Expand Down
4 changes: 4 additions & 0 deletions testdata/unittest/Unittest#ValidateConf.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
ID,Name,Tag,Prop
uint32,string,"[]string","map<string, int32>"
Conf ID,Conf name,Tags,Props
0,this name is too long,"a,b","x:1"
4 changes: 4 additions & 0 deletions testdata/unittest/conf/ValidateConf.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"id": 0,
"name": "this name is too long"
}