Skip to content

Commit e98837c

Browse files
authored
Merge pull request #143 from monzo/allow-use-of-legacyproto
Requests should allow encoding and decoding as legacy protobuf
2 parents cd92746 + becd421 commit e98837c

File tree

6 files changed

+244
-11
lines changed

6 files changed

+244
-11
lines changed

.gitignore

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,17 @@
11
.DS_Store
2+
.AppleDouble
3+
.LSOverride
4+
.idea
5+
.vscode
6+
coverage.out
7+
8+
# Files that might appear on external disk
9+
.Spotlight-V100
10+
.Trashes
11+
12+
# Directories potentially created on remote AFP share
13+
.AppleDB
14+
.AppleDesktop
15+
Network Trash Folder
16+
Temporary Items
17+
.apdisk

legacyprototest/prototest.pb.go

Lines changed: 85 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

legacyprototest/prototest.proto

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
syntax = "proto3";
2+
option go_package = "legacyprototest";
3+
4+
message LegacyGreeting {
5+
string message = 1;
6+
int32 priority = 2;
7+
}

request.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"net/http"
1010
"strings"
1111

12+
legacyproto "github.com/golang/protobuf/proto"
1213
"github.com/monzo/terrors"
1314
"google.golang.org/protobuf/proto"
1415
)
@@ -97,19 +98,18 @@ func (r Request) Decode(v interface{}) error {
9798
// See: https://datatracker.ietf.org/doc/html/draft-rfernando-protocol-buffers-00#section-3.2
9899
// See: https://github.com/google/protorpc/blob/eb03145/python/protorpc/protobuf.py#L49-L51
99100
case "application/octet-stream", "application/x-google-protobuf", "application/protobuf", "application/x-protobuf":
100-
m, ok := v.(proto.Message)
101-
if !ok {
101+
switch m := v.(type) {
102+
case proto.Message:
103+
err = proto.Unmarshal(b, m)
104+
case legacyproto.Message:
105+
err = legacyproto.Unmarshal(b, m)
106+
default:
102107
return terrors.InternalService("invalid_type", "could not decode proto message", nil)
103108
}
104-
err = proto.Unmarshal(b, m)
105109
// As older versions of typhon used json, we don't use protojson here as they are mutually exclusive standards with
106110
// major differences in how they handle some types (such as Enums)
107111
default:
108-
m, ok := v.(proto.Message)
109-
if !ok {
110-
return terrors.InternalService("invalid_type", "could not decode proto message", nil)
111-
}
112-
err = json.Unmarshal(b, m)
112+
err = json.Unmarshal(b, v)
113113
}
114114

115115
return terrors.WrapWithCode(err, nil, terrors.ErrBadRequest)

request_test.go

Lines changed: 88 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,16 @@ import (
55
"bytes"
66
"context"
77
"encoding/json"
8-
"google.golang.org/protobuf/proto"
98
"io/ioutil"
109
"strings"
1110
"testing"
1211

12+
legacyproto "github.com/golang/protobuf/proto"
1313
"github.com/stretchr/testify/assert"
1414
"github.com/stretchr/testify/require"
15+
"google.golang.org/protobuf/proto"
1516

17+
"github.com/monzo/typhon/legacyprototest"
1618
"github.com/monzo/typhon/prototest"
1719
)
1820

@@ -33,6 +35,90 @@ func TestRequestDecodeCloses(t *testing.T) {
3335
}
3436
}
3537

38+
func TestRequestDecodeJSONStruct(t *testing.T) {
39+
req := NewRequest(nil, "GET", "/", nil)
40+
b := []byte("{\"message\":\"Hello world!\"}\n")
41+
r := newDoneReader(ioutil.NopCloser(bytes.NewReader(b)), -1)
42+
req.Body = r
43+
44+
g := &struct {
45+
Message string `json:"message"`
46+
}{}
47+
err := req.Decode(g)
48+
assert.NoError(t, err)
49+
assert.Equal(t, "Hello world!", g.Message)
50+
}
51+
52+
func TestRequestDecodeProto(t *testing.T) {
53+
generateRequest := func() Request {
54+
req := NewRequest(nil, "GET", "/", nil)
55+
b, _ := proto.Marshal(&prototest.Greeting{Message: "Hello world!"})
56+
r := newDoneReader(ioutil.NopCloser(bytes.NewReader(b)), -1)
57+
req.Header.Set("Content-Type", "application/protobuf")
58+
req.Body = r
59+
return req
60+
}
61+
62+
req1 := generateRequest()
63+
g1 := &prototest.Greeting{}
64+
err := req1.Decode(g1)
65+
assert.NoError(t, err)
66+
assert.Equal(t, "Hello world!", g1.Message)
67+
68+
req2 := generateRequest()
69+
g2 := &legacyprototest.LegacyGreeting{}
70+
err = req2.Decode(g2)
71+
assert.NoError(t, err)
72+
assert.Equal(t, "Hello world!", g2.Message)
73+
}
74+
75+
func TestRequestDecodeProtoMaskingAsJSON(t *testing.T) {
76+
req := NewRequest(nil, "GET", "/", nil)
77+
b := []byte("{\"message\":\"Hello world!\"}\n")
78+
r := newDoneReader(ioutil.NopCloser(bytes.NewReader(b)), -1)
79+
req.Body = r
80+
81+
g := &prototest.Greeting{}
82+
err := req.Decode(g)
83+
assert.NoError(t, err)
84+
assert.Equal(t, "Hello world!", g.Message)
85+
}
86+
87+
func TestRequestDecodeLegacyProto(t *testing.T) {
88+
generateRequest := func() Request {
89+
req := NewRequest(nil, "GET", "/", nil)
90+
b, _ := legacyproto.Marshal(&legacyprototest.LegacyGreeting{Message: "Hello world!"})
91+
r := newDoneReader(ioutil.NopCloser(bytes.NewReader(b)), -1)
92+
req.Header.Set("Content-Type", "application/protobuf")
93+
req.Body = r
94+
return req
95+
}
96+
97+
req1 := generateRequest()
98+
g1 := &prototest.Greeting{}
99+
err := req1.Decode(g1)
100+
assert.NoError(t, err)
101+
assert.Equal(t, "Hello world!", g1.Message)
102+
103+
req2 := generateRequest()
104+
g2 := &legacyprototest.LegacyGreeting{}
105+
err = req2.Decode(g2)
106+
assert.NoError(t, err)
107+
assert.Equal(t, "Hello world!", g2.Message)
108+
}
109+
110+
func TestRequestDecodeLegacyProtoMaskingAsJSON(t *testing.T) {
111+
req := NewRequest(nil, "GET", "/", nil)
112+
b := []byte("{\"message\":\"Hello world!\"}\n")
113+
r := newDoneReader(ioutil.NopCloser(bytes.NewReader(b)), -1)
114+
req.Body = r
115+
116+
g := &legacyprototest.LegacyGreeting{}
117+
err := req.Decode(g)
118+
assert.NoError(t, err)
119+
assert.Equal(t, "Hello world!", g.Message)
120+
}
121+
36122
// TestRequestEncodeReader verifies that passing an io.Reader to request.Encode() uses it properly as the body, and
37123
// does not attempt to encode it as JSON
38124
func TestRequestEncodeReader(t *testing.T) {
@@ -88,7 +174,7 @@ func TestRequestEncodeProtobuf(t *testing.T) {
88174
}
89175

90176
func TestRequestEncodeJSON(t *testing.T) {
91-
message := map[string]interface{} {
177+
message := map[string]interface{}{
92178
"foo": "bar",
93179
"bar": 3,
94180
}
@@ -118,7 +204,6 @@ func TestRequestSetMetadata(t *testing.T) {
118204
assert.Equal(t, []string{"data"}, req.Request.Header["meta"])
119205
}
120206

121-
122207
func jsonStreamMarshal(v interface{}) ([]byte, error) {
123208
var buffer bytes.Buffer
124209
writer := bufio.NewWriter(&buffer)

response_test.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import (
55
"context"
66
"encoding/json"
77
"errors"
8+
legacyproto "github.com/golang/protobuf/proto"
9+
"github.com/monzo/typhon/legacyprototest"
810
"io"
911
"io/ioutil"
1012
"net/http"
@@ -218,6 +220,44 @@ func TestResponseDecodeProtobufWithAltType(t *testing.T) {
218220
assert.EqualValues(t, 1, gout.Priority)
219221
}
220222

223+
// TestResponseDecodeLegacyProtobuf verifies decoding of a legacy protobuf message
224+
func TestResponseDecodeLegacyProtobuf(t *testing.T) {
225+
t.Parallel()
226+
227+
g := &legacyprototest.LegacyGreeting{
228+
Message: "Hello world!",
229+
Priority: 1,
230+
}
231+
b, _ := legacyproto.Marshal(g)
232+
rsp := NewResponse(Request{})
233+
rsp.Body = ioutil.NopCloser(bytes.NewReader(b))
234+
rsp.Header.Set("Content-Type", "application/protobuf")
235+
236+
gout := &legacyprototest.LegacyGreeting{}
237+
assert.NoError(t, rsp.Decode(gout))
238+
assert.Equal(t, "Hello world!", gout.Message)
239+
assert.EqualValues(t, 1, gout.Priority)
240+
}
241+
242+
// TestResponseDecodeLegacyProtobufWithAltType verifies decoding of a legacy protobuf message
243+
func TestResponseDecodeLegacyProtobufWithAltType(t *testing.T) {
244+
t.Parallel()
245+
246+
g := &legacyprototest.LegacyGreeting{
247+
Message: "Hello world!",
248+
Priority: 1,
249+
}
250+
b, _ := legacyproto.Marshal(g)
251+
rsp := NewResponse(Request{})
252+
rsp.Body = ioutil.NopCloser(bytes.NewReader(b))
253+
rsp.Header.Set("Content-Type", "application/x-protobuf")
254+
255+
gout := &prototest.Greeting{}
256+
assert.NoError(t, rsp.Decode(gout))
257+
assert.Equal(t, "Hello world!", gout.Message)
258+
assert.EqualValues(t, 1, gout.Priority)
259+
}
260+
221261
// rc is a helper type used in tests involving a generic io.ReadCloser
222262
type rc struct {
223263
strings.Reader

0 commit comments

Comments
 (0)