Skip to content

Implement detailed specs of google.api.http #14

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

Merged
merged 10 commits into from
May 21, 2015
4 changes: 3 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ GO_PLUGIN=bin/protoc-gen-go
GO_PLUGIN_PKG=github.com/golang/protobuf/protoc-gen-go
GATEWAY_PLUGIN=bin/protoc-gen-grpc-gateway
GATEWAY_PLUGIN_PKG=$(PKG)/protoc-gen-grpc-gateway
GATEWAY_PLUGIN_SRC= protoc-gen-grpc-gateway/descriptor/name.go \
GATEWAY_PLUGIN_SRC= internal/doc.go \
internal/name.go \
internal/pattern.go \
protoc-gen-grpc-gateway/descriptor/registry.go \
protoc-gen-grpc-gateway/descriptor/services.go \
protoc-gen-grpc-gateway/descriptor/types.go \
Expand Down
132 changes: 96 additions & 36 deletions examples/a_bit_of_everything.pb.gw.go

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions examples/a_bit_of_everything.proto
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,13 @@ service ABitOfEverythingService {
rpc Echo(gengo.grpc.gateway.examples.sub.StringMessage) returns (gengo.grpc.gateway.examples.sub.StringMessage) {
option (google.api.http) = {
get: "/v1/example/a_bit_of_everything/echo/{value}"
additional_bindings {
post: "/v2/example/echo"
body: "value"
}
additional_bindings {
get: "/v2/example/echo"
}
};
}
rpc BulkEcho(stream gengo.grpc.gateway.examples.sub.StringMessage) returns (stream gengo.grpc.gateway.examples.sub.StringMessage) {
Expand Down
18 changes: 10 additions & 8 deletions examples/echo_service.pb.gw.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

59 changes: 59 additions & 0 deletions examples/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (

gw "github.com/gengo/grpc-gateway/examples"
server "github.com/gengo/grpc-gateway/examples/server"
sub "github.com/gengo/grpc-gateway/examples/sub"
)

func TestIntegration(t *testing.T) {
Expand Down Expand Up @@ -43,6 +44,7 @@ func TestIntegration(t *testing.T) {
testABEBulkCreate(t)
testABELookup(t)
testABEList(t)
testAdditionalBindings(t)
}

func testEcho(t *testing.T) {
Expand Down Expand Up @@ -377,3 +379,60 @@ func testABEList(t *testing.T) {
t.Errorf("i == %d; want > 0", i)
}
}

func testAdditionalBindings(t *testing.T) {
for i, f := range []func() *http.Response{
func() *http.Response {
url := "http://localhost:8080/v1/example/a_bit_of_everything/echo/hello"
resp, err := http.Get(url)
if err != nil {
t.Errorf("http.Get(%q) failed with %v; want success", url, err)
return nil
}
return resp
},
func() *http.Response {
url := "http://localhost:8080/v2/example/echo"
resp, err := http.Post(url, "application/json", strings.NewReader(`"hello"`))
if err != nil {
t.Errorf("http.Post(%q, %q, %q) failed with %v; want success", url, "application/json", `"hello"`, err)
return nil
}
return resp
},
func() *http.Response {
url := "http://localhost:8080/v2/example/echo?value=hello"
resp, err := http.Get(url)
if err != nil {
t.Errorf("http.Get(%q) failed with %v; want success", url, err)
return nil
}
return resp
},
} {
resp := f()
if resp == nil {
continue
}

defer resp.Body.Close()
buf, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Errorf("iotuil.ReadAll(resp.Body) failed with %v; want success; i=%d", err, i)
return
}
if got, want := resp.StatusCode, http.StatusOK; got != want {
t.Errorf("resp.StatusCode = %d; want %d; i=%d", got, want, i)
t.Logf("%s", buf)
}

var msg sub.StringMessage
if err := json.Unmarshal(buf, &msg); err != nil {
t.Errorf("json.Unmarshal(%s, &msg) failed with %v; want success; %i", buf, err, i)
return
}
if got, want := msg.GetValue(), "hello"; got != want {
t.Errorf("msg.GetValue() = %q; want %q", got, want)
}
}
}
10 changes: 3 additions & 7 deletions protoc-gen-grpc-gateway/descriptor/name.go → internal/name.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
package descriptor
package internal

import (
"regexp"
"strings"
)

var (
upperPattern = regexp.MustCompile("[A-Z]")
)

func toCamel(str string) string {
// PascalFromSnake converts an identifier in snake_case into PascalCase.
func PascalFromSnake(str string) string {
var components []string
for _, c := range strings.Split(str, "_") {
components = append(components, strings.Title(strings.ToLower(c)))
Expand Down
22 changes: 22 additions & 0 deletions internal/name_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package internal_test

import (
"testing"

"github.com/gengo/grpc-gateway/internal"
)

func TestPascalToSnake(t *testing.T) {
for _, spec := range []struct {
input, want string
}{
{input: "value", want: "Value"},
{input: "prefixed_value", want: "PrefixedValue"},
{input: "foo_id", want: "FooId"},
} {
got := internal.PascalFromSnake(spec.input)
if got != spec.want {
t.Errorf("internal.PascalFromSnake(%q) = %q; want %q", spec.input, got, spec.want)
}
}
}
177 changes: 177 additions & 0 deletions internal/trie.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
package internal

import (
"sort"
)

// DoubleArray is a Double Array implementation of trie on sequences of strings.
type DoubleArray struct {
// Encoding keeps an encoding from string to int
Encoding map[string]int
// Base is the base array of Double Array
Base []int
// Check is the check array of Double Array
Check []int
}

// NewDoubleArray builds a DoubleArray from a set of sequences of strings.
func NewDoubleArray(seqs [][]string) *DoubleArray {
da := &DoubleArray{Encoding: make(map[string]int)}
if len(seqs) == 0 {
return da
}

encoded := registerTokens(da, seqs)
sort.Sort(byLex(encoded))

root := node{row: -1, col: -1, left: 0, right: len(encoded)}
addSeqs(da, encoded, 0, root)

for i := len(da.Base); i > 0; i-- {
if da.Check[i-1] != 0 {
da.Base = da.Base[:i]
da.Check = da.Check[:i]
break
}
}
return da
}

func registerTokens(da *DoubleArray, seqs [][]string) [][]int {
var result [][]int
for _, seq := range seqs {
var encoded []int
for _, token := range seq {
if _, ok := da.Encoding[token]; !ok {
da.Encoding[token] = len(da.Encoding)
}
encoded = append(encoded, da.Encoding[token])
}
result = append(result, encoded)
}
for i := range result {
result[i] = append(result[i], len(da.Encoding))
}
return result
}

type node struct {
row, col int
left, right int
}

func (n node) value(seqs [][]int) int {
return seqs[n.row][n.col]
}

func (n node) children(seqs [][]int) []*node {
var result []*node
lastVal := int(-1)
last := new(node)
for i := n.left; i < n.right; i++ {
if lastVal == seqs[i][n.col+1] {
continue
}
last.right = i
last = &node{
row: i,
col: n.col + 1,
left: i,
}
result = append(result, last)
}
last.right = n.right
return result
}

func addSeqs(da *DoubleArray, seqs [][]int, pos int, n node) {
ensureSize(da, pos)

children := n.children(seqs)
var i int
for i = 1; ; i++ {
ok := func() bool {
for _, child := range children {
code := child.value(seqs)
j := i + code
ensureSize(da, j)
if da.Check[j] != 0 {
return false
}
}
return true
}()
if ok {
break
}
}
da.Base[pos] = i
for _, child := range children {
code := child.value(seqs)
j := i + code
da.Check[j] = pos + 1
}
terminator := len(da.Encoding)
for _, child := range children {
code := child.value(seqs)
if code == terminator {
continue
}
j := i + code
addSeqs(da, seqs, j, *child)
}
}

func ensureSize(da *DoubleArray, i int) {
for i >= len(da.Base) {
da.Base = append(da.Base, make([]int, len(da.Base)+1)...)
da.Check = append(da.Check, make([]int, len(da.Check)+1)...)
}
}

type byLex [][]int

func (l byLex) Len() int { return len(l) }
func (l byLex) Swap(i, j int) { l[i], l[j] = l[j], l[i] }
func (l byLex) Less(i, j int) bool {
si := l[i]
sj := l[j]
var k int
for k = 0; k < len(si) && k < len(sj); k++ {
if si[k] < sj[k] {
return true
}
if si[k] > sj[k] {
return false
}
}
if k < len(sj) {
return true
}
return false
}

// HasCommonPrefix determines if any sequence in the DoubleArray is a prefix of the given sequence.
func (da *DoubleArray) HasCommonPrefix(seq []string) bool {
if len(da.Base) == 0 {
return false
}

var i int
for _, t := range seq {
code, ok := da.Encoding[t]
if !ok {
break
}
j := da.Base[i] + code
if len(da.Check) <= j || da.Check[j] != i+1 {
break
}
i = j
}
j := da.Base[i] + len(da.Encoding)
if len(da.Check) <= j || da.Check[j] != i+1 {
return false
}
return true
}
Loading