-
Notifications
You must be signed in to change notification settings - Fork 31
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add plugin runner and move utils into subpackage
- Loading branch information
1 parent
e62a38f
commit d1aa8a1
Showing
11 changed files
with
357 additions
and
78 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
package protokit | ||
|
||
import ( | ||
"github.com/golang/protobuf/proto" | ||
"github.com/golang/protobuf/protoc-gen-go/plugin" | ||
|
||
"fmt" | ||
"io" | ||
"io/ioutil" | ||
"os" | ||
) | ||
|
||
// Plugin describes an interface for running protoc code generator plugins | ||
type Plugin interface { | ||
Generate(req *plugin_go.CodeGeneratorRequest) (*plugin_go.CodeGeneratorResponse, error) | ||
} | ||
|
||
// RunPlugin runs the supplied plugin by reading input from stdin and generating output to stdout. | ||
func RunPlugin(p Plugin) error { | ||
return RunPluginWithIO(p, os.Stdin, os.Stdout) | ||
} | ||
|
||
// RunPluginWithIO runs the supplied plugin using the supplied reader and writer for IO. | ||
func RunPluginWithIO(p Plugin, r io.Reader, w io.Writer) error { | ||
req, err := readRequest(r) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
resp, err := p.Generate(req) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return writeResponse(w, resp) | ||
} | ||
|
||
func readRequest(r io.Reader) (*plugin_go.CodeGeneratorRequest, error) { | ||
data, err := ioutil.ReadAll(r) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
req := new(plugin_go.CodeGeneratorRequest) | ||
if err = proto.Unmarshal(data, req); err != nil { | ||
return nil, err | ||
} | ||
|
||
if len(req.GetFileToGenerate()) == 0 { | ||
return nil, fmt.Errorf("no files were supplied to the generator") | ||
} | ||
|
||
return req, nil | ||
} | ||
|
||
func writeResponse(w io.Writer, resp *plugin_go.CodeGeneratorResponse) error { | ||
data, err := proto.Marshal(resp) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if _, err := w.Write(data); err != nil { | ||
return err | ||
} | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
package protokit_test | ||
|
||
import ( | ||
"github.com/golang/protobuf/proto" | ||
"github.com/golang/protobuf/protoc-gen-go/plugin" | ||
"github.com/stretchr/testify/suite" | ||
|
||
"bytes" | ||
"errors" | ||
"testing" | ||
|
||
"github.com/pseudomuto/protokit" | ||
"github.com/pseudomuto/protokit/utils" | ||
) | ||
|
||
type PluginTest struct { | ||
suite.Suite | ||
} | ||
|
||
func TestPlugin(t *testing.T) { | ||
suite.Run(t, new(PluginTest)) | ||
} | ||
|
||
func (assert *PluginTest) TestRunPlugin() { | ||
fds, err := utils.LoadDescriptorSet("fixtures", "fileset.pb") | ||
assert.NoError(err) | ||
|
||
req := utils.CreateGenRequest(fds, "booking.proto", "todo.proto") | ||
data, err := proto.Marshal(req) | ||
assert.NoError(err) | ||
|
||
in := bytes.NewBuffer(data) | ||
out := new(bytes.Buffer) | ||
|
||
assert.NoError(protokit.RunPluginWithIO(new(OkPlugin), in, out)) | ||
assert.NotEmpty(out) | ||
} | ||
|
||
func (assert *PluginTest) TestRunPluginInputError() { | ||
in := bytes.NewBufferString("Not a codegen request") | ||
out := new(bytes.Buffer) | ||
|
||
err := protokit.RunPluginWithIO(nil, in, out) | ||
assert.EqualError(err, "proto: can't skip unknown wire type 6 for plugin_go.CodeGeneratorRequest") | ||
assert.Empty(out) | ||
} | ||
|
||
func (assert *PluginTest) TestRunPluginNoFilesToGenerate() { | ||
fds, err := utils.LoadDescriptorSet("fixtures", "fileset.pb") | ||
assert.NoError(err) | ||
|
||
req := utils.CreateGenRequest(fds) | ||
data, err := proto.Marshal(req) | ||
assert.NoError(err) | ||
|
||
in := bytes.NewBuffer(data) | ||
out := new(bytes.Buffer) | ||
|
||
err = protokit.RunPluginWithIO(new(ErrorPlugin), in, out) | ||
assert.EqualError(err, "no files were supplied to the generator") | ||
assert.Empty(out) | ||
} | ||
|
||
func (assert *PluginTest) TestRunPluginGeneratorError() { | ||
fds, err := utils.LoadDescriptorSet("fixtures", "fileset.pb") | ||
assert.NoError(err) | ||
|
||
req := utils.CreateGenRequest(fds, "booking.proto", "todo.proto") | ||
data, err := proto.Marshal(req) | ||
assert.NoError(err) | ||
|
||
in := bytes.NewBuffer(data) | ||
out := new(bytes.Buffer) | ||
|
||
err = protokit.RunPluginWithIO(new(ErrorPlugin), in, out) | ||
assert.EqualError(err, "generator error") | ||
assert.Empty(out) | ||
} | ||
|
||
type ErrorPlugin struct{} | ||
|
||
func (ep *ErrorPlugin) Generate(r *plugin_go.CodeGeneratorRequest) (*plugin_go.CodeGeneratorResponse, error) { | ||
return nil, errors.New("generator error") | ||
} | ||
|
||
type OkPlugin struct{} | ||
|
||
func (op *OkPlugin) Generate(r *plugin_go.CodeGeneratorRequest) (*plugin_go.CodeGeneratorResponse, error) { | ||
resp := new(plugin_go.CodeGeneratorResponse) | ||
resp.File = append(resp.File, &plugin_go.CodeGeneratorResponse_File{ | ||
Name: proto.String("myfile.out"), | ||
Content: proto.String("someoutput"), | ||
}) | ||
|
||
return resp, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
package utils | ||
|
||
import ( | ||
"github.com/golang/protobuf/proto" | ||
"github.com/golang/protobuf/protoc-gen-go/descriptor" | ||
"github.com/golang/protobuf/protoc-gen-go/plugin" | ||
|
||
"errors" | ||
"io/ioutil" | ||
"path/filepath" | ||
) | ||
|
||
// CreateGenRequest creates a codegen request from a `FileDescriptorSet` | ||
func CreateGenRequest(fds *descriptor.FileDescriptorSet, filesToGen ...string) *plugin_go.CodeGeneratorRequest { | ||
req := new(plugin_go.CodeGeneratorRequest) | ||
req.ProtoFile = fds.GetFile() | ||
|
||
for _, f := range req.GetProtoFile() { | ||
if InStringSlice(filesToGen, filepath.Base(f.GetName())) { | ||
req.FileToGenerate = append(req.FileToGenerate, f.GetName()) | ||
} | ||
} | ||
|
||
return req | ||
} | ||
|
||
// LoadDescriptorSet loads a `FileDescriptorSet` from a file on disk. Such a file can be generated using the | ||
// `--descriptor_set_out` flag with `protoc`. | ||
// | ||
// Example: | ||
// protoc --descriptor_set_out=fileset.pb --include_imports --include_source_info ./booking.proto ./todo.proto | ||
func LoadDescriptorSet(pathSegments ...string) (*descriptor.FileDescriptorSet, error) { | ||
f, err := ioutil.ReadFile(filepath.Join(pathSegments...)) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
set := new(descriptor.FileDescriptorSet) | ||
if err = proto.Unmarshal(f, set); err != nil { | ||
return nil, err | ||
} | ||
|
||
return set, nil | ||
} | ||
|
||
// FindDescriptor finds the named descriptor in the given set. Only base names are searched. The first match is | ||
// returned, on `nil` if not found | ||
func FindDescriptor(set *descriptor.FileDescriptorSet, name string) *descriptor.FileDescriptorProto { | ||
for _, pf := range set.GetFile() { | ||
if filepath.Base(pf.GetName()) == name { | ||
return pf | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// LoadDescriptor loads file descriptor protos from a file on disk, and returns the named proto descriptor. This is | ||
// useful mostly for testing purposes. | ||
func LoadDescriptor(name string, pathSegments ...string) (*descriptor.FileDescriptorProto, error) { | ||
set, err := LoadDescriptorSet(pathSegments...) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
if pf := FindDescriptor(set, name); pf != nil { | ||
return pf, nil | ||
} | ||
|
||
return nil, errors.New("FileDescriptor not found") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
package utils_test | ||
|
||
import ( | ||
"github.com/stretchr/testify/suite" | ||
|
||
"testing" | ||
|
||
"github.com/pseudomuto/protokit/utils" | ||
) | ||
|
||
type UtilsTest struct { | ||
suite.Suite | ||
} | ||
|
||
func TestUtils(t *testing.T) { | ||
suite.Run(t, new(UtilsTest)) | ||
} | ||
|
||
func (assert *UtilsTest) TestCreateGenRequest() { | ||
fds, err := utils.LoadDescriptorSet("..", "fixtures", "fileset.pb") | ||
assert.NoError(err) | ||
|
||
req := utils.CreateGenRequest(fds, "booking.proto", "todo.proto") | ||
assert.Equal([]string{"booking.proto", "todo.proto"}, req.GetFileToGenerate()) | ||
|
||
expectedProtos := []string{ | ||
"booking.proto", | ||
"google/protobuf/any.proto", | ||
"google/protobuf/timestamp.proto", | ||
"todo.proto", | ||
} | ||
|
||
for _, pf := range req.GetProtoFile() { | ||
assert.True(utils.InStringSlice(expectedProtos, pf.GetName())) | ||
} | ||
} | ||
|
||
func (assert *UtilsTest) TestLoadDescriptorSet() { | ||
set, err := utils.LoadDescriptorSet("..", "fixtures", "fileset.pb") | ||
assert.NoError(err) | ||
assert.Len(set.GetFile(), 4) | ||
|
||
assert.NotNil(utils.FindDescriptor(set, "todo.proto")) | ||
assert.Nil(utils.FindDescriptor(set, "whodis.proto")) | ||
} | ||
|
||
func (assert *UtilsTest) TestLoadDescriptorSetFileNotFound() { | ||
set, err := utils.LoadDescriptorSet("..", "fixtures", "notgonnadoit.pb") | ||
assert.Nil(set) | ||
assert.EqualError(err, "open ../fixtures/notgonnadoit.pb: no such file or directory") | ||
} | ||
|
||
func (assert *UtilsTest) TestLoadDescriptorSetMarshalError() { | ||
set, err := utils.LoadDescriptorSet("..", "fixtures", "todo.proto") | ||
assert.Nil(set) | ||
assert.EqualError(err, "proto: can't skip unknown wire type 7 for descriptor.FileDescriptorSet") | ||
} | ||
|
||
func (assert *UtilsTest) TestLoadDescriptor() { | ||
proto, err := utils.LoadDescriptor("todo.proto", "..", "fixtures", "fileset.pb") | ||
assert.NotNil(proto) | ||
assert.NoError(err) | ||
} | ||
|
||
func (assert *UtilsTest) TestLoadDescriptorFileNotFound() { | ||
proto, err := utils.LoadDescriptor("todo.proto", "..", "fixtures", "notgonnadoit.pb") | ||
assert.Nil(proto) | ||
assert.EqualError(err, "open ../fixtures/notgonnadoit.pb: no such file or directory") | ||
} | ||
|
||
func (assert *UtilsTest) TestLoadDescriptorMarshalError() { | ||
proto, err := utils.LoadDescriptor("todo.proto", "..", "fixtures", "todo.proto") | ||
assert.Nil(proto) | ||
assert.EqualError(err, "proto: can't skip unknown wire type 7 for descriptor.FileDescriptorSet") | ||
} | ||
|
||
func (assert *UtilsTest) TestLoadDescriptorDescriptorNotFound() { | ||
proto, err := utils.LoadDescriptor("nothere.proto", "..", "fixtures", "fileset.pb") | ||
assert.Nil(proto) | ||
assert.EqualError(err, "FileDescriptor not found") | ||
} |
Oops, something went wrong.