From ac765410d3d7c62d45ee677e470a1910f704ff3a Mon Sep 17 00:00:00 2001 From: kkoehler Date: Tue, 24 Jun 2025 17:18:33 +0200 Subject: [PATCH 1/3] adjusted negotiation logic so that it is possible to add or overwrite render logic for sepcific content-types and added/referenced a default protobuf renderer. --- context.go | 62 ++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 48 insertions(+), 14 deletions(-) diff --git a/context.go b/context.go index bf12830c..9696d56d 100644 --- a/context.go +++ b/context.go @@ -1264,32 +1264,66 @@ 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...) + for bind, fn := range negotiationRenderMappings { + if bind == accepted { + 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. From adae8b76db8db51c431cd19c9ec6fe6ff46bc162 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20K=C3=B6hler?= Date: Fri, 27 Jun 2025 09:42:04 +0200 Subject: [PATCH 2/3] Update context.go Changed from iteration to lookup Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- context.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/context.go b/context.go index 9696d56d..45f1ae7b 100644 --- a/context.go +++ b/context.go @@ -1316,11 +1316,9 @@ var negotiationRenderMappings = map[string]NegotiationRenderFunc{ func (c *Context) Negotiate(code int, config Negotiate) { accepted := c.NegotiateFormat(config.Offered...) - for bind, fn := range negotiationRenderMappings { - if bind == accepted { - fn(code, config, c) - return - } + 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 From 22ae924b10f959da8fcd360b84281553f03f633a Mon Sep 17 00:00:00 2001 From: kkoehler Date: Fri, 27 Jun 2025 09:54:49 +0200 Subject: [PATCH 3/3] added test for protobuf negotiation logic and for the registration of custom contentType --- context_test.go | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/context_test.go b/context_test.go index fe3ccd69..27c404de 100644 --- a/context_test.go +++ b/context_test.go @@ -1542,6 +1542,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)