From 9d644d22e0c8102d2d66122e43b91ccc3480109e Mon Sep 17 00:00:00 2001 From: zhing Date: Sun, 12 Jul 2015 17:42:39 +0800 Subject: [PATCH] add protobuf binding for gin --- binding/binding.go | 10 +++- binding/binding_test.go | 34 +++++++++++ binding/example/test.pb.go | 113 +++++++++++++++++++++++++++++++++++++ binding/example/test.proto | 12 ++++ binding/protobuf.go | 35 ++++++++++++ 5 files changed, 201 insertions(+), 3 deletions(-) create mode 100644 binding/example/test.pb.go create mode 100644 binding/example/test.proto create mode 100644 binding/protobuf.go diff --git a/binding/binding.go b/binding/binding.go index f719fbc..38d45e9 100644 --- a/binding/binding.go +++ b/binding/binding.go @@ -14,6 +14,7 @@ const ( MIMEPlain = "text/plain" MIMEPOSTForm = "application/x-www-form-urlencoded" MIMEMultipartPOSTForm = "multipart/form-data" + MIMEPROTOBUF = "application/octet-stream" ) type Binding interface { @@ -33,9 +34,10 @@ type StructValidator interface { var Validator StructValidator = &defaultValidator{} var ( - JSON = jsonBinding{} - XML = xmlBinding{} - Form = formBinding{} + JSON = jsonBinding{} + XML = xmlBinding{} + Form = formBinding{} + ProtoBuf = protobufBinding{} ) func Default(method, contentType string) Binding { @@ -47,6 +49,8 @@ func Default(method, contentType string) Binding { return JSON case MIMEXML, MIMEXML2: return XML + case MIMEPROTOBUF: + return ProtoBuf default: //case MIMEPOSTForm, MIMEMultipartPOSTForm: return Form } diff --git a/binding/binding_test.go b/binding/binding_test.go index db1678e..517be43 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -6,6 +6,8 @@ package binding import ( "bytes" + "github.com/gin-gonic/gin/binding/example" + "github.com/golang/protobuf/proto" "net/http" "testing" @@ -36,6 +38,9 @@ func TestBindingDefault(t *testing.T) { assert.Equal(t, Default("POST", MIMEMultipartPOSTForm), Form) assert.Equal(t, Default("PUT", MIMEMultipartPOSTForm), Form) + + assert.Equal(t, Default("POST", MIMEPROTOBUF), ProtoBuf) + assert.Equal(t, Default("PUT", MIMEPROTOBUF), ProtoBuf) } func TestBindingJSON(t *testing.T) { @@ -64,6 +69,18 @@ func TestBindingXML(t *testing.T) { "bar", "foo") } +func TestBindingProtoBuf(t *testing.T) { + test := &example.Test{ + Label: proto.String("yes"), + } + data, _ := proto.Marshal(test) + + testProtoBodyBinding(t, + ProtoBuf, "protobuf", + "/", "/", + string(data), string(data[1:])) +} + func TestValidationFails(t *testing.T) { var obj FooStruct req := requestWithBody("POST", "/", `{"bar": "foo"}`) @@ -117,6 +134,23 @@ func testBodyBinding(t *testing.T, b Binding, name, path, badPath, body, badBody assert.Error(t, err) } +func testProtoBodyBinding(t *testing.T, b Binding, name, path, badPath, body, badBody string) { + assert.Equal(t, b.Name(), name) + + obj := example.Test{} + req := requestWithBody("POST", path, body) + req.Header.Add("Content-Type", MIMEPROTOBUF) + err := b.Bind(req, &obj) + assert.NoError(t, err) + assert.Equal(t, *obj.Label, "yes") + + obj = example.Test{} + req = requestWithBody("POST", badPath, badBody) + req.Header.Add("Content-Type", MIMEPROTOBUF) + err = ProtoBuf.Bind(req, &obj) + assert.Error(t, err) +} + func requestWithBody(method, path, body string) (req *http.Request) { req, _ = http.NewRequest(method, path, bytes.NewBufferString(body)) return diff --git a/binding/example/test.pb.go b/binding/example/test.pb.go new file mode 100644 index 0000000..3de8444 --- /dev/null +++ b/binding/example/test.pb.go @@ -0,0 +1,113 @@ +// Code generated by protoc-gen-go. +// source: test.proto +// DO NOT EDIT! + +/* +Package example is a generated protocol buffer package. + +It is generated from these files: + test.proto + +It has these top-level messages: + Test +*/ +package example + +import proto "github.com/golang/protobuf/proto" +import math "math" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = math.Inf + +type FOO int32 + +const ( + FOO_X FOO = 17 +) + +var FOO_name = map[int32]string{ + 17: "X", +} +var FOO_value = map[string]int32{ + "X": 17, +} + +func (x FOO) Enum() *FOO { + p := new(FOO) + *p = x + return p +} +func (x FOO) String() string { + return proto.EnumName(FOO_name, int32(x)) +} +func (x *FOO) UnmarshalJSON(data []byte) error { + value, err := proto.UnmarshalJSONEnum(FOO_value, data, "FOO") + if err != nil { + return err + } + *x = FOO(value) + return nil +} + +type Test struct { + Label *string `protobuf:"bytes,1,req,name=label" json:"label,omitempty"` + Type *int32 `protobuf:"varint,2,opt,name=type,def=77" json:"type,omitempty"` + Reps []int64 `protobuf:"varint,3,rep,name=reps" json:"reps,omitempty"` + Optionalgroup *Test_OptionalGroup `protobuf:"group,4,opt,name=OptionalGroup" json:"optionalgroup,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *Test) Reset() { *m = Test{} } +func (m *Test) String() string { return proto.CompactTextString(m) } +func (*Test) ProtoMessage() {} + +const Default_Test_Type int32 = 77 + +func (m *Test) GetLabel() string { + if m != nil && m.Label != nil { + return *m.Label + } + return "" +} + +func (m *Test) GetType() int32 { + if m != nil && m.Type != nil { + return *m.Type + } + return Default_Test_Type +} + +func (m *Test) GetReps() []int64 { + if m != nil { + return m.Reps + } + return nil +} + +func (m *Test) GetOptionalgroup() *Test_OptionalGroup { + if m != nil { + return m.Optionalgroup + } + return nil +} + +type Test_OptionalGroup struct { + RequiredField *string `protobuf:"bytes,5,req" json:"RequiredField,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *Test_OptionalGroup) Reset() { *m = Test_OptionalGroup{} } +func (m *Test_OptionalGroup) String() string { return proto.CompactTextString(m) } +func (*Test_OptionalGroup) ProtoMessage() {} + +func (m *Test_OptionalGroup) GetRequiredField() string { + if m != nil && m.RequiredField != nil { + return *m.RequiredField + } + return "" +} + +func init() { + proto.RegisterEnum("example.FOO", FOO_name, FOO_value) +} diff --git a/binding/example/test.proto b/binding/example/test.proto new file mode 100644 index 0000000..8ee9800 --- /dev/null +++ b/binding/example/test.proto @@ -0,0 +1,12 @@ +package example; + +enum FOO {X=17;}; + +message Test { + required string label = 1; + optional int32 type = 2[default=77]; + repeated int64 reps = 3; + optional group OptionalGroup = 4{ + required string RequiredField = 5; + } +} diff --git a/binding/protobuf.go b/binding/protobuf.go new file mode 100644 index 0000000..d6bef02 --- /dev/null +++ b/binding/protobuf.go @@ -0,0 +1,35 @@ +// Copyright 2014 Manu Martinez-Almeida. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package binding + +import ( + "github.com/golang/protobuf/proto" + + "io/ioutil" + "net/http" +) + +type protobufBinding struct{} + +func (_ protobufBinding) Name() string { + return "protobuf" +} + +func (_ protobufBinding) Bind(req *http.Request, obj interface{}) error { + + buf, err := ioutil.ReadAll(req.Body) + if err != nil { + return err + } + + if err = proto.Unmarshal(buf, obj.(proto.Message)); err != nil { + return err + } + + //Here it's same to return validate(obj), but util now we cann't add `binding:""` to the struct + //which automatically generate by gen-proto + return nil + //return validate(obj) +}