-
-
Notifications
You must be signed in to change notification settings - Fork 2k
Description
Feature Proposal Description
The current API for Ctx.Format does not align with res.format from Express. While they both perform content negotiation, the usage is different (see below for a comparison). This may be confusing for Gophers coming from Express who expect to be able to add their own handlers.
I propose reworking Ctx.Format to take a mapping of accept types to handlers. We can move the existing Ctx.Format functionality to a separate method (e.g., Ctx.AutoFormat)
Alignment with Express API
In Express, res.format is used to perform content negotiation and call a different handler based on the result. The handlers are fully user-defined aside from the default handler which will send a 406 HTML response.
From https://expressjs.com/en/api.html#res.format
res.format({
'text/plain': function () {
res.send('hey')
},
'text/html': function () {
res.send('<p>hey</p>')
},
'application/json': function () {
res.send({ message: 'hey' })
},
default: function () {
// log the request and respond with 406
res.status(406).send('Not Acceptable')
}
})Fiber's Ctx.Format has automatic handlers for a fixed set of Accept headers. The usage is different; it hides details of the request's Accept header and the outgoing Content Type from the implementer.
data := SomeData{ FirstName: "Jane", LastName: "Doe" }
c.Format(data)See below for proposed syntax.
HTTP RFC Standards Compliance
RFC-9110 does not specify how servers should reply to requests with respect to content - it simply gives various suggestions and algorithms. res.format follows the proactive negotiation algorithm.
API Stability
The new Ctx.Format should be in line with res.format, meaning it should have a stable API. The main risk for API stability is the data structure in which the handlers are passed, as that may have performance considerations (allocations, etc).
Feature Examples
package main
import (
"fmt"
"time"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/logger"
)
type IceCream struct {
Flavor string `json:"flavor" xml:"flavor"`
Color string `json:"color" xml:"color"`
}
func (ic IceCream) String() string {
return fmt.Sprintf("Ice Cream!\nFlavor: %v\nColor:%v", ic.Flavor, ic.Color)
}
func main() {
app := fiber.New()
app.Use("/", logger.New())
dessert := IceCream{"Strawberry", "Pink"}
app.Get("/dessert", func(c *fiber.Ctx) error {
// Since maps are unordered, */* will give us problems, even if
// we initialize the map once at startup.
return c.Format(map[string]fiber.Handler{
"application/json": func(c *fiber.Ctx) error { return c.JSON(dessert) },
"application/xml": func(c *fiber.Ctx) error { return c.XML(dessert) },
"text/plain": func(c *fiber.Ctx) error { return c.SendString(dessert.String()) },
"application/vnd.parlor+json": func(c *fiber.Ctx) error {
return c.JSON(fiber.Map{
"orderDate": time.Now(),
"course": "dessert",
"food": "iceCream",
})
},
})
})
app.Get("/dessert2", func(c *fiber.Ctx) error {
// This is a bit verbose. It might need some beautifying.
return c.Format2(
fiber.AcceptHandler{
MediaType: "application/json",
Handler: func(c *fiber.Ctx) error { return c.JSON(dessert) },
},
fiber.AcceptHandler{
MediaType: "application/xml",
Handler: func(c *fiber.Ctx) error { return c.XML(dessert) },
},
fiber.AcceptHandler{
MediaType: "text/plain",
Handler: func(c *fiber.Ctx) error { return c.SendString(dessert.String()) },
},
fiber.AcceptHandler{
MediaType: "application/vnd.parlor+json",
Handler: func(c *fiber.Ctx) error {
return c.JSON(fiber.Map{
"orderDate": time.Now(),
"course": "dessert",
"food": "iceCream",
})
},
},
)
})
app.Get("/dessert3", func(c *fiber.Ctx) error {
// Previous c.Format functionality.
return c.AutoFormat(dessert)
})
app.Listen(":3000")
}Checklist:
- I agree to follow Fiber's Code of Conduct.
- I have searched for existing issues that describe my proposal before opening this one.
- I understand that a proposal that does not meet these guidelines may be closed without explanation.
Metadata
Metadata
Assignees
Type
Projects
Status