Skip to content

Commit

Permalink
Merge pull request #15 from donutloop/feat/protobuffer
Browse files Browse the repository at this point in the history
Client & Server Generator: added support for protobuffer handling
  • Loading branch information
donutloop committed Feb 28, 2018
2 parents 03c7cde + 74bac9a commit 0f3b849
Show file tree
Hide file tree
Showing 8 changed files with 252 additions and 39 deletions.
27 changes: 25 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,9 @@ func main() {
}
```

Now you can just use the auto-generated Client to make remote calls to your new service:
Now you can just use the auto-generated JSON or Protobuffer Client to make remote calls to your new service:

##### JSON
```go
package main

Expand All @@ -100,13 +101,35 @@ func main() {
}
```

##### Protobuffer

```go
package main

import (
"context"
"fmt"
"net/http"

pb "github.com/donutloop/xservice-example/helloworld"
)

func main() {
client := pb.NewHelloWorldProtobufferClient("http://localhost:8080", &http.Client{})

resp, err := client.Hello(context.Background(), &pb.HelloReq{Subject: "World"})
if err == nil {
fmt.Println(resp.Text) // prints "Hello World"
}
}
```

## QuickStart for developers

Please refer [**docs/DeveloperQuickStart.md**](https://github.com/donutloop/xservice/blob/master/docs/DeveloperQuickstartGuide.md)

## Roadmap

* Golang client & server: support for protobuffer content type
* Multi language support

## Contribution
Expand Down
2 changes: 1 addition & 1 deletion framework/transport/transport.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ func marshalErrorToJSON(terr errors.Error) []byte {
}

// doProtobufRequest is common code to make a request to the remote service.
func doProtobufRequest(ctx context.Context, client HTTPClient, url string, in, out proto.Message) (err error) {
func DoProtobufferRequest(ctx context.Context, client HTTPClient, url string, in, out proto.Message) (err error) {
reqBodyBytes, err := proto.Marshal(in)
if err != nil {
return errors.ClientError("failed to marshal proto request", err)
Expand Down
2 changes: 2 additions & 0 deletions framework/xhttp/xhttp.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ package xhttp
const ContentTypeHeader string = "Content-Type"

const ApplicationJson = "application/json"

const ApplicationProtobuf = "application/protobuf"
112 changes: 83 additions & 29 deletions generator/proto/go/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ import (
"strings"
)

const (
ServeJSON string = "JSON"
ServeProtobuffer string = "Protobuffer"
)

type API struct {
filesHandled int
currentPackage string // Go name of current package we're working on
Expand Down Expand Up @@ -232,8 +237,14 @@ func (a *API) generateService(fileDescriptor *descriptor.FileDescriptorProto, se
return nil, err
}

// Server
goFile, err = a.generateClient("JSON", fileDescriptor, service, goFile)
// JSON Client
goFile, err = a.generateClient(ServeJSON, fileDescriptor, service, goFile)
if err != nil {
return nil, err
}

// Protobuffer Client
goFile, err = a.generateClient(ServeProtobuffer, fileDescriptor, service, goFile)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -711,26 +722,35 @@ func (a *API) generateServerMethod(service *descriptor.ServiceDescriptorProto, m

dispatcherMethod.DefIfBegin("modifiedHeader", token.EQL, `xhttp.ApplicationJson`)
dispatcherMethod.Caller(types.NewUnsafeTypeReference(fmt.Sprintf("s.serve%sJSON", methName)), []string{"ctx", "resp", "req"})
dispatcherMethod.Return(nil)
dispatcherMethod.DefElseIf("modifiedHeader", token.EQL, `xhttp.ApplicationProtobuf`)
dispatcherMethod.Caller(types.NewUnsafeTypeReference(fmt.Sprintf("s.serve%sProtobuffer", methName)), []string{"ctx", "resp", "req"})
dispatcherMethod.Return(nil)
dispatcherMethod.Else()
dispatcherMethod.DefAssginCall([]string{"msg"}, types.NewUnsafeTypeReference("fmt.Sprintf"), []string{`"unexpected Content-Type: %q"`, "header"})
dispatcherMethod.DefAssginCall([]string{"terr"}, types.NewUnsafeTypeReference("errors.BadRouteError"), []string{"msg", "req.Method", "req.URL.Path"})
dispatcherMethod.Caller(types.NewUnsafeTypeReference("s.writeError"), []string{"ctx", "resp", "terr"})
dispatcherMethod.CloseIf()

dispatcherMethod.Return(nil)
structGenerator.AddMethod(dispatcherMethod)

structGenerator, err = a.generateServerJSONMethod(service, method, structGenerator)
structGenerator, err = a.generateServerServeMethod(ServeJSON, service, method, structGenerator)
if err != nil {
return nil, err
}

structGenerator, err = a.generateServerServeMethod(ServeProtobuffer, service, method, structGenerator)
if err != nil {
return nil, err
}

return structGenerator, nil
}

func (a *API) generateServerJSONMethod(service *descriptor.ServiceDescriptorProto, method *descriptor.MethodDescriptorProto, structGenerator *types.StructGenerator) (*types.StructGenerator, error) {
func (a *API) generateServerServeMethod(contentType string, service *descriptor.ServiceDescriptorProto, method *descriptor.MethodDescriptorProto, structGenerator *types.StructGenerator) (*types.StructGenerator, error) {
methName := types.CamelCase(method.GetName())

serveMethod, err := types.NewGoMethod("s", fmt.Sprintf("*%s", structGenerator.StructMetaData.Name), fmt.Sprintf("serve%sJSON", methName), []*types.Parameter{
serveMethod, err := types.NewGoMethod("s", fmt.Sprintf("*%s", structGenerator.StructMetaData.Name), fmt.Sprintf("serve%s%s", methName, contentType), []*types.Parameter{
{
NameOfParameter: "ctx",
Typ: types.NewUnsafeTypeReference("context.Context"),
Expand Down Expand Up @@ -769,16 +789,40 @@ func (a *API) generateServerJSONMethod(service *descriptor.ServiceDescriptorProt
serveMethod.Return()
serveMethod.CloseIf()
serveMethod.Defer(types.NewUnsafeTypeReference("transport.Closebody"), []string{"req.Body", "s.logErrorFunc"})
serveMethod.DefNew("reqContent", types.NewUnsafeTypeReference(inputType))
serveMethod.DefShortVar("unmarshaler", "jsonpb.Unmarshaler{AllowUnknownFields: true}")
serveMethod.DefCall([]string{"err"}, types.NewUnsafeTypeReference("unmarshaler.Unmarshal"), []string{"req.Body", "reqContent"})
serveMethod.DefIfBegin("err", token.NEQ, "nil")
serveMethod.DefCall([]string{"err"}, types.NewUnsafeTypeReference("errors.WrapErr"), []string{"err", `"failed to parse request json"`})
serveMethod.DefAssginCall([]string{"terr"}, types.NewUnsafeTypeReference("errors.InternalErrorWith"), []string{"err"})
serveMethod.Caller(types.NewUnsafeTypeReference("s.logErrorFunc"), []string{`"%v"`, "err"})
serveMethod.Caller(types.NewUnsafeTypeReference("s.writeError"), []string{"ctx", "resp", "terr"})
serveMethod.Return()
serveMethod.CloseIf()

if contentType == ServeJSON {
serveMethod.DefNew("reqContent", types.NewUnsafeTypeReference(inputType))
serveMethod.DefShortVar("unmarshaler", "jsonpb.Unmarshaler{AllowUnknownFields: true}")
serveMethod.DefCall([]string{"err"}, types.NewUnsafeTypeReference("unmarshaler.Unmarshal"), []string{"req.Body", "reqContent"})
serveMethod.DefIfBegin("err", token.NEQ, "nil")
serveMethod.DefCall([]string{"err"}, types.NewUnsafeTypeReference("errors.WrapErr"), []string{"err", `"failed to parse request json"`})
serveMethod.DefAssginCall([]string{"terr"}, types.NewUnsafeTypeReference("errors.InternalErrorWith"), []string{"err"})
serveMethod.Caller(types.NewUnsafeTypeReference("s.logErrorFunc"), []string{`"%v"`, "err"})
serveMethod.Caller(types.NewUnsafeTypeReference("s.writeError"), []string{"ctx", "resp", "terr"})
serveMethod.Return()
serveMethod.CloseIf()
} else if contentType == ServeProtobuffer {
serveMethod.DefAssginCall([]string{"buff", "err"}, types.NewUnsafeTypeReference("ioutil.ReadAll"), []string{"req.Body"})
serveMethod.DefIfBegin("err", token.NEQ, "nil")
serveMethod.DefCall([]string{"err"}, types.NewUnsafeTypeReference("errors.WrapErr"), []string{"err", `"failed to read request proto"`})
serveMethod.DefAssginCall([]string{"terr"}, types.NewUnsafeTypeReference("errors.InternalErrorWith"), []string{"err"})
serveMethod.Caller(types.NewUnsafeTypeReference("s.logErrorFunc"), []string{`"%v"`, "err"})
serveMethod.Caller(types.NewUnsafeTypeReference("s.writeError"), []string{"ctx", "resp", "terr"})
serveMethod.Return()
serveMethod.CloseIf()
serveMethod.DefNew("reqContent", types.NewUnsafeTypeReference(inputType))
serveMethod.DefCall([]string{"err"}, types.NewUnsafeTypeReference("proto.Unmarshal"), []string{"buff", "reqContent"})
serveMethod.DefIfBegin("err", token.NEQ, "nil")
serveMethod.DefCall([]string{"err"}, types.NewUnsafeTypeReference("errors.WrapErr"), []string{"err", `"failed to parse request proto"`})
serveMethod.DefAssginCall([]string{"terr"}, types.NewUnsafeTypeReference("errors.InternalErrorWith"), []string{"err"})
serveMethod.Caller(types.NewUnsafeTypeReference("s.logErrorFunc"), []string{`"%v"`, "err"})
serveMethod.Caller(types.NewUnsafeTypeReference("s.writeError"), []string{"ctx", "resp", "terr"})
serveMethod.Return()
serveMethod.CloseIf()
} else {
return nil, errors.New("content type isn't supported")
}

serveMethod.DefNew("respContent", types.NewUnsafeTypeReference(outputType))
responseCallWrapper, _ := types.NewAnonymousGoFunc("responseCallWrapper", nil, nil)
responseDeferWrapper, _ := types.NewAnonymousGoFunc("responseDeferWrapper", nil, nil)
Expand Down Expand Up @@ -808,22 +852,32 @@ func (a *API) generateServerJSONMethod(service *descriptor.ServiceDescriptorProt
serveMethod.CloseIf()

serveMethod.DefCall([]string{"ctx"}, types.NewUnsafeTypeReference("transport.CallResponsePrepared"), []string{"ctx", "s.hooks"})
serveMethod.DefNew("buff", types.NewUnsafeTypeReference("bytes.Buffer"))
serveMethod.DefShortVar("marshaler", "&jsonpb.Marshaler{OrigName: true}")
serveMethod.DefCall([]string{"err"}, types.NewUnsafeTypeReference("marshaler.Marshal"), []string{"buff", "respContent"})
serveMethod.DefIfBegin("err", token.NEQ, "nil")
serveMethod.DefCall([]string{"err"}, types.NewUnsafeTypeReference("errors.WrapErr"), []string{"err", `"failed to marshal json response"`})
serveMethod.DefAssginCall([]string{"terr"}, types.NewUnsafeTypeReference("errors.InternalErrorWith"), []string{"err"})
serveMethod.Caller(types.NewUnsafeTypeReference("s.logErrorFunc"), []string{`"%v"`, "err"})
serveMethod.Caller(types.NewUnsafeTypeReference("s.writeError"), []string{"ctx", "resp", "terr"})
serveMethod.Return()
serveMethod.CloseIf()
if contentType == ServeJSON {
serveMethod.DefNew("buff", types.NewUnsafeTypeReference("bytes.Buffer"))
serveMethod.DefShortVar("marshaler", "&jsonpb.Marshaler{OrigName: true}")
serveMethod.DefCall([]string{"err"}, types.NewUnsafeTypeReference("marshaler.Marshal"), []string{"buff", "respContent"})
serveMethod.DefIfBegin("err", token.NEQ, "nil")
serveMethod.DefCall([]string{"err"}, types.NewUnsafeTypeReference("errors.WrapErr"), []string{"err", `"failed to marshal json response"`})
serveMethod.DefAssginCall([]string{"terr"}, types.NewUnsafeTypeReference("errors.InternalErrorWith"), []string{"err"})
serveMethod.Caller(types.NewUnsafeTypeReference("s.logErrorFunc"), []string{`"%v"`, "err"})
serveMethod.Caller(types.NewUnsafeTypeReference("s.writeError"), []string{"ctx", "resp", "terr"})
serveMethod.Return()
serveMethod.CloseIf()
serveMethod.DefAssginCall([]string{"respBytes"}, types.NewUnsafeTypeReference("buff.Bytes"), nil)
serveMethod.Caller(types.NewUnsafeTypeReference("req.Header.Set"), []string{"xhttp.ContentTypeHeader", "xhttp.ApplicationJson"})
} else if contentType == ServeProtobuffer {
serveMethod.DefAssginCall([]string{"respBytes", "err"}, types.NewUnsafeTypeReference("proto.Marshal"), []string{"respContent"})
serveMethod.DefIfBegin("err", token.NEQ, "nil")
serveMethod.DefCall([]string{"err"}, types.NewUnsafeTypeReference("errors.WrapErr"), []string{"err", `"failed to marshal json response"`})
serveMethod.DefAssginCall([]string{"terr"}, types.NewUnsafeTypeReference("errors.InternalErrorWith"), []string{"err"})
serveMethod.Caller(types.NewUnsafeTypeReference("s.logErrorFunc"), []string{`"%v"`, "err"})
serveMethod.Caller(types.NewUnsafeTypeReference("s.writeError"), []string{"ctx", "resp", "terr"})
serveMethod.Return()
serveMethod.CloseIf()
}

serveMethod.DefCall([]string{"ctx"}, types.NewUnsafeTypeReference("xcontext.WithStatusCode"), []string{"ctx", "http.StatusOK"})
serveMethod.Caller(types.NewUnsafeTypeReference("req.Header.Set"), []string{"xhttp.ContentTypeHeader", "xhttp.ApplicationJson"})
serveMethod.Caller(types.NewUnsafeTypeReference("resp.WriteHeader"), []string{"http.StatusOK"})
serveMethod.DefAssginCall([]string{"respBytes"}, types.NewUnsafeTypeReference("buff.Bytes"), nil)

serveMethod.DefCall([]string{"_", "err"}, types.NewUnsafeTypeReference("resp.Write"), []string{"respBytes"})

serveMethod.DefIfBegin("err", token.NEQ, "nil")
Expand Down
22 changes: 18 additions & 4 deletions integration_tests/api_hello_world/api_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ func (s *HelloWorldServer) Hello(ctx context.Context, req *helloworld.HelloReq)
return &helloworld.HelloResp{Text: "Hello " + req.Subject}, nil
}

var client helloworld.HelloWorld
var JSONClient helloworld.HelloWorld
var ProtobufferClient helloworld.HelloWorld

func TestMain(m *testing.M) {
handler := helloworld.NewHelloWorldServer(&HelloWorldServer{}, nil)
Expand All @@ -37,14 +38,27 @@ func TestMain(m *testing.M) {
server := httptest.NewServer(mux)
defer server.Close()

client = helloworld.NewHelloWorldJSONClient(server.URL, &http.Client{})
JSONClient = helloworld.NewHelloWorldJSONClient(server.URL, &http.Client{})
ProtobufferClient = helloworld.NewHelloWorldProtobufferClient(server.URL, &http.Client{})

// call flag.Parse() here if TestMain uses flags
os.Exit(m.Run())
}

func TestHelloWorldCall(t *testing.T) {
resp, err := client.Hello(context.Background(), &helloworld.HelloReq{Subject: "World"})
func TestHelloWorldJSONCall(t *testing.T) {
resp, err := JSONClient.Hello(context.Background(), &helloworld.HelloReq{Subject: "World"})
if err != nil {
t.Fatal(err)
}

expectedMessage := "Hello World"
if resp.Text != expectedMessage {
t.Fatalf(`unexpected text (actual: "%s", expected: "%s")`, resp.Text, expectedMessage)
}
}

func TestHelloWorldProtobufferCall(t *testing.T) {
resp, err := ProtobufferClient.Hello(context.Background(), &helloworld.HelloReq{Subject: "World"})
if err != nil {
t.Fatal(err)
}
Expand Down
Loading

0 comments on commit 0f3b849

Please sign in to comment.