Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(internal/protoveneer): add protoveneer tool #9352

Merged
merged 4 commits into from
Feb 5, 2024
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
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
/logging/ @googleapis/api-logging @googleapis/api-logging-partners @googleapis/yoshi-go-admins
/profiler/ @googleapis/api-profiler @googleapis/yoshi-go-admins
/vertexai/ @googleapis/go-vertexai @googleapis/yoshi-go-admins
/internal/protoveneer/ @googleapis/yoshi-go-admins @jba @eliben

# individual release versions manifest is unowned (to avoid notifying every team)
.release-please-manifest-individual.json
Expand Down
1 change: 1 addition & 0 deletions go.work
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ use (
./internal/generated/snippets
./internal/godocfx
./internal/postprocessor
./internal/protoveneer
./iot
./kms
./language
Expand Down
23 changes: 23 additions & 0 deletions internal/protoveneer/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# The protoveneer tool

Protoveneer is an experimental tool that generates idiomatic Go types that
correspond to protocol buffer messages and enums -- a veneer on top of the proto
layer.

## Usage

Call protoveneer with a config file and a directory containing *.pb.go files:

protoveneer config.yaml ../ai/generativelanguage/apiv1beta/generativelanguagepb

That will write Go source code to the current directory, or the one specified by -outdir.

To add a license to the generated code, pass the -license flag with a filename.

See testdata/basic/config.yaml for a sample config file.

The generated code requires the "support" package. Copy this package to your
project and provide its import path as the value of `supportImportPath` in the
config file.


13 changes: 13 additions & 0 deletions internal/protoveneer/cmd/protoveneer/apache-llc.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Copyright 2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
108 changes: 108 additions & 0 deletions internal/protoveneer/cmd/protoveneer/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
// Copyright 2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
"errors"
"fmt"
"os"

"gopkg.in/yaml.v3"
)

// config holds the configuration for a package.
type config struct {
Package string
ProtoImportPath string `yaml:"protoImportPath"`
// Import path for the support package needed by the generated code.
SupportImportPath string `yaml:"supportImportPath"`

// The types to process. Only these types and the types they depend
// on will be output.
// The key is the name of the proto type.
Types map[string]*typeConfig
// Omit the types in this list, even if they would normally be output.
// Elements can be globs.
OmitTypes []string `yaml:"omitTypes"`
// Converter functions for types not in the proto package.
// Each value should be "tofunc, fromfunc"
Converters map[string]string
}

type typeConfig struct {
// The name for the veneer type, if different.
Name string
// The prefix of the proto enum values. It will be removed.
ProtoPrefix string `yaml:"protoPrefix"`
// The prefix for the veneer enum values, if different from the type name.
VeneerPrefix string `yaml:"veneerPrefix"`
// Overrides for enum values.
ValueNames map[string]string `yaml:"valueNames"`
// Overrides for field types. Map key is proto field name.
Fields map[string]fieldConfig
// Custom conversion functions: "tofunc, fromfunc"
ConvertToFrom string `yaml:"convertToFrom"`
// Doc string for the type, omitting the initial type name.
Doc string
// Verb to place after type name in doc. Default: "is".
// Ignored if Doc is non-empty.
DocVerb string `yaml:"docVerb"`
}

type fieldConfig struct {
Name string // veneer name
Type string // veneer type
// Omit from output.
Omit bool
}

func (c *config) init() {
for protoName, tc := range c.Types {
if tc == nil {
tc = &typeConfig{Name: protoName}
c.Types[protoName] = tc
}
if tc.Name == "" {
tc.Name = protoName
}
tc.init()
}
}

func (tc *typeConfig) init() {
if tc.VeneerPrefix == "" {
tc.VeneerPrefix = tc.Name
}
}

func readConfigFile(filename string) (*config, error) {
if filename == "" {
return nil, errors.New("missing config file")
}
f, err := os.Open(filename)
if err != nil {
return nil, err
}
defer f.Close()
dec := yaml.NewDecoder(f)
dec.KnownFields(true)

var c config
if err := dec.Decode(&c); err != nil {
return nil, fmt.Errorf("reading %s: %w", filename, err)
}
c.init()
return &c, nil
}
153 changes: 153 additions & 0 deletions internal/protoveneer/cmd/protoveneer/converters.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
// Copyright 2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import "fmt"

// A converter generates code to convert between a proto type and a veneer type.
type converter interface {
// genFrom returns code to convert from proto to veneer.
genFrom(string) string
// genTo returns code to convert to proto from veneer.
genTo(string) string
// These return the function argument to Transform{Slice,MapValues}, or "" if we don't need it.
genTransformFrom() string
genTransformTo() string
}

// An identityConverter does no conversion.
type identityConverter struct{}

func (identityConverter) genFrom(arg string) string { return arg }
func (identityConverter) genTo(arg string) string { return arg }
func (identityConverter) genTransformFrom() string { return "" }
func (identityConverter) genTransformTo() string { return "" }

// A derefConverter converts between T in the veneer and *T in the proto.
type derefConverter struct{}

func (derefConverter) genFrom(arg string) string { return fmt.Sprintf("support.DerefOrZero(%s)", arg) }
func (derefConverter) genTo(arg string) string { return fmt.Sprintf("support.AddrOrNil(%s)", arg) }
func (derefConverter) genTransformFrom() string { panic("can't handle deref slices") }
func (derefConverter) genTransformTo() string { panic("can't handle deref slices") }

type enumConverter struct {
protoName, veneerName string
}

func (c enumConverter) genFrom(arg string) string {
return fmt.Sprintf("%s(%s)", c.veneerName, arg)
}

func (c enumConverter) genTransformFrom() string {
return fmt.Sprintf("func(p pb.%s) %s { return %s }", c.protoName, c.veneerName, c.genFrom("p"))
}

func (c enumConverter) genTo(arg string) string {
return fmt.Sprintf("pb.%s(%s)", c.protoName, arg)
}

func (c enumConverter) genTransformTo() string {
return fmt.Sprintf("func(v %s) pb.%s { return %s }", c.veneerName, c.protoName, c.genTo("v"))
}

type protoConverter struct {
veneerName string
}

func (c protoConverter) genFrom(arg string) string {
return fmt.Sprintf("(%s{}).fromProto(%s)", c.veneerName, arg)
}

func (c protoConverter) genTransformFrom() string {
return fmt.Sprintf("(%s{}).fromProto", c.veneerName)
}

func (c protoConverter) genTo(arg string) string {
return fmt.Sprintf("%s.toProto()", arg)
}

func (c protoConverter) genTransformTo() string {
return fmt.Sprintf("(*%s).toProto", c.veneerName)
}

type customConverter struct {
toFunc, fromFunc string
}

func (c customConverter) genFrom(arg string) string {
return fmt.Sprintf("%s(%s)", c.fromFunc, arg)
}

func (c customConverter) genTransformFrom() string { return c.fromFunc }

func (c customConverter) genTo(arg string) string {
return fmt.Sprintf("%s(%s)", c.toFunc, arg)
}

func (c customConverter) genTransformTo() string { return c.toFunc }

type sliceConverter struct {
eltConverter converter
}

func (c sliceConverter) genFrom(arg string) string {
if fn := c.eltConverter.genTransformFrom(); fn != "" {
return fmt.Sprintf("support.TransformSlice(%s, %s)", arg, fn)
}
return c.eltConverter.genFrom(arg)
}

func (c sliceConverter) genTo(arg string) string {
if fn := c.eltConverter.genTransformTo(); fn != "" {
return fmt.Sprintf("support.TransformSlice(%s, %s)", arg, fn)
}
return c.eltConverter.genTo(arg)
}

func (c sliceConverter) genTransformTo() string {
panic("sliceConverter.genToSlice called")
}

func (c sliceConverter) genTransformFrom() string {
panic("sliceConverter.genFromSlice called")
}

// Only the values are converted.
type mapConverter struct {
valueConverter converter
}

func (c mapConverter) genFrom(arg string) string {
if fn := c.valueConverter.genTransformFrom(); fn != "" {
return fmt.Sprintf("support.TransformMapValues(%s, %s)", arg, fn)
}
return c.valueConverter.genFrom(arg)
}

func (c mapConverter) genTo(arg string) string {
if fn := c.valueConverter.genTransformTo(); fn != "" {
return fmt.Sprintf("support.TransformMapValues(%s, %s)", arg, fn)
}
return c.valueConverter.genTo(arg)
}

func (c mapConverter) genTransformTo() string {
panic("mapConverter.genToSlice called")
}

func (c mapConverter) genTransformFrom() string {
panic("mapConverter.genFromSlice called")
}
Loading