diff --git a/module/BUILD b/module/BUILD index 103322ab8..8139c259a 100644 --- a/module/BUILD +++ b/module/BUILD @@ -10,6 +10,7 @@ go_library( visibility = ["//visibility:public"], deps = [ "//templates", + "//templates/shared", "//templates/java", "//validate", "@com_github_lyft_protoc_gen_star//:protoc-gen-star", diff --git a/module/validate.go b/module/validate.go index 1ca8510f4..2f2149374 100644 --- a/module/validate.go +++ b/module/validate.go @@ -1,17 +1,20 @@ package module import ( + "strings" + "github.com/envoyproxy/protoc-gen-validate/templates" "github.com/envoyproxy/protoc-gen-validate/templates/java" + "github.com/envoyproxy/protoc-gen-validate/templates/shared" pgs "github.com/lyft/protoc-gen-star" pgsgo "github.com/lyft/protoc-gen-star/lang/go" - "strings" ) const ( - validatorName = "validator" - langParam = "lang" - moduleParam = "module" + validatorName = "validator" + langParam = "lang" + langPluginParam = "lang-plugin" + moduleParam = "module" ) type Module struct { @@ -29,13 +32,8 @@ func (m *Module) InitContext(ctx pgs.BuildContext) { func (m *Module) Name() string { return validatorName } func (m *Module) Execute(targets map[string]pgs.File, pkgs map[string]pgs.Package) []pgs.Artifact { - lang := m.Parameters().Str(langParam) - m.Assert(lang != "", "`lang` parameter must be set") module := m.Parameters().Str(moduleParam) - - // Process file-level templates - tpls := templates.Template(m.Parameters())[lang] - m.Assert(tpls != nil, "could not find templates for `lang`: ", lang) + plugin := m.resolveTemplate() for _, f := range targets { m.Push(f.Name().String()) @@ -44,8 +42,8 @@ func (m *Module) Execute(targets map[string]pgs.File, pkgs map[string]pgs.Packag m.CheckRules(msg) } - for _, tpl := range tpls { - out := templates.FilePathFor(tpl)(f, m.ctx, tpl) + for _, tpl := range plugin.Templates { + out := plugin.PathFunction(f, m.ctx, tpl) // A nil path means no output should be generated for this file - as controlled by // implementation-specific FilePathFor implementations. @@ -53,7 +51,7 @@ func (m *Module) Execute(targets map[string]pgs.File, pkgs map[string]pgs.Packag if out != nil { outPath := strings.TrimLeft(strings.ReplaceAll(out.String(), module, ""), "/") - if opts := f.Descriptor().GetOptions(); opts != nil && opts.GetJavaMultipleFiles() && lang == "java" { + if opts := f.Descriptor().GetOptions(); opts != nil && opts.GetJavaMultipleFiles() && plugin.Name == "java" { // TODO: Only Java supports multiple file generation. If more languages add multiple file generation // support, the implementation should be made more inderect. for _, msg := range f.Messages() { @@ -71,4 +69,20 @@ func (m *Module) Execute(targets map[string]pgs.File, pkgs map[string]pgs.Packag return m.Artifacts() } +func (m *Module) resolveTemplate() *shared.TemplatePlugin { + lang := m.Parameters().Str(langParam) + langPlugin := m.Parameters().Str(langPluginParam) + m.Assert(lang != "" || langPlugin != "", "`lang` parameter or `lang-plugin` must be set") + + if lang != "" { + plugin := templates.MakeTemplateForLang(m.Parameters(), lang) + m.Assert(plugin != nil, "could not find templates for `lang`: ", lang) + return plugin + } + + plugin, err := templates.MakeTemplateFromPlugin(langPlugin, m.Parameters()) + m.Assert(err == nil, err) + return plugin +} + var _ pgs.Module = (*Module)(nil) diff --git a/templates/cc/BUILD.bazel b/templates/cc/BUILD.bazel index d24051ca9..cf6890506 100644 --- a/templates/cc/BUILD.bazel +++ b/templates/cc/BUILD.bazel @@ -17,6 +17,7 @@ go_library( "msg.go", "none.go", "num.go", + "plugin.go", "register.go", "repeated.go", "string.go", diff --git a/templates/cc/plugin.go b/templates/cc/plugin.go new file mode 100644 index 000000000..fc7dad7be --- /dev/null +++ b/templates/cc/plugin.go @@ -0,0 +1,21 @@ +package cc + +import ( + "text/template" + + "github.com/envoyproxy/protoc-gen-validate/templates/shared" + pgs "github.com/lyft/protoc-gen-star" +) + +const PluginName = "cc" + +func MakePlugin(params pgs.Parameters) *shared.TemplatePlugin { + return &shared.TemplatePlugin{ + Templates: []*template.Template{ + shared.MakeTemplate("h", registerHeader, params), + shared.MakeTemplate("cc", registerModule, params), + }, + PathFunction: ccFilePath, + Name: PluginName, + } +} diff --git a/templates/cc/register.go b/templates/cc/register.go index 615932c4c..614ee551c 100644 --- a/templates/cc/register.go +++ b/templates/cc/register.go @@ -15,7 +15,7 @@ import ( "google.golang.org/protobuf/types/known/timestamppb" ) -func RegisterModule(tpl *template.Template, params pgs.Parameters) { +func registerModule(tpl *template.Template, params pgs.Parameters) { fns := CCFuncs{pgsgo.InitContext(params)} tpl.Funcs(map[string]interface{}{ @@ -91,7 +91,7 @@ func RegisterModule(tpl *template.Template, params pgs.Parameters) { template.Must(tpl.New("wrapper").Parse(wrapperTpl)) } -func RegisterHeader(tpl *template.Template, params pgs.Parameters) { +func registerHeader(tpl *template.Template, params pgs.Parameters) { fns := CCFuncs{pgsgo.InitContext(params)} tpl.Funcs(map[string]interface{}{ @@ -107,7 +107,7 @@ func RegisterHeader(tpl *template.Template, params pgs.Parameters) { // TODO(rodaine): break pgsgo dependency here (with equivalent pgscc subpackage) type CCFuncs struct{ pgsgo.Context } -func CcFilePath(f pgs.File, ctx pgsgo.Context, tpl *template.Template) *pgs.FilePath { +func ccFilePath(f pgs.File, ctx pgsgo.Context, tpl *template.Template) *pgs.FilePath { out := pgs.FilePath(f.Name().String()) out = out.SetExt(".pb.validate." + tpl.Name()) return &out diff --git a/templates/go/BUILD.bazel b/templates/go/BUILD.bazel index 8f08fc1f5..47adf7315 100644 --- a/templates/go/BUILD.bazel +++ b/templates/go/BUILD.bazel @@ -6,6 +6,7 @@ go_library( "duration.go", "file.go", "message.go", + "plugin.go", "register.go", "required.go", "timestamp.go", @@ -13,8 +14,10 @@ go_library( importpath = "github.com/envoyproxy/protoc-gen-validate/templates/go", visibility = ["//visibility:public"], deps = [ + "//templates/shared", "//templates/goshared", "@com_github_lyft_protoc_gen_star//:protoc-gen-star", + "@com_github_lyft_protoc_gen_star//lang/go", ], ) diff --git a/templates/go/plugin.go b/templates/go/plugin.go new file mode 100644 index 000000000..065dc18dc --- /dev/null +++ b/templates/go/plugin.go @@ -0,0 +1,23 @@ +package golang + +import ( + "text/template" + + "github.com/envoyproxy/protoc-gen-validate/templates/shared" + pgs "github.com/lyft/protoc-gen-star" + pgsgo "github.com/lyft/protoc-gen-star/lang/go" +) + +const PluginName = "go" + +func MakePlugin(params pgs.Parameters) *shared.TemplatePlugin { + return &shared.TemplatePlugin{ + Templates: []*template.Template{shared.MakeTemplate("go", register, params)}, + PathFunction: func(f pgs.File, ctx pgsgo.Context, tpl *template.Template) *pgs.FilePath { + out := ctx.OutputPath(f) + out = out.SetExt(".validate." + tpl.Name()) + return &out + }, + Name: PluginName, + } +} diff --git a/templates/go/register.go b/templates/go/register.go index 3085565af..dfd5d2927 100644 --- a/templates/go/register.go +++ b/templates/go/register.go @@ -3,11 +3,11 @@ package golang import ( "text/template" - "github.com/lyft/protoc-gen-star" "github.com/envoyproxy/protoc-gen-validate/templates/goshared" + pgs "github.com/lyft/protoc-gen-star" ) -func Register(tpl *template.Template, params pgs.Parameters) { +func register(tpl *template.Template, params pgs.Parameters) { goshared.Register(tpl, params) template.Must(tpl.Parse(fileTpl)) template.Must(tpl.New("required").Parse(requiredTpl)) diff --git a/templates/java/BUILD.bazel b/templates/java/BUILD.bazel index f52a202e3..1e434587b 100644 --- a/templates/java/BUILD.bazel +++ b/templates/java/BUILD.bazel @@ -15,6 +15,7 @@ go_library( "none.go", "num.go", "oneof.go", + "plugin.go", "register.go", "repeated.go", "required.go", diff --git a/templates/java/plugin.go b/templates/java/plugin.go new file mode 100644 index 000000000..2853c3a3f --- /dev/null +++ b/templates/java/plugin.go @@ -0,0 +1,18 @@ +package java + +import ( + "text/template" + + "github.com/envoyproxy/protoc-gen-validate/templates/shared" + pgs "github.com/lyft/protoc-gen-star" +) + +const PluginName = "java" + +func MakePlugin(params pgs.Parameters) *shared.TemplatePlugin { + return &shared.TemplatePlugin{ + Templates: []*template.Template{shared.MakeTemplate("java", register, params)}, + PathFunction: javaFilePath, + Name: PluginName, + } +} diff --git a/templates/java/register.go b/templates/java/register.go index 534785a24..71c67851a 100644 --- a/templates/java/register.go +++ b/templates/java/register.go @@ -16,7 +16,7 @@ import ( "google.golang.org/protobuf/types/known/timestamppb" ) -func RegisterIndex(tpl *template.Template, params pgs.Parameters) { +func registerIndex(tpl *template.Template, params pgs.Parameters) { fns := javaFuncs{pgsgo.InitContext(params)} tpl.Funcs(map[string]interface{}{ @@ -28,7 +28,7 @@ func RegisterIndex(tpl *template.Template, params pgs.Parameters) { }) } -func Register(tpl *template.Template, params pgs.Parameters) { +func register(tpl *template.Template, params pgs.Parameters) { fns := javaFuncs{pgsgo.InitContext(params)} tpl.Funcs(map[string]interface{}{ @@ -117,7 +117,7 @@ func Register(tpl *template.Template, params pgs.Parameters) { type javaFuncs struct{ pgsgo.Context } -func JavaFilePath(f pgs.File, ctx pgsgo.Context, tpl *template.Template) *pgs.FilePath { +func javaFilePath(f pgs.File, ctx pgsgo.Context, tpl *template.Template) *pgs.FilePath { // Don't generate validators for files that don't import PGV if !importsPvg(f) { return nil diff --git a/templates/pkg.go b/templates/pkg.go index 79dfa948c..cfe4b91de 100644 --- a/templates/pkg.go +++ b/templates/pkg.go @@ -1,47 +1,46 @@ package templates import ( - "text/template" + "errors" + "plugin" - "github.com/lyft/protoc-gen-star" - "github.com/lyft/protoc-gen-star/lang/go" "github.com/envoyproxy/protoc-gen-validate/templates/cc" - "github.com/envoyproxy/protoc-gen-validate/templates/go" + golang "github.com/envoyproxy/protoc-gen-validate/templates/go" "github.com/envoyproxy/protoc-gen-validate/templates/java" "github.com/envoyproxy/protoc-gen-validate/templates/shared" + pgs "github.com/lyft/protoc-gen-star" ) -type RegisterFn func(tpl *template.Template, params pgs.Parameters) -type FilePathFn func(f pgs.File, ctx pgsgo.Context, tpl *template.Template) *pgs.FilePath +type makePluginFn func(params pgs.Parameters) *shared.TemplatePlugin -func makeTemplate(ext string, fn RegisterFn, params pgs.Parameters) *template.Template { - tpl := template.New(ext) - shared.RegisterFunctions(tpl, params) - fn(tpl, params) - return tpl +func MakeTemplateForLang(params pgs.Parameters, lang string) *shared.TemplatePlugin { + switch lang { + case cc.PluginName: + return cc.MakePlugin(params) + case golang.PluginName: + return golang.MakePlugin(params) + case java.PluginName: + return java.MakePlugin(params) + default: + return nil + } } -func Template(params pgs.Parameters) map[string][]*template.Template { - return map[string][]*template.Template{ - "cc": {makeTemplate("h", cc.RegisterHeader, params), makeTemplate("cc", cc.RegisterModule, params)}, - "go": {makeTemplate("go", golang.Register, params)}, - "java": {makeTemplate("java", java.Register, params)}, +func MakeTemplateFromPlugin(path string, params pgs.Parameters) (*shared.TemplatePlugin, error) { + plug, err := plugin.Open(path) + if err != nil { + return nil, err } -} -func FilePathFor(tpl *template.Template) FilePathFn { - switch tpl.Name() { - case "h": - return cc.CcFilePath - case "cc": - return cc.CcFilePath - case "java": - return java.JavaFilePath - default: - return func(f pgs.File, ctx pgsgo.Context, tpl *template.Template) *pgs.FilePath { - out := ctx.OutputPath(f) - out = out.SetExt(".validate." + tpl.Name()) - return &out - } + symPlugin, err := plug.Lookup("Plugin") + if err != nil { + return nil, err } + + makePlugin, ok := symPlugin.(makePluginFn) + if !ok { + return nil, errors.New("loaded object has an incorrect type, expected: *TemplatePlugin") + } + + return makePlugin(params), nil } diff --git a/templates/shared/BUILD.bazel b/templates/shared/BUILD.bazel index cc7b1c824..966f64066 100644 --- a/templates/shared/BUILD.bazel +++ b/templates/shared/BUILD.bazel @@ -7,6 +7,7 @@ go_library( "disabled.go", "functions.go", "reflection.go", + "template.go", "well_known.go", ], importpath = "github.com/envoyproxy/protoc-gen-validate/templates/shared", @@ -14,6 +15,7 @@ go_library( deps = [ "//validate", "@com_github_lyft_protoc_gen_star//:protoc-gen-star", + "@com_github_lyft_protoc_gen_star//lang/go", "@org_golang_google_protobuf//proto", ], ) diff --git a/templates/shared/template.go b/templates/shared/template.go new file mode 100644 index 000000000..56f6c22b9 --- /dev/null +++ b/templates/shared/template.go @@ -0,0 +1,24 @@ +package shared + +import ( + "text/template" + + pgs "github.com/lyft/protoc-gen-star" + pgsgo "github.com/lyft/protoc-gen-star/lang/go" +) + +type RegisterFn func(tpl *template.Template, params pgs.Parameters) +type FilePathFn func(f pgs.File, ctx pgsgo.Context, tpl *template.Template) *pgs.FilePath + +type TemplatePlugin struct { + Templates []*template.Template + PathFunction FilePathFn + Name string +} + +func MakeTemplate(ext string, fn RegisterFn, params pgs.Parameters) *template.Template { + tpl := template.New(ext) + RegisterFunctions(tpl, params) + fn(tpl, params) + return tpl +} diff --git a/validate/BUILD b/validate/BUILD index 5040d640f..011f4a1f6 100644 --- a/validate/BUILD +++ b/validate/BUILD @@ -35,6 +35,7 @@ go_proto_library( name = "validate_go_proto", importpath = "github.com/envoyproxy/protoc-gen-validate/validate", proto = ":validate_proto", + gc_goopts = ["-trimpath=$(BINDIR)=>."], ) cc_library(