diff --git a/context.go b/context.go index 5db121a5..7a92e4d2 100644 --- a/context.go +++ b/context.go @@ -26,6 +26,7 @@ const ( MIMEHTML = binding.MIMEHTML MIMEXML = binding.MIMEXML MIMEXML2 = binding.MIMEXML2 + MIMEPROTOBUF = binding.MIMEPROTOBUF MIMEPlain = binding.MIMEPlain MIMEPOSTForm = binding.MIMEPOSTForm MIMEMultipartPOSTForm = binding.MIMEMultipartPOSTForm @@ -426,6 +427,12 @@ func (c *Context) XML(code int, obj interface{}) { c.Render(code, render.XML{Data: obj}) } +// Protobuf serializes the given struct as PB binary-stream into response body +// It also sets the Content-Type as "application/x-protobuf" +func (c *Context) Protobuf(code int, obj interface{}) { + c.Render(code, render.Protobuf{Data: obj}) +} + // String writes the given string into the response body. func (c *Context) String(code int, format string, values ...interface{}) { c.Status(code) @@ -489,6 +496,7 @@ type Negotiate struct { HTMLData interface{} JSONData interface{} XMLData interface{} + PBData interface{} Data interface{} } @@ -506,6 +514,10 @@ func (c *Context) Negotiate(code int, config Negotiate) { data := chooseData(config.XMLData, config.Data) c.XML(code, data) + case binding.MIMEPROTOBUF: + data := chooseData(config.PBData, config.Data) + c.Protobuf(code, data) + default: c.AbortWithError(http.StatusNotAcceptable, errors.New("the accepted formats are not offered by the server")) } diff --git a/context_test.go b/context_test.go index 322c4829..59bf198b 100644 --- a/context_test.go +++ b/context_test.go @@ -15,6 +15,8 @@ import ( "testing" "time" + "github.com/gin-gonic/gin/binding/example" + "github.com/golang/protobuf/proto" "github.com/manucorporat/sse" "github.com/stretchr/testify/assert" ) @@ -374,6 +376,16 @@ func TestContextRenderXML(t *testing.T) { assert.Equal(t, w.HeaderMap.Get("Content-Type"), "application/xml; charset=utf-8") } +// TestContextRenderProtobuf tests that the response is serialized as Protobuf binary-stream +// and Content-Type is set to application/x-protobuf +func TestContextRenderProtobuf(t *testing.T) { + c, w, _ := CreateTestContext() + c.Protobuf(201, &example.Test{Label: proto.String("test")}) + + assert.Equal(t, w.Code, 201) + assert.Equal(t, w.HeaderMap.Get("Content-Type"), "application/x-protobuf") +} + // TestContextString tests that the response is returned // with Content-Type set to text/plain func TestContextRenderString(t *testing.T) { @@ -500,15 +512,17 @@ func TestContextNegotiationFormat(t *testing.T) { assert.Panics(t, func() { c.NegotiateFormat() }) assert.Equal(t, c.NegotiateFormat(MIMEJSON, MIMEXML), MIMEJSON) assert.Equal(t, c.NegotiateFormat(MIMEHTML, MIMEJSON), MIMEHTML) + assert.Equal(t, c.NegotiateFormat(MIMEPROTOBUF, MIMEPROTOBUF), MIMEPROTOBUF) } func TestContextNegotiationFormatWithAccept(t *testing.T) { c, _, _ := CreateTestContext() c.Request, _ = http.NewRequest("POST", "/", nil) - c.Request.Header.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8") + c.Request.Header.Add("Accept", "text/html,application/xhtml+xml,application/xml,application/x-protobuf;q=0.9,*/*;q=0.8") assert.Equal(t, c.NegotiateFormat(MIMEJSON, MIMEXML), MIMEXML) assert.Equal(t, c.NegotiateFormat(MIMEXML, MIMEHTML), MIMEHTML) + assert.Equal(t, c.NegotiateFormat(MIMEPROTOBUF, MIMEPROTOBUF), MIMEPROTOBUF) assert.Equal(t, c.NegotiateFormat(MIMEJSON), "") } diff --git a/render/protobuf.go b/render/protobuf.go new file mode 100644 index 00000000..2a19bfe1 --- /dev/null +++ b/render/protobuf.go @@ -0,0 +1,24 @@ +package render + +import ( + "github.com/golang/protobuf/proto" + "net/http" +) + +type Protobuf struct { + Data interface{} +} + +var pbContentType = []string{"application/x-protobuf"} + +func (r Protobuf) Render(w http.ResponseWriter) error { + writeContentType(w, pbContentType) + encoded, err := proto.Marshal(r.Data.(proto.Message)) + if err != nil { + return err + } + if _, err = w.Write(encoded); err != nil { + return err + } + return nil +} diff --git a/render/render_test.go b/render/render_test.go index 7a6ffb7d..602de9b8 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -10,6 +10,8 @@ import ( "net/http/httptest" "testing" + "github.com/gin-gonic/gin/binding/example" + "github.com/golang/protobuf/proto" "github.com/stretchr/testify/assert" ) @@ -82,6 +84,19 @@ func TestRenderXML(t *testing.T) { assert.Equal(t, w.Header().Get("Content-Type"), "application/xml; charset=utf-8") } +func TestRenderProtobuf(t *testing.T) { + w := httptest.NewRecorder() + data := &example.Test{ + Label: proto.String("test"), + } + + err := (Protobuf{data}).Render(w) + + assert.NoError(t, err) + // assert.Equal(t, w.Body.String(), "bar") + // assert.Equal(t, w.Header().Get("Content-Type"), "application/xml; charset=utf-8") +} + func TestRenderRedirect(t *testing.T) { // TODO }