Skip to content

Commit

Permalink
Generic Fuego main handler
Browse files Browse the repository at this point in the history
  • Loading branch information
EwenQuim committed Dec 22, 2024
1 parent 5ca1eea commit 405e625
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 51 deletions.
25 changes: 20 additions & 5 deletions ctx.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ type ContextWithBody[B any] interface {
// SetStatus sets the status code of the response.
// Alias to http.ResponseWriter.WriteHeader.
SetStatus(code int)
// SetDefaultStatusCode sets the status code of the response defined in the options.
// Is automatically done by Fuego but you might need it.
SetDefaultStatusCode()

// Redirect redirects to the given url with the given status code.
// Example:
Expand All @@ -102,17 +105,22 @@ type ContextWithBody[B any] interface {
// })
Redirect(code int, url string) (any, error)

Serialize(data any) error // Serialize serializes the given data to the response. It uses the Content-Type header to determine the serialization format.
SerializeError(err error) // SerializeError serializes the given error to the response. It uses the Content-Type header to determine the serialization format.
// Serialize serializes the given data to the response.
// Is automatically done by Fuego but you might need it.
Serialize(data any) error
// SerializeError serializes the given error to the response.
// Is automatically done by Fuego but you might need it.
SerializeError(err error)
}

// NewNetHTTPContext returns a new context. It is used internally by Fuego. You probably want to use Ctx[B] instead.
func NewNetHTTPContext[B any](route BaseRoute, w http.ResponseWriter, r *http.Request, options readOptions) *netHttpContext[B] {
c := &netHttpContext[B]{
CommonContext: internal.CommonContext[B]{
CommonCtx: r.Context(),
UrlValues: r.URL.Query(),
OpenAPIParams: route.Params,
CommonCtx: r.Context(),
UrlValues: r.URL.Query(),
OpenAPIParams: route.Params,
DefaultStatusCode: route.DefaultStatusCode,
},
Req: r,
Res: w,
Expand Down Expand Up @@ -279,6 +287,13 @@ func (c netHttpContext[B]) SerializeError(err error) {
c.serializeError(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
3 changes: 3 additions & 0 deletions internal/common_context.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ type CommonContext[B any] struct {

UrlValues url.Values
OpenAPIParams map[string]OpenAPIParam // list of expected query parameters (declared in the OpenAPI spec)

// default status code for the response
DefaultStatusCode int
}

type ParamType string // Query, Header, Cookie
Expand Down
97 changes: 51 additions & 46 deletions serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,13 +66,12 @@ func HTTPHandler[ReturnType, Body any](s *Server, controller func(c ContextWithB
}

return func(w http.ResponseWriter, r *http.Request) {
// CONTEXT INITIALIZATION
timeCtxInit := time.Now()
var templates *template.Template
if s.template != nil {
templates = template.Must(s.template.Clone())
}

// CONTEXT INITIALIZATION
ctx := NewNetHTTPContext[Body](*route, w, r, readOptions{
DisallowUnknownFields: s.DisallowUnknownFields,
MaxBodySize: s.maxBodySize,
Expand All @@ -81,54 +80,60 @@ func HTTPHandler[ReturnType, Body any](s *Server, controller func(c ContextWithB
ctx.serializeError = s.SerializeError
ctx.fs = s.fs
ctx.templates = templates
ctx.SetHeader("X-Powered-By", "Fuego")
ctx.SetHeader("Trailer", "Server-Timing")

// PARAMS VALIDATION
err := ValidateParams(ctx)
if err != nil {
err = s.ErrorHandler(err)
ctx.SerializeError(err)
return
}

timeController := time.Now()
ctx.SetHeader("Server-Timing", Timing{"fuegoReqInit", timeController.Sub(timeCtxInit), ""}.String())
flow(s.Engine, ctx, controller)
}
}

// CONTROLLER
ans, err := controller(ctx)
if err != nil {
err = s.ErrorHandler(err)
ctx.SerializeError(err)
return
}
ctx.SetHeader("Server-Timing", Timing{"controller", time.Since(timeController), ""}.String())
// Generic handler for Fuego controllers.
func flow[B, T any](s *Engine, ctx ContextWithBody[B], controller func(c ContextWithBody[B]) (T, error)) {
ctx.SetHeader("X-Powered-By", "Fuego")
ctx.SetHeader("Trailer", "Server-Timing")

if route.DefaultStatusCode != 0 {
ctx.SetStatus(route.DefaultStatusCode)
}
timeCtxInit := time.Now()

if reflect.TypeOf(ans) == nil {
return
}
// PARAMS VALIDATION
err := ValidateParams(ctx)
if err != nil {
err = s.ErrorHandler(err)
ctx.SerializeError(err)
return
}

// TRANSFORM OUT
timeTransformOut := time.Now()
ans, err = transformOut(ctx.Context(), ans)
if err != nil {
err = s.ErrorHandler(err)
ctx.SerializeError(err)
return
}
timeAfterTransformOut := time.Now()
ctx.SetHeader("Server-Timing", Timing{"transformOut", timeAfterTransformOut.Sub(timeTransformOut), "transformOut"}.String())

// SERIALIZATION
err = ctx.Serialize(ans)
if err != nil {
err = s.ErrorHandler(err)
ctx.SerializeError(err)
}
ctx.SetHeader("Server-Timing", Timing{"serialize", time.Since(timeAfterTransformOut), ""}.String())
timeController := time.Now()
ctx.SetHeader("Server-Timing", Timing{"fuegoReqInit", timeController.Sub(timeCtxInit), ""}.String())

// CONTROLLER
ans, err := controller(ctx)
if err != nil {
err = s.ErrorHandler(err)
ctx.SerializeError(err)
return
}
ctx.SetHeader("Server-Timing", Timing{"controller", time.Since(timeController), ""}.String())

ctx.SetDefaultStatusCode()

if reflect.TypeOf(ans) == nil {
return
}

// TRANSFORM OUT
timeTransformOut := time.Now()
ans, err = transformOut(ctx.Context(), ans)
if err != nil {
err = s.ErrorHandler(err)
ctx.SerializeError(err)
return
}
timeAfterTransformOut := time.Now()
ctx.SetHeader("Server-Timing", Timing{"transformOut", timeAfterTransformOut.Sub(timeTransformOut), "transformOut"}.String())

// SERIALIZATION
err = ctx.Serialize(ans)
if err != nil {
err = s.ErrorHandler(err)
ctx.SerializeError(err)
}
ctx.SetHeader("Server-Timing", Timing{"serialize", time.Since(timeAfterTransformOut), ""}.String())
}

0 comments on commit 405e625

Please # to comment.