diff --git a/context.go b/context.go index d7280c5d..93c30fc7 100644 --- a/context.go +++ b/context.go @@ -1280,32 +1280,64 @@ type Negotiate struct { TOMLData any } -// Negotiate calls different Render according to acceptable Accept format. -func (c *Context) Negotiate(code int, config Negotiate) { - switch c.NegotiateFormat(config.Offered...) { - case binding.MIMEJSON: +func NewNegotiate(offered []string) Negotiate { + return Negotiate{Offered: offered} +} +func (receiver Negotiate) WithData(data any) Negotiate { + receiver.Data = data + return receiver +} + +// NegotiationRenderFunc is responsible for rendering data in a specific format. +type NegotiationRenderFunc func(status int, config Negotiate, c *Context) + +func AddNegotiationRenderMapping(mimeType string, binding NegotiationRenderFunc) { + negotiationRenderMappings[mimeType] = binding +} + +// All predefined negotiationRenderMappings - associate a content type +// with a NegotiationRenderFunc, which is responsible for rendering +// data in a specific format. +var negotiationRenderMappings = map[string]NegotiationRenderFunc{ + binding.MIMEJSON: func(code int, config Negotiate, c *Context) { data := chooseData(config.JSONData, config.Data) c.JSON(code, data) - - case binding.MIMEHTML: + }, + binding.MIMEHTML: func(code int, config Negotiate, c *Context) { data := chooseData(config.HTMLData, config.Data) c.HTML(code, config.HTMLName, data) - - case binding.MIMEXML: + }, + binding.MIMEXML: func(code int, config Negotiate, c *Context) { data := chooseData(config.XMLData, config.Data) c.XML(code, data) - - case binding.MIMEYAML, binding.MIMEYAML2: + }, + binding.MIMEYAML: func(code int, config Negotiate, c *Context) { data := chooseData(config.YAMLData, config.Data) c.YAML(code, data) - - case binding.MIMETOML: + }, + binding.MIMEYAML2: func(code int, config Negotiate, c *Context) { + data := chooseData(config.YAMLData, config.Data) + c.YAML(code, data) + }, + binding.MIMETOML: func(code int, config Negotiate, c *Context) { data := chooseData(config.TOMLData, config.Data) c.TOML(code, data) + }, + binding.MIMEPROTOBUF: func(code int, config Negotiate, c *Context) { + c.ProtoBuf(code, config.Data) + }, +} - default: - c.AbortWithError(http.StatusNotAcceptable, errors.New("the accepted formats are not offered by the server")) //nolint: errcheck +// Negotiate calls different Render according to acceptable Accept format. +func (c *Context) Negotiate(code int, config Negotiate) { + + accepted := c.NegotiateFormat(config.Offered...) + if fn, ok := negotiationRenderMappings[accepted]; ok { + fn(code, config, c) + return } + c.AbortWithError(http.StatusNotAcceptable, errors.New("the accepted formats are not offered by the server")) //nolint: errcheck + } // NegotiateFormat returns an acceptable Accept format. diff --git a/context_test.go b/context_test.go index cc066ef8..ab6bbe20 100644 --- a/context_test.go +++ b/context_test.go @@ -1615,6 +1615,44 @@ func TestContextNegotiationWithHTML(t *testing.T) { assert.Equal(t, "text/html; charset=utf-8", w.Header().Get("Content-Type")) } +func TestContextNegotiationWithProto(t *testing.T) { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + c.Request, _ = http.NewRequest(http.MethodPost, "", nil) + + c.Negotiate(http.StatusOK, Negotiate{ + Offered: []string{binding.MIMEPROTOBUF}, + Data: &testdata.Test{}, + }) + + assert.Equal(t, http.StatusOK, w.Code) + assert.Equal(t, 0, w.Body.Len()) + assert.Equal(t, "application/x-protobuf", w.Header().Get("Content-Type")) +} + +func TestContextNegotiationWithCustom(t *testing.T) { + + contentType := "application/whatever" + + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + c.Request, _ = http.NewRequest(http.MethodPost, "", nil) + + AddNegotiationRenderMapping(contentType, func(status int, config Negotiate, c *Context) { + data := config.Data.([]byte) + c.Data(status, contentType, data) + }) + + c.Negotiate(http.StatusOK, Negotiate{ + Offered: []string{contentType}, + Data: []byte("Hello World"), + }) + + assert.Equal(t, http.StatusOK, w.Code) + assert.Equal(t, "Hello World", w.Body.String()) + assert.Equal(t, contentType, w.Header().Get("Content-Type")) +} + func TestContextNegotiationNotSupport(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w)