Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BREAKING: add ContextFlowable too demand GetDefaultStatusCode #376

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 3 additions & 10 deletions ctx.go
Original file line number Diff line number Diff line change
Expand Up @@ -263,11 +263,11 @@ func (c *netHttpContext[B]) Body() (B, error) {
}

// Serialize serializes the given data to the response. It uses the Content-Type header to determine the serialization format.
func (c netHttpContext[B]) Serialize(data any) error {
func (c netHttpContext[B]) Serialize(code int, data any) error {
if c.serializer == nil {
return Send(c.Res, c.Req, data)
return Send(c.Res, c.Req, code, data)
}
return c.serializer(c.Res, c.Req, data)
return c.serializer(c.Res, c.Req, code, data)
}

// SerializeError serializes the given error to the response. It uses the Content-Type header to determine the serialization format.
Expand All @@ -279,13 +279,6 @@ func (c netHttpContext[B]) SerializeError(err error) {
c.errorSerializer(c.Res, c.Req, err)
}

// SetDefaultStatusCode sets the default status code of the response.
func (c netHttpContext[B]) SetDefaultStatusCode() {
if c.DefaultStatusCode != 0 {
c.SetStatus(c.DefaultStatusCode)
}
}

func body[B any](c netHttpContext[B]) (B, error) {
// Limit the size of the request body.
if c.readOptions.MaxBodySize != 0 {
Expand Down
7 changes: 2 additions & 5 deletions extra/fuegoecho/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,13 +102,10 @@ func (c echoContext[B]) SetStatus(code int) {
c.echoCtx.Response().WriteHeader(code)
}

func (c echoContext[B]) Serialize(data any) error {
func (c echoContext[B]) Serialize(code int, data any) error {
status := c.echoCtx.Response().Status
if status == 0 {
status = c.DefaultStatusCode
}
if status == 0 {
status = http.StatusOK
status = code
}
c.echoCtx.JSON(status, data)
return nil
Expand Down
2 changes: 2 additions & 0 deletions extra/fuegoecho/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ module github.com/go-fuego/fuego/extra/fuegoecho

go 1.23.3

replace github.com/go-fuego/fuego => ../..

require (
github.com/go-fuego/fuego v0.18.0-rc2
github.com/labstack/echo/v4 v4.13.3
Expand Down
2 changes: 0 additions & 2 deletions extra/fuegoecho/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3G
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
github.com/getkin/kin-openapi v0.128.0 h1:jqq3D9vC9pPq1dGcOCv7yOp1DaEe7c/T1vzcLbITSp4=
github.com/getkin/kin-openapi v0.128.0/go.mod h1:OZrfXzUfGrNbsKj+xmFBx6E5c6yH3At/tAKSc2UszXM=
github.com/go-fuego/fuego v0.18.0-rc2 h1:Ewm8+r+/B9Lr2FKLK9rFR2fxHRdjMQ/AaGkfZgBzv4s=
github.com/go-fuego/fuego v0.18.0-rc2/go.mod h1:OY7FkKD5g154J3mCTpY6SXqBQYrcI2Rg9N0Owft4WmU=
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
Expand Down
14 changes: 2 additions & 12 deletions extra/fuegogin/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,13 +102,10 @@ func (c ginContext[B]) SetStatus(code int) {
c.ginCtx.Status(code)
}

func (c ginContext[B]) Serialize(data any) error {
func (c ginContext[B]) Serialize(code int, data any) error {
status := c.ginCtx.Writer.Status()
if status == 0 {
status = c.DefaultStatusCode
}
if status == 0 {
status = http.StatusOK
status = code
}
c.ginCtx.JSON(status, data)
return nil
Expand All @@ -122,10 +119,3 @@ func (c ginContext[B]) SerializeError(err error) {
}
c.ginCtx.JSON(statusCode, err)
}

func (c ginContext[B]) SetDefaultStatusCode() {
if c.DefaultStatusCode == 0 {
c.DefaultStatusCode = http.StatusOK
}
c.SetStatus(c.DefaultStatusCode)
}
3 changes: 3 additions & 0 deletions extra/fuegogin/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ module github.com/go-fuego/fuego/extra/fuegogin

go 1.23.3

replace github.com/go-fuego/fuego => ../..

require (
github.com/gin-gonic/gin v1.10.0
github.com/go-fuego/fuego v0.17.0
Expand All @@ -21,6 +23,7 @@ require (
github.com/go-playground/validator/v10 v10.23.0 // indirect
github.com/goccy/go-json v0.10.4 // indirect
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/schema v1.4.1 // indirect
github.com/invopop/yaml v0.3.1 // indirect
github.com/josharian/intern v1.0.0 // indirect
Expand Down
10 changes: 6 additions & 4 deletions extra/fuegogin/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ github.com/gin-contrib/sse v1.0.0 h1:y3bT1mUWUxDpW4JLQg/HnTqV4rozuW4tC9eFKTxYI9E
github.com/gin-contrib/sse v1.0.0/go.mod h1:zNuFdwarAygJBht0NTKiSi3jRf6RbqeILZ9Sp6Slhe0=
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
github.com/go-fuego/fuego v0.17.0 h1:UBR0ib0Qq0XkGX8J3OTIwrlvHDGmysBVG3NHowA6UCQ=
github.com/go-fuego/fuego v0.17.0/go.mod h1:glaJIBAO3AaZx+c4jGYW4pHyhQkeMGuHx8qLchGAH8M=
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
Expand All @@ -37,9 +35,11 @@ github.com/goccy/go-json v0.10.4 h1:JSwxQzIqKfmFX1swYPpUThQZp/Ka4wzJdK0LWVytLPM=
github.com/goccy/go-json v0.10.4/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/schema v1.4.1 h1:jUg5hUjCSDZpNGLuXQOgIWGdlgrIdYvgQ0wZtdK1M3E=
github.com/gorilla/schema v1.4.1/go.mod h1:Dg5SSm5PV60mhF2NFaTV1xuYYj8tV8NOPRo4FggUMnM=
github.com/invopop/yaml v0.3.1 h1:f0+ZpmhfBSS4MhG+4HYseMdJhoeeopbSKbq5Rpeelso=
Expand Down Expand Up @@ -106,6 +106,8 @@ golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.36.2 h1:R8FeyR1/eLmkutZOM5CWghmo5itiG9z0ktFlTVLuTmU=
google.golang.org/protobuf v1.36.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
Expand Down
4 changes: 4 additions & 0 deletions internal/common_context.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ type CommonContext[B any] struct {
DefaultStatusCode int
}

func (c CommonContext[B]) GetDefaultStatusCode() int {
return c.DefaultStatusCode
}

type ParamType string // Query, Header, Cookie

// GetOpenAPIParams returns the OpenAPI parameters declared in the OpenAPI spec.
Expand Down
32 changes: 15 additions & 17 deletions serialization.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,25 +79,31 @@
return ans, nil
}

type Sender func(http.ResponseWriter, *http.Request, any) error
type Sender func(http.ResponseWriter, *http.Request, int, any) error

Check failure on line 82 in serialization.go

View workflow job for this annotation

GitHub Actions / golangci-lint

File is not properly formatted (gofumpt)
type internalSender func(http.ResponseWriter, *http.Request, any) error

// Send sends a response.
// The format is determined by the Accept header.
// If Accept header `*/*` is found Send will Attempt to send
// HTML, and then JSON.
func Send(w http.ResponseWriter, r *http.Request, ans any) (err error) {
func Send(w http.ResponseWriter, r *http.Request, code int, ans any) (err error) {
send := func(mimeType string, code int, ans any, sender internalSender) error {
w.Header().Set("Content-Type", mimeType)
w.WriteHeader(code)
return sender(w, r, ans)
}
for _, header := range parseAcceptHeader(r.Header) {
switch inferAcceptHeader(header, ans) {
switch header := inferAcceptHeader(header, ans); header {
case "application/xml":
err = SendXML(w, nil, ans)
err = send("application/xml", code, ans, SendXML)
case "text/html":
err = SendHTML(w, r, ans)
err = send("text/html; charset=utf-8", code, ans, SendHTML)
case "text/plain":
err = SendText(w, nil, ans)
err = send("text/plain; charset=utf-8", code, ans, SendText)
case "application/json":
err = SendJSON(w, nil, ans)
err = send("application/json", code, ans, SendJSON)
case "application/x-yaml", "text/yaml; charset=utf-8", "application/yaml": // https://www.rfc-editor.org/rfc/rfc9512.html
err = SendYAML(w, nil, ans)
err = send("application/x-yaml", code, ans, SendYAML)
default:
// if we don't support the header, try the next one
continue
Expand Down Expand Up @@ -128,7 +134,6 @@
}
}()

w.Header().Set("Content-Type", "application/x-yaml")
err = yaml.NewEncoder(w).Encode(ans)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
Expand All @@ -155,7 +160,6 @@
// Declared as a variable to be able to override it for clients that need to customize serialization.
// If serialization fails, it does NOT write to the response writer. It has to be passed to SendJSONError.
var SendJSON = func(w http.ResponseWriter, _ *http.Request, ans any) error {
w.Header().Set("Content-Type", "application/json")
err := json.NewEncoder(w).Encode(ans)
if err != nil {
slog.Error("Cannot serialize returned response to JSON", "error", err, "errtype", fmt.Sprintf("%T", err))
Expand Down Expand Up @@ -219,7 +223,6 @@
// Declared as a variable to be able to override it for clients that need to customize serialization.
// If serialization fails, it does NOT write to the response writer. It has to be passed to SendJSONError.
var SendXML = func(w http.ResponseWriter, _ *http.Request, ans any) error {
w.Header().Set("Content-Type", "application/xml")
err := xml.NewEncoder(w).Encode(ans)
if err != nil {
slog.Error("Cannot serialize returned response to XML", "error", err, "errtype", fmt.Sprintf("%T", err))
Expand Down Expand Up @@ -255,8 +258,6 @@
// SendHTML sends a HTML response.
// Declared as a variable to be able to override it for clients that need to customize serialization.
var SendHTML = func(w http.ResponseWriter, r *http.Request, ans any) error {
w.Header().Set("Content-Type", "text/html; charset=utf-8")

ctxRenderer, ok := any(ans).(CtxRenderer)
if ok {
return ctxRenderer.Render(r.Context(), w)
Expand Down Expand Up @@ -299,8 +300,6 @@
// SendText sends a HTML response.
// Declared as a variable to be able to override it for clients that need to customize serialization.
func SendText(w http.ResponseWriter, _ *http.Request, ans any) error {
var err error
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
stringToWrite, ok := any(ans).(string)
if !ok {
stringToWritePtr, okPtr := any(ans).(*string)
Expand All @@ -310,8 +309,7 @@
stringToWrite = fmt.Sprintf("%v", ans)
}
}
_, err = w.Write([]byte(stringToWrite))

_, err := w.Write([]byte(stringToWrite))
return err
}

Expand Down
4 changes: 2 additions & 2 deletions serialization_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func TestSend(t *testing.T) {
w := httptest.NewRecorder()
r := httptest.NewRequest(http.MethodGet, "/", nil)
r.Header.Set("Accept", "application/json")
Send(w, r, &StdRenderer{
Send(w, r, 200, &StdRenderer{
templates: template,
templateToExecute: templateName,
})
Expand All @@ -44,7 +44,7 @@ func TestSendWhenError(t *testing.T) {
r := httptest.NewRequest(http.MethodGet, "/", nil)
r.Header.Set("Accept", "text/junk,application/json,text/html")
errorWriter := &errorWriter{}
err := Send(errorWriter, r, response{})
err := Send(errorWriter, r, 0, response{})
require.Error(t, err)
SendError(w, r, err)
require.Equal(t, "application/json", w.Header().Get("Content-Type"))
Expand Down
13 changes: 7 additions & 6 deletions serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,10 +106,9 @@ func HTTPHandler[ReturnType, Body any](s *Server, controller func(c ContextWithB
type ContextFlowable[B any] interface {
ContextWithBody[B]

// SetDefaultStatusCode sets the status code of the response defined in the options.
SetDefaultStatusCode()
GetDefaultStatusCode() int
// Serialize serializes the given data to the response.
Serialize(data any) error
Serialize(code int, data any) error
// SerializeError serializes the given error to the response.
SerializeError(err error)
}
Expand Down Expand Up @@ -141,8 +140,6 @@ func Flow[B, T any](s *Engine, ctx ContextFlowable[B], controller func(c Context
}
ctx.SetHeader("Server-Timing", Timing{"controller", "", time.Since(timeController)}.String())

ctx.SetDefaultStatusCode()

if reflect.TypeOf(ans) == nil {
return
}
Expand All @@ -158,8 +155,12 @@ func Flow[B, T any](s *Engine, ctx ContextFlowable[B], controller func(c Context
timeAfterTransformOut := time.Now()
ctx.SetHeader("Server-Timing", Timing{"transformOut", "transformOut", timeAfterTransformOut.Sub(timeTransformOut)}.String())

code := ctx.GetDefaultStatusCode()
if code == 0 {
code = http.StatusOK
}
// SERIALIZATION
err = ctx.Serialize(ans)
err = ctx.Serialize(code, ans)
if err != nil {
err = s.ErrorHandler(err)
ctx.SerializeError(err)
Expand Down
10 changes: 0 additions & 10 deletions server.go
Original file line number Diff line number Diff line change
Expand Up @@ -335,16 +335,6 @@ func WithAddr(addr string) func(*Server) {
}
}

// WithXML sets the serializer to XML
//
// Deprecated: fuego supports automatic XML serialization when using the header "Accept: application/xml".
func WithXML() func(*Server) {
return func(c *Server) {
c.Serialize = SendXML
c.SerializeError = SendXMLError
}
}

// WithLogHandler sets the log handler of the server.
func WithLogHandler(handler slog.Handler) func(*Server) {
return func(*Server) {
Expand Down
2 changes: 1 addition & 1 deletion server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,7 @@ func dummyController(_ ContextWithBody[ReqBody]) (Resp, error) {

func TestCustomSerialization(t *testing.T) {
s := NewServer(
WithSerializer(func(w http.ResponseWriter, r *http.Request, a any) error {
WithSerializer(func(w http.ResponseWriter, r *http.Request, _ int, a any) error {
w.WriteHeader(202)
_, err := w.Write([]byte("custom serialization"))
return err
Expand Down
Loading