Merge branch 'master' into granular_endpoint_middlewares

This commit is contained in:
Ian Molee 2016-12-20 18:30:27 -08:00 коммит произвёл GitHub
Родитель d0124ce367 c248479dbc
Коммит b341b59948
41 изменённых файлов: 455 добавлений и 319 удалений

Просмотреть файл

@ -18,9 +18,10 @@ before_install:
- export PATH=$HOME/bin/:$PATH
# golang/protobuf contains protoc-gen-go which generates the .pb.go files
- go get -v github.com/golang/protobuf/...
#- go get -v github.com/golang/protobuf/... || true
- go get -v github.com/golang/protobuf/protoc-gen-go
# For http annotation support
- go get -v google.golang.org/genproto/...
- go get -v github.com/TuneLab/go-genproto/... || true
# go-bindata is used to create binary data from template files
- go get -v github.com/jteeuwen/go-bindata/...

Просмотреть файл

@ -8,12 +8,12 @@ default: truss
dependencies:
go get github.com/go-kit/kit
go get google.golang.org/genproto
go get github.com/TuneLab/go-genproto
go get github.com/golang/protobuf/{proto,protoc-gen-go}
update-dependencies:
go get -u github.com/go-kit/kit
go get -u google.golang.org/genproto
go get -u github.com/TuneLab/go-genproto
go get -u github.com/golang/protobuf/{proto,protoc-gen-go}
# Generate go files containing the all template files in []byte form

Просмотреть файл

@ -21,15 +21,15 @@ To install this software, you must:
download a release from [github](https://github.com/google/protobuf/releases)
and add to `$PATH`.
Otherwise [install from source.](https://github.com/google/protobuf)
1. Install the `proto` and `protoc-gen-go` packages. (`protoc-gen-go` must be in `$PATH`)
1. Install the `protoc-gen-go` package (must be in `$PATH`).
```
go get -u github.com/golang/protobuf/{proto,protoc-gen-go}
go get -u github.com/golang/protobuf/protoc-gen-go
```
1. Install HTTP Annotations
```
go get -u google.golang.org/genproto
go get -u github.com/TuneLab/go-genproto
```
1. Install Truss with

Просмотреть файл

@ -34,8 +34,9 @@ message LouderRequest {
}
```
The RPC calls can be annotated with HTTP transport option (endpoint name and type of request). For this we must import the google annotations library.
```
import "google.golang.org/genproto/googleapis/api/serviceconfig/annotations.proto";
import "github.com/TuneLab/go-genproto/googleapis/api/serviceconfig/annotations.proto";
service Echo {
...
@ -185,4 +186,4 @@ Executing this command will place the *.pb.go files into `$GOPATH/truss-demo/int
## Middlewares
TODO
TODO

Просмотреть файл

@ -8,7 +8,7 @@ syntax = "proto3";
// for `package echo;` truss will create the directory "echo-service".
package echo;
import "google.golang.org/genproto/googleapis/api/serviceconfig/annotations.proto";
import "github.com/TuneLab/go-genproto/googleapis/api/serviceconfig/annotations.proto";
service Echo {
// Echo "echos" the incoming string

Просмотреть файл

@ -14,8 +14,8 @@ in a sane way, even for requests not made with the generated HTTP client.
The test harness works as follows:
- Runs truss against `transport/transport-test.proto`, which generates `transport/transport-service`
- Copy `transport/handlers` into `transport/transport-service`
- Runs truss against `transport/transport-test.proto`, which generates `transport/transportpermutations-service`
- Copy `transport/handlers` into `transport/transportpermutations-service`
- Run `go test -v`
- Runs truss again against `transport/transport-test.proto` (for regeneration tests)
- Run `go test -v`

Просмотреть файл

@ -135,7 +135,7 @@ func truss(path string, options ...string) (string, error) {
return string(out), err
}
// buildTestService builds a truss service with the package TEST
// buildTestService builds a truss service with the package "test"
// into the `serviceDir`/bin directory
func buildTestService(serviceDir string) (err error) {
@ -156,14 +156,14 @@ func buildTestService(serviceDir string) (err error) {
return err
}
const serverPath = "/TEST-service/TEST-server"
const clientPath = "/TEST-service/TEST-cli-client"
const serverPath = "/test-service/test-server"
const clientPath = "/test-service/test-cli-client"
// Build server and client
errChan := make(chan error)
go goBuild("TEST-server", binDir, filepath.Join(relDir, serverPath), errChan)
go goBuild("TEST-cli-client", binDir, filepath.Join(relDir, clientPath), errChan)
go goBuild("test-server", binDir, filepath.Join(relDir, serverPath), errChan)
go goBuild("test-cli-client", binDir, filepath.Join(relDir, clientPath), errChan)
err = <-errChan
if err != nil {
@ -218,12 +218,12 @@ func goBuild(name, outputPath, relCodePath string, errChan chan error) {
errChan <- nil
}
// runServerAndClient execs a TEST-server and TEST-client and puts a
// runServerAndClient execs a test-server and test-client and puts a
// runReference to their interaction on the runRefs channel
func runServerAndClient(path string, port int, debugPort int) runReference {
// From within a folder with a truss `service`
// These are the paths to the compiled binaries
const relativeServerPath = "/bin/TEST-server"
const relativeServerPath = "/bin/test-server"
// Output buffer for the server Stdout and Stderr
serverOut := bytes.NewBuffer(nil)
@ -299,7 +299,7 @@ func runServerAndClient(path string, port int, debugPort int) runReference {
}
func runClient(path string, trans string, port int) ([]byte, bool) {
const relativeClientPath = "/bin/TEST-cli-client"
const relativeClientPath = "/bin/test-cli-client"
var client *exec.Cmd
switch trans {
@ -354,7 +354,7 @@ func cleanTests(servicesDir string) {
// removeTestFiles removes all files created by running truss and building the
// service from a single definition directory
func removeTestFiles(defDir string) {
os.RemoveAll(filepath.Join(defDir, "TEST-service"))
os.RemoveAll(filepath.Join(defDir, "test-service"))
os.RemoveAll(filepath.Join(defDir, "bin"))
os.RemoveAll(filepath.Join(defDir, "pbout"))
os.MkdirAll(filepath.Join(defDir, "pbout"), 0777)

Просмотреть файл

@ -1,10 +1,10 @@
syntax = "proto3";
package TEST;
package basic;
import "google.golang.org/genproto/googleapis/api/serviceconfig/annotations.proto";
import "github.com/TuneLab/go-genproto/googleapis/api/serviceconfig/annotations.proto";
service Basic {
service TEST {
rpc GetBasic (BasicTypeRequest) returns (BasicTypeResponse) {
option (google.api.http) = {
get: "/1"

Просмотреть файл

@ -1,12 +1,12 @@
syntax = "proto3";
package TEST;
package multifile;
import "google.golang.org/genproto/googleapis/api/serviceconfig/annotations.proto";
import "github.com/TuneLab/go-genproto/googleapis/api/serviceconfig/annotations.proto";
import "imported.proto";
service Basic {
service TEST {
rpc GetBasic (BasicTypeRequest) returns (BasicTypeResponse) {
option (google.api.http) = {
get: "/1"

Просмотреть файл

@ -1,8 +1,8 @@
syntax = "proto3";
package TEST;
package multifile;
import "google.golang.org/genproto/googleapis/api/serviceconfig/annotations.proto";
import "github.com/TuneLab/go-genproto/googleapis/api/serviceconfig/annotations.proto";
message BasicTypeRequest {
double A = 1;

Просмотреть файл

@ -1,10 +1,10 @@
syntax = "proto3";
package TEST;
package Repeated;
import "google.golang.org/genproto/googleapis/api/serviceconfig/annotations.proto";
import "github.com/TuneLab/go-genproto/googleapis/api/serviceconfig/annotations.proto";
service Repeated {
service TEST {
rpc GetRepeated (RepeatedTypeRequest) returns (RepeatedTypeResponse) {
option (google.api.http) = {
get: "/1"

Просмотреть файл

@ -1,10 +1,10 @@
syntax = "proto3";
package TEST;
package nested;
import "google.golang.org/genproto/googleapis/api/serviceconfig/annotations.proto";
import "github.com/TuneLab/go-genproto/googleapis/api/serviceconfig/annotations.proto";
service Nested {
service TEST {
rpc GetNested (NestedTypeRequest) returns (NestedTypeResponse) {
option (google.api.http) = {
get: "/1"

Просмотреть файл

@ -1,10 +1,10 @@
syntax = "proto3";
package TEST;
package map;
import "google.golang.org/genproto/googleapis/api/serviceconfig/annotations.proto";
import "github.com/TuneLab/go-genproto/googleapis/api/serviceconfig/annotations.proto";
service Map {
service TEST {
rpc GetMap (MapTypeRequest) returns (MapTypeResponse) {
option (google.api.http) = {
get: "/1"

Просмотреть файл

@ -14,7 +14,7 @@ all: test
test:
@echo -e '$(TRUSS_MSG)'
truss transport-test.proto
cp -r handlers transport-service
cp -r handlers transportpermutations-service
@echo -e '$(TEST_RUNNING_MSG)'
go test -v
@echo -e '$(TRUSS_AGAIN_MSG)'
@ -24,4 +24,4 @@ test:
$(MAKE) clean
clean:
rm -rf transport-service
rm -rf transportpermutations-service

Просмотреть файл

@ -8,8 +8,8 @@ import (
"golang.org/x/net/context"
"google.golang.org/grpc"
pb "github.com/TuneLab/go-truss/cmd/_integration-tests/transport/transport-service"
grpcclient "github.com/TuneLab/go-truss/cmd/_integration-tests/transport/transport-service/generated/client/grpc"
pb "github.com/TuneLab/go-truss/cmd/_integration-tests/transport/transportpermutations-service"
grpcclient "github.com/TuneLab/go-truss/cmd/_integration-tests/transport/transportpermutations-service/generated/client/grpc"
)
var grpcAddr string

Просмотреть файл

@ -8,7 +8,7 @@ import (
"golang.org/x/net/context"
pb "github.com/TuneLab/go-truss/cmd/_integration-tests/transport/transport-service"
pb "github.com/TuneLab/go-truss/cmd/_integration-tests/transport/transportpermutations-service"
)
// NewService returns a naïve, stateless implementation of Service.

Просмотреть файл

@ -13,8 +13,8 @@ import (
// 3d Party
"golang.org/x/net/context"
// This Service
pb "github.com/TuneLab/go-truss/cmd/_integration-tests/transport/transport-service"
httpclient "github.com/TuneLab/go-truss/cmd/_integration-tests/transport/transport-service/generated/client/http"
pb "github.com/TuneLab/go-truss/cmd/_integration-tests/transport/transportpermutations-service"
httpclient "github.com/TuneLab/go-truss/cmd/_integration-tests/transport/transportpermutations-service/generated/client/http"
"github.com/pkg/errors"
)

Просмотреть файл

@ -14,9 +14,9 @@ import (
// Go Kit
"github.com/go-kit/kit/log"
pb "github.com/TuneLab/go-truss/cmd/_integration-tests/transport/transport-service"
svc "github.com/TuneLab/go-truss/cmd/_integration-tests/transport/transport-service/generated"
handler "github.com/TuneLab/go-truss/cmd/_integration-tests/transport/transport-service/handlers/server"
pb "github.com/TuneLab/go-truss/cmd/_integration-tests/transport/transportpermutations-service"
svc "github.com/TuneLab/go-truss/cmd/_integration-tests/transport/transportpermutations-service/generated"
handler "github.com/TuneLab/go-truss/cmd/_integration-tests/transport/transportpermutations-service/handlers/server"
)
func TestMain(m *testing.M) {

Просмотреть файл

@ -2,7 +2,7 @@ syntax = "proto3";
package transport;
import "google.golang.org/genproto/googleapis/api/serviceconfig/annotations.proto";
import "github.com/TuneLab/go-genproto/googleapis/api/serviceconfig/annotations.proto";
service TransportPermutations {
rpc GetWithQuery (GetWithQueryRequest) returns (GetWithQueryResponse) {

Просмотреть файл

@ -14,7 +14,7 @@ import (
"github.com/TuneLab/go-truss/truss"
"github.com/TuneLab/go-truss/truss/execprotoc"
"github.com/TuneLab/go-truss/truss/parsepkgname"
"github.com/TuneLab/go-truss/truss/parsesvcname"
"github.com/TuneLab/go-truss/deftree"
"github.com/TuneLab/go-truss/gendoc"
@ -111,13 +111,10 @@ func parseInput() (*truss.Config, error) {
}
// Service Path
defFile, err := os.Open(cfg.DefPaths[0])
svcName, err := parsesvcname.FromPaths(cfg.GoPath, cfg.DefPaths)
svcName = strings.ToLower(svcName)
if err != nil {
return nil, errors.Wrapf(err, "cannot open package file %q", cfg.DefPaths[0])
}
svcName, err := parsepkgname.FromReader(defFile)
if err != nil {
return nil, errors.Wrapf(err, "cannot parse package name from file %q", cfg.DefPaths[0])
return nil, errors.Wrap(err, "cannot parse service name from the provided definition files")
}
svcFolderName := svcName + "-service"
@ -195,16 +192,15 @@ func parseServiceDefinition(cfg *truss.Config) (deftree.Deftree, *svcdef.Svcdef,
return nil, nil, errors.Wrap(err, "cannot create .pb.go files")
}
// Open all .pb.go files and store in slice to be passed to svcdef.New()
//var openFiles func([]string) ([]io.Reader, error)
openFiles := func(paths []string) ([]io.Reader, error) {
rv := []io.Reader{}
// Open all .pb.go files and store in map to be passed to svcdef.New()
openFiles := func(paths []string) (map[string]io.Reader, error) {
rv := map[string]io.Reader{}
for _, p := range paths {
reader, err := os.Open(p)
if err != nil {
return nil, errors.Wrapf(err, "cannot open file %q", p)
}
rv = append(rv, reader)
rv[p] = reader
}
return rv, nil
}
@ -228,7 +224,7 @@ func parseServiceDefinition(cfg *truss.Config) (deftree.Deftree, *svcdef.Svcdef,
// Create the svcdef
sd, err := svcdef.New(pbgoFiles, pbFiles)
if err != nil {
return nil, nil, errors.Wrap(err, "cannot create svcdef")
return nil, nil, errors.Wrapf(err, "failed to create service definition; did you pass ALL the protobuf files to truss?")
}
// Create the Deftree

Просмотреть файл

@ -28,7 +28,7 @@ func TestNewFromString(t *testing.T) {
// General package
package general;
import "google.golang.org/genproto/googleapis/api/serviceconfig/annotations.proto";
import "github.com/TuneLab/go-genproto/googleapis/api/serviceconfig/annotations.proto";
// RequestMessage is so foo
message RequestMessage {

Просмотреть файл

@ -7,8 +7,19 @@ import (
"strings"
"github.com/TuneLab/go-truss/deftree"
"github.com/golang/protobuf/protoc-gen-go/generator"
)
func findServiceName(md *deftree.MicroserviceDefinition) string {
rv := "default"
for _, f := range md.Files {
for _, svc := range f.Services {
rv = svc.GetName()
}
}
return rv
}
// GenerateDocs accepts a deftree that represents an ast of a group of
// protofiles and returns map[string]io.Reader that represents a relative
// filestructure of generated docs
@ -23,7 +34,10 @@ func GenerateDocs(dt deftree.Deftree) map[string]io.Reader {
}
files := make(map[string]io.Reader)
files[dt.GetName()+"-service/docs/docs.md"] = strings.NewReader(response)
md := dt.(*deftree.MicroserviceDefinition)
// Normalize the service to prevent diversion from convention
svcname := strings.ToLower(generator.CamelCase(findServiceName(md)))
files[svcname+"-service/docs/docs.md"] = strings.NewReader(response)
return files
}

Просмотреть файл

@ -31,7 +31,7 @@ func TestNewClientServiceArgs(t *testing.T) {
// General package
package general;
import "google.golang.org/genproto/googleapis/api/serviceconfig/annotations.proto";
import "github.com/TuneLab/go-genproto/googleapis/api/serviceconfig/annotations.proto";
message SumRequest {
repeated int64 a = 1;

Просмотреть файл

@ -6,6 +6,7 @@ import (
"go/format"
"io"
"io/ioutil"
"path"
"strings"
log "github.com/Sirupsen/logrus"
@ -30,8 +31,12 @@ func GenerateGokit(sd *svcdef.Svcdef, conf gengokit.Config) (map[string]io.Reade
codeGenFiles := make(map[string]io.Reader)
// Remove the suffix "-service" since it's added back in by templatePathToActual
svcname := strings.TrimSuffix(path.Base(conf.GoPackage), "-service")
for _, templPath := range templFiles.AssetNames() {
actualPath := templatePathToActual(templPath, sd.PkgName)
// Re-derive the actual path for this file based on the service output
// path provided by the truss main.go
actualPath := templatePathToActual(templPath, svcname)
file, err := generateResponseFile(templPath, data, conf.PreviousFiles[actualPath])
if err != nil {
return nil, errors.Wrap(err, "cannot render template")

Просмотреть файл

@ -49,7 +49,7 @@ func TestApplyTemplateFromPath(t *testing.T) {
// General package
package general;
import "google.golang.org/genproto/googleapis/api/serviceconfig/annotations.proto";
import "github.com/TuneLab/go-genproto/googleapis/api/serviceconfig/annotations.proto";
// RequestMessage is so foo
message RequestMessage {
@ -141,7 +141,7 @@ func TestAllTemplates(t *testing.T) {
// General package
package general;
import "google.golang.org/genproto/googleapis/api/serviceconfig/annotations.proto";
import "github.com/TuneLab/go-genproto/googleapis/api/serviceconfig/annotations.proto";
// RequestMessage is so foo
message RequestMessage {
@ -171,7 +171,7 @@ func TestAllTemplates(t *testing.T) {
// General package
package general;
import "google.golang.org/genproto/googleapis/api/serviceconfig/annotations.proto";
import "github.com/TuneLab/go-genproto/googleapis/api/serviceconfig/annotations.proto";
// RequestMessage is so foo
message RequestMessage {

Просмотреть файл

@ -21,7 +21,7 @@ func TestNewData(t *testing.T) {
// General package
package general;
import "google.golang.org/genproto/googleapis/api/serviceconfig/annotations.proto";
import "github.com/TuneLab/go-genproto/googleapis/api/serviceconfig/annotations.proto";
// RequestMessage is so foo
message RequestMessage {

Просмотреть файл

@ -38,7 +38,7 @@ func TestServerMethsTempl(t *testing.T) {
// General package
package general;
import "google.golang.org/genproto/googleapis/api/serviceconfig/annotations.proto";
import "github.com/TuneLab/go-genproto/googleapis/api/serviceconfig/annotations.proto";
// RequestMessage is so foo
message RequestMessage {
@ -98,7 +98,7 @@ func TestApplyServerTempl(t *testing.T) {
// General package
package general;
import "google.golang.org/genproto/googleapis/api/serviceconfig/annotations.proto";
import "github.com/TuneLab/go-genproto/googleapis/api/serviceconfig/annotations.proto";
// RequestMessage is so foo
message RequestMessage {
@ -190,7 +190,7 @@ func TestIsValidFunc(t *testing.T) {
// General package
package general;
import "google.golang.org/genproto/googleapis/api/serviceconfig/annotations.proto";
import "github.com/TuneLab/go-genproto/googleapis/api/serviceconfig/annotations.proto";
// RequestMessage is so foo
message RequestMessage {
@ -257,7 +257,7 @@ func TestPruneDecls(t *testing.T) {
// General package
package general;
import "google.golang.org/genproto/googleapis/api/serviceconfig/annotations.proto";
import "github.com/TuneLab/go-genproto/googleapis/api/serviceconfig/annotations.proto";
// RequestMessage is so foo
message RequestMessage {
@ -384,7 +384,7 @@ func TestUpdateMethods(t *testing.T) {
// General package
package general;
import "google.golang.org/genproto/googleapis/api/serviceconfig/annotations.proto";
import "github.com/TuneLab/go-genproto/googleapis/api/serviceconfig/annotations.proto";
// RequestMessage is so foo
message RequestMessage {

Просмотреть файл

@ -28,7 +28,7 @@ func TestNewMethod(t *testing.T) {
// General package
package general;
import "google.golang.org/genproto/googleapis/api/serviceconfig/annotations.proto";
import "github.com/TuneLab/go-genproto/googleapis/api/serviceconfig/annotations.proto";
message SumRequest {
int64 a = 1;

Просмотреть файл

@ -217,7 +217,7 @@ func generalService() (*svcdef.Svcdef, *gengokit.Data, error) {
// General package
package general;
import "google.golang.org/genproto/googleapis/api/serviceconfig/annotations.proto";
import "github.com/TuneLab/go-genproto/googleapis/api/serviceconfig/annotations.proto";
// RequestMessage is so foo
message RequestMessage {

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Просмотреть файл

@ -36,7 +36,7 @@ func isEOF(err error) bool {
// each `HTTPBinding` will have a populated list of all the http parameters
// that that binding requires, where that parameter should be located, and the
// type of each parameter.
func consolidateHTTP(sd *Svcdef, protoFiles []io.Reader) error {
func consolidateHTTP(sd *Svcdef, protoFiles map[string]io.Reader) error {
for _, pfile := range protoFiles {
lex := svcparse.NewSvcLexer(pfile)
protosvc, err := svcparse.ParseService(lex)

Просмотреть файл

@ -56,7 +56,7 @@ type MapServer interface {
protoCode := `
syntax = "proto3";
package TEST;
import "google.golang.org/genproto/googleapis/api/serviceconfig/annotations.proto";
import "github.com/TuneLab/go-genproto/googleapis/api/serviceconfig/annotations.proto";
enum EnumType {
A = 0;
@ -84,7 +84,7 @@ service Map {
}
}`
// From code, build our SvcDef
sd, err := New([]io.Reader{strings.NewReader(goCode)}, []io.Reader{strings.NewReader(protoCode)})
sd, err := New(map[string]io.Reader{"/tmp/notreal": strings.NewReader(goCode)}, map[string]io.Reader{"/tmp/alsonotreal": strings.NewReader(protoCode)})
if err != nil {
t.Fatal(err)
}

Просмотреть файл

@ -44,7 +44,13 @@ func NewFromString(def string, gopath []string) (*Svcdef, error) {
}
pbgo := string(buf)
sd, err := New([]io.Reader{strings.NewReader(pbgo)}, []io.Reader{strings.NewReader(def)})
pbgoMap := map[string]io.Reader{
"/tmp/doesntexist.pb.go": strings.NewReader(pbgo),
}
pFileMap := map[string]io.Reader{
"/tmp/doesntexist.proto": strings.NewReader(def),
}
sd, err := New(pbgoMap, pFileMap)
if err != nil {
return nil, errors.Wrap(err, "cannot create new svcdef from pb.go and definition")
}

Просмотреть файл

@ -23,7 +23,7 @@ func basicFromString(t *testing.T) *Svcdef {
// General package
package general;
import "google.golang.org/genproto/googleapis/api/serviceconfig/annotations.proto";
import "github.com/TuneLab/go-genproto/googleapis/api/serviceconfig/annotations.proto";
message SumRequest {
int64 a = 1;
@ -158,7 +158,7 @@ func TestNoHTTPBinding(t *testing.T) {
// General package
package general;
import "google.golang.org/genproto/googleapis/api/serviceconfig/annotations.proto";
import "github.com/TuneLab/go-genproto/googleapis/api/serviceconfig/annotations.proto";
message SumRequest {
int64 a = 1;

Просмотреть файл

@ -17,6 +17,7 @@ the path of an HTTP annotation.
package svcdef
import (
"fmt"
"go/ast"
"go/parser"
"go/token"
@ -135,16 +136,65 @@ func retrieveTypeSpecs(f *ast.File) ([]*ast.TypeSpec, error) {
return rv, nil
}
// DebugInfo contains context necessary for many functions to provide useful
// debugging output when encountering errors. All DebugInfo methods are
// implemented such that calling them with `nil` recievers means they'll return
// empty values. And since DebugInfo is used PURELY for creating nice error
// messages and no actual business logic depends on them, this means you can
// safely pass 'nil' DebugInfo structs to functions and you'll just get
// unhelpful error messages out, it won't break things. This is done to make
// the code more testable.
type DebugInfo struct {
Fset *token.FileSet
Path string
}
func (di *DebugInfo) Position(pos token.Pos) string {
if di == nil {
return ""
} else {
return fmt.Sprintf("%s", di.Fset.Position(pos))
}
}
// LocationError is a special kind of error, carrying special information about
// where the error was encountered within a file.
type LocationError struct {
Path string
Position string
Err string
}
func NewLocationError(err string, path string, pos string) LocationError {
return LocationError{
Path: path,
Position: pos,
Err: err,
}
}
func (le LocationError) Error() string {
return fmt.Sprintf("%s in file %q at line %s", le.Err, le.Path, le.Position)
}
func (le LocationError) Location() string {
return le.Position
}
// New creates a Svcdef by parsing the provided Go and Protobuf source files to
// derive type information, gRPC service data, and HTTP annotations.
func New(goFiles []io.Reader, protoFiles []io.Reader) (*Svcdef, error) {
func New(goFiles map[string]io.Reader, protoFiles map[string]io.Reader) (*Svcdef, error) {
rv := Svcdef{}
for _, gofile := range goFiles {
for path, gofile := range goFiles {
fset := token.NewFileSet()
fileAst, err := parser.ParseFile(fset, "", gofile, parser.ParseComments)
if err != nil {
return nil, errors.Wrap(err, "couldn't parse go file to create Svcdef")
return nil, errors.Wrapf(err, "couldn't parse go file %q to create Svcdef", path)
}
debugInfo := &DebugInfo{
Path: path,
Fset: fset,
}
rv.PkgName = fileAst.Name.Name
@ -180,7 +230,7 @@ func New(goFiles []io.Reader, protoFiles []io.Reader) (*Svcdef, error) {
if strings.HasSuffix("Client", t.Name.Name) {
break
}
nsvc, err := NewService(t)
nsvc, err := NewService(t, debugInfo)
if err != nil {
return nil, errors.Wrapf(err, "error parsing service %q", t.Name.Name)
}
@ -263,13 +313,13 @@ func NewMap(m ast.Expr) (*Map, error) {
// NewService returns a new Service struct derived from an *ast.TypeSpec with a
// Type of *ast.InterfaceType representing an "{SVCNAME}Server" interface.
func NewService(s *ast.TypeSpec) (*Service, error) {
func NewService(s *ast.TypeSpec, info *DebugInfo) (*Service, error) {
rv := &Service{
Name: strings.TrimSuffix(s.Name.Name, "Server"),
}
asvc := s.Type.(*ast.InterfaceType)
for _, m := range asvc.Methods.List {
nmeth, err := NewServiceMethod(m)
nmeth, err := NewServiceMethod(m, info)
if err != nil {
return nil, errors.Wrapf(err, "Couldn't create service method %q of service %q", m.Names[0].Name, rv.Name)
}
@ -281,13 +331,15 @@ func NewService(s *ast.TypeSpec) (*Service, error) {
// NewServiceMethod returns a new ServiceMethod derived from a method of a
// Service interface. This is accepted in the form of an *ast.Field which
// contains the name of the method.
func NewServiceMethod(m *ast.Field) (*ServiceMethod, error) {
func NewServiceMethod(m *ast.Field, info *DebugInfo) (*ServiceMethod, error) {
rv := &ServiceMethod{
Name: m.Names[0].Name,
}
ft, ok := m.Type.(*ast.FuncType)
if !ok {
return nil, errors.New("Provided *ast.Field.Type is not of type *ast.FuncType; cannot proceed")
return nil, NewLocationError("Provided *ast.Field.Type is not of type "+
"*ast.FuncType; cannot proceed",
info.Path, info.Position(m.Pos()))
}
input := ft.Params.List
@ -308,11 +360,15 @@ func NewServiceMethod(m *ast.Field) (*ServiceMethod, error) {
makeFieldType := func(in *ast.Field) (*FieldType, error) {
star, ok := in.Type.(*ast.StarExpr)
if !ok {
return nil, errors.New("could not create FieldType, in.Type is not *ast.StarExpr")
return nil, NewLocationError("could not create FieldType, in.Type "+
"is not *ast.StarExpr",
info.Path, info.Position(in.Pos()))
}
ident, ok := star.X.(*ast.Ident)
if !ok {
return nil, errors.New("could not create FieldType, star.Type is not *ast.Ident")
return nil, NewLocationError("could not create FieldType, "+
"star.Type is not *ast.Ident",
info.Path, info.Position(star.Pos()))
}
return &FieldType{
Name: ident.Name,

Просмотреть файл

@ -17,7 +17,7 @@ func TestSvcdef(t *testing.T) {
t.Fatal(err)
}
sd, err := New([]io.Reader{gf}, []io.Reader{pf})
sd, err := New(map[string]io.Reader{"./test-go.txt": gf}, map[string]io.Reader{"./test-proto.txt": pf})
if err != nil {
t.Fatal(err)
}
@ -46,7 +46,7 @@ type NestedTypeRequest struct {
B []*NestedMessageB
C EnumType
}`
sd, err := New([]io.Reader{strings.NewReader(caseCode)}, nil)
sd, err := New(map[string]io.Reader{"/tmp/notreal": strings.NewReader(caseCode)}, nil)
if err != nil {
t.Fatal(err)
}
@ -109,7 +109,7 @@ type MsgWithMap struct {
Beta map[int64]*NestedMessageC
}
`
sd, err := New([]io.Reader{strings.NewReader(caseCode)}, nil)
sd, err := New(map[string]io.Reader{"/tmp/notreal": strings.NewReader(caseCode)}, nil)
if err != nil {
t.Fatal(err)
}

Просмотреть файл

@ -3,7 +3,7 @@ syntax = "proto3";
package TEST;
import "google.golang.org/genproto/googleapis/api/serviceconfig/annotations.proto";
import "github.com/TuneLab/go-genproto/googleapis/api/serviceconfig/annotations.proto";
service Map {
rpc GetMap (MapTypeRequest) returns (MapTypeResponse) {

Просмотреть файл

@ -1,113 +0,0 @@
/*
Package parsepkgname provides functions for extracting the name of a package
from a protocol buffer definition file. For example, given a protocol buffer 3
file like this:
// A comment about this proto file
package examplepackage;
// and the rest of the file goes here
The functions in this package would extract the name "examplepackage" as the
name of the protobuf package.
*/
package parsepkgname
import (
"io"
"unicode"
"github.com/TuneLab/go-truss/deftree/svcparse"
)
type token int
const (
ident token = iota
whitespaceToken
comment
other
)
type Scanner interface {
// ReadUnit must return groups of runes representing at least the following
// lexical groups:
//
// ident
// comments (c++ style single line comments and block comments)
// whitespace
//
// If you need a scanner which provides these out of the box, see the
// SvcScanner struct in github.com/TuneLab/go-truss/deftree/svcparse
ReadUnit() ([]rune, error)
}
func categorize(unit []rune) token {
rv := other
r := unit[0]
switch {
case unicode.IsLetter(r):
rv = ident
case unicode.IsDigit(r):
rv = ident
case r == '_':
rv = ident
case unicode.IsSpace(r):
rv = whitespaceToken
case r == '/' && len(unit) > 1:
rv = comment
}
return rv
}
// FromReader accepts an io.Reader, the contents of which should be a
// valid proto3 file, and returns the name of the protobuf package which that
// file belongs to.
func FromReader(protofile io.Reader) (string, error) {
scanner := svcparse.NewSvcScanner(protofile)
return FromScanner(scanner)
}
// FromScanner accepts a Scanner for a protobuf file and returns the name of
// the protobuf package that the file belongs to.
func FromScanner(scanner Scanner) (string, error) {
foundpackage := false
// A nice way to ignore comments. Recursively calls itself until it
// recieves a unit from the scanner which is not a comment.
var readIgnoreComment func(Scanner) (token, []rune, error)
readIgnoreComment = func(scn Scanner) (token, []rune, error) {
unit, err := scn.ReadUnit()
if err != nil {
return other, nil, err
}
tkn := categorize(unit)
if tkn == comment {
return readIgnoreComment(scn)
}
return tkn, unit, err
}
// A tiny state machine to find two sequential idents: the ident "package"
// and the ident immediately following. That second ident will be the name
// of the package.
for {
tkn, unit, err := readIgnoreComment(scanner)
if err != nil {
return "", err
}
if foundpackage {
if tkn == ident {
return string(unit), nil
} else if tkn == whitespaceToken {
continue
} else {
foundpackage = false
}
} else {
if tkn == ident && string(unit) == "package" {
foundpackage = true
}
}
}
}

Просмотреть файл

@ -1,88 +0,0 @@
package parsepkgname
import (
"io"
"strings"
"testing"
)
type testScanner struct {
contents [][]rune
position int
}
func (t *testScanner) ReadUnit() ([]rune, error) {
if t.position < len(t.contents) {
rv := t.contents[t.position]
t.position += 1
return rv, nil
}
return nil, io.EOF
}
func NewTestScanner(units []string) *testScanner {
rv := testScanner{position: 0}
for _, u := range units {
rv.contents = append(rv.contents, []rune(u))
}
return &rv
}
func TestFromScanner_simple(t *testing.T) {
basicContents := []string{
"\n",
"package",
" ",
"examplename",
";",
"\n",
}
scn := NewTestScanner(basicContents)
want := "examplename"
got, err := FromScanner(scn)
if err != nil {
t.Fatal(err)
}
if got != want {
t.Fatalf("Got %q for package name, want %q", got, want)
}
}
func TestFromScanner_mid_comment(t *testing.T) {
contents := []string{
"\n",
"package",
" ",
"/* comment in the middle of the declaration */",
"examplename",
";",
"\n",
}
scn := NewTestScanner(contents)
want := "examplename"
got, err := FromScanner(scn)
if err != nil {
t.Fatal(err)
}
if got != want {
t.Fatalf("Got %q for package name, want %q", got, want)
}
}
func TestFromReader(t *testing.T) {
code := `
// A comment about this proto file
package /* some mid-definition comment */ examplepackage;
// and the rest of the file goes here
`
name, err := FromReader(strings.NewReader(code))
if err != nil {
t.Fatal(err)
}
got := name
want := "examplepackage"
if got != want {
t.Fatalf("Got %q for package name, want %q", got, want)
}
}

Просмотреть файл

@ -0,0 +1,89 @@
// Package parsesvcname will parse the service name of a protobuf package. The
// name returned will always be camelcased according to the conventions
// outlined in github.com/golang/protobuf/protoc-gen-go/generator.CamelCase.
package parsesvcname
import (
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/TuneLab/go-truss/svcdef"
"github.com/TuneLab/go-truss/truss/execprotoc"
"github.com/pkg/errors"
)
// FromPaths accepts the paths of protobuf definition files and returns the
// name of the service in that protobuf definition file.
func FromPaths(gopath []string, protoDefPaths []string) (string, error) {
td, err := ioutil.TempDir("", "parsesvcname")
defer os.RemoveAll(td)
if err != nil {
return "", errors.Wrap(err, "failed to create temporary directory for .pb.go files")
}
err = execprotoc.GeneratePBDotGo(protoDefPaths, gopath, td)
if err != nil {
return "", errors.Wrap(err, "failed to generate .pb.go files from protobuf definition files")
}
// Get path names of .pb.go files
pbgoPaths := []string{}
for _, p := range protoDefPaths {
base := filepath.Base(p)
barename := strings.TrimSuffix(base, filepath.Ext(p))
pbgp := filepath.Join(td, barename+".pb.go")
pbgoPaths = append(pbgoPaths, pbgp)
}
// Open all .pb.go files and store in map to be passed to svcdef.New()
openFiles := func(paths []string) (map[string]io.Reader, error) {
rv := map[string]io.Reader{}
for _, p := range paths {
reader, err := os.Open(p)
if err != nil {
return nil, errors.Wrapf(err, "cannot open file %q", p)
}
rv[p] = reader
}
return rv, nil
}
pbgoFiles, err := openFiles(pbgoPaths)
if err != nil {
return "", errors.Wrap(err, "cannot open all .pb.go files")
}
pbFiles, err := openFiles(protoDefPaths)
if err != nil {
return "", errors.Wrap(err, "cannot open all .proto files")
}
sd, err := svcdef.New(pbgoFiles, pbFiles)
if err != nil {
return "", errors.Wrapf(err, "failed to create service definition; did you pass ALL the protobuf files to truss?")
}
return sd.Service.Name, nil
}
func FromReaders(gopath []string, protoDefReaders []io.Reader) (string, error) {
protoDir, err := ioutil.TempDir("", "parsesvcname-fromreaders")
if err != nil {
return "", errors.Wrap(err, "failed to create temporary directory for protobuf files")
}
// Ensures all the temporary files are removed
defer os.RemoveAll(protoDir)
protoDefPaths := []string{}
for _, rdr := range protoDefReaders {
f, err := ioutil.TempFile(protoDir, "parsesvcname-fromreader")
_, err = io.Copy(f, rdr)
if err != nil {
return "", errors.Wrap(err, "couldn't copy contents of our proto file into the os.File: ")
}
path := f.Name()
f.Close()
protoDefPaths = append(protoDefPaths, path)
}
return FromPaths(gopath, protoDefPaths)
}

Просмотреть файл

@ -0,0 +1,169 @@
package parsesvcname
import (
"io"
"io/ioutil"
"os"
"strings"
"testing"
)
// Provide a basic proto file to test that FromPaths returns the name of the
// service in the file at the provided path.
func TestFromPaths(t *testing.T) {
protoStr := `
syntax = "proto3";
package echo;
import "github.com/TuneLab/go-genproto/googleapis/api/serviceconfig/annotations.proto";
service BounceEcho {
rpc Echo (EchoRequest) returns (EchoResponse) {
option (google.api.http) = {
get: "/echo"
};
}
}
message EchoRequest {
string In = 1;
}
message EchoResponse {
string Out = 1;
}
`
protoDir, err := ioutil.TempDir("", "parsesvcname-test")
if err != nil {
t.Fatal("cannot create temp directory to store proto definition: ", err)
}
defer os.RemoveAll(protoDir)
f, err := ioutil.TempFile(protoDir, "trusstest")
_, err = io.Copy(f, strings.NewReader(protoStr))
if err != nil {
t.Fatal("couldn't copy contents of our proto file into the os.File: ", err)
}
path := f.Name()
f.Close()
svcname, err := FromPaths([]string{os.Getenv("GOPATH")}, []string{path})
if err != nil {
t.Fatal("failed to get service name from path: ", err)
}
if got, want := svcname, "BounceEcho"; got != want {
t.Fatalf("got != want; got = %q, want = %q", got, want)
}
}
// Provide a basic protobuf file to FromReader to ensure it returns the service
// name we expect.
func TestFromReader(t *testing.T) {
protoStr := `
syntax = "proto3";
package echo;
import "github.com/TuneLab/go-genproto/googleapis/api/serviceconfig/annotations.proto";
service BounceEcho {
rpc Echo (EchoRequest) returns (EchoResponse) {
option (google.api.http) = {
get: "/echo"
};
}
}
message EchoRequest {
string In = 1;
}
message EchoResponse {
string Out = 1;
}
`
svcname, err := FromReaders([]string{os.Getenv("GOPATH")}, []io.Reader{strings.NewReader(protoStr)})
if err != nil {
t.Fatal("failed to get service name from path: ", err)
}
if got, want := svcname, "BounceEcho"; got != want {
t.Fatalf("got != want; got = %q, want = %q", got, want)
}
}
// Ensure that passing a protobuf file that's not importing google annotations
// will function properly.
func TestNoAnnotations(t *testing.T) {
protoStr := `
syntax = "proto3";
package echo;
service BounceEcho {
rpc Echo (EchoRequest) returns (EchoResponse) {}
}
message EchoRequest {
string In = 1;
}
message EchoResponse {
string Out = 1;
}
`
svcname, err := FromReaders([]string{os.Getenv("GOPATH")}, []io.Reader{strings.NewReader(protoStr)})
if err != nil {
t.Fatal("failed to get service name from path: ", err)
}
if got, want := svcname, "BounceEcho"; got != want {
t.Fatalf("got != want; got = %q, want = %q", got, want)
}
}
// Test that having a service name which includes an underscore doesn't cause
// problems.
func TestUnderscoreService(t *testing.T) {
protoStr := `
syntax = "proto3";
package echo;
service foo_bar_test {
rpc Echo (EchoRequest) returns (EchoResponse) {}
}
message EchoRequest {
string In = 1;
}
message EchoResponse {
string Out = 1;
}
`
svcname, err := FromReaders([]string{os.Getenv("GOPATH")}, []io.Reader{strings.NewReader(protoStr)})
if err != nil {
t.Fatal("failed to get service name from path: ", err)
}
if got, want := svcname, "FooBarTest"; got != want {
t.Fatalf("got != want; got = %q, want = %q", got, want)
}
}
// Test that having a service name which starts with an underscore doesn't
// cause problems.
func TestLeadingUnderscoreService(t *testing.T) {
protoStr := `
syntax = "proto3";
package echo;
service _Foo_Bar {
rpc Echo (EchoRequest) returns (EchoResponse) {}
}
message EchoRequest {
string In = 1;
}
message EchoResponse {
string Out = 1;
}
`
svcname, err := FromReaders([]string{os.Getenv("GOPATH")}, []io.Reader{strings.NewReader(protoStr)})
if err != nil {
t.Fatal("failed to get service name from path: ", err)
}
if got, want := svcname, "XFoo_Bar"; got != want {
t.Fatalf("got != want; got = %q, want = %q", got, want)
}
}