diff --git a/app.go b/app.go
index ae922aa007..c28aeb6389 100644
--- a/app.go
+++ b/app.go
@@ -23,6 +23,7 @@ import (
"time"
"encoding/json"
+ "encoding/xml"
"github.com/gofiber/fiber/v2/utils"
"github.com/valyala/fasthttp"
@@ -63,17 +64,18 @@ type Storage interface {
// ErrorHandler defines a function that will process all errors
// returned from any handlers in the stack
-// cfg := fiber.Config{}
-// cfg.ErrorHandler = func(c *Ctx, err error) error {
-// code := StatusInternalServerError
-// var e *fiber.Error
-// if errors.As(err, &e) {
-// code = e.Code
-// }
-// c.Set(HeaderContentType, MIMETextPlainCharsetUTF8)
-// return c.Status(code).SendString(err.Error())
-// }
-// app := fiber.New(cfg)
+//
+// cfg := fiber.Config{}
+// cfg.ErrorHandler = func(c *Ctx, err error) error {
+// code := StatusInternalServerError
+// var e *fiber.Error
+// if errors.As(err, &e) {
+// code = e.Code
+// }
+// c.Set(HeaderContentType, MIMETextPlainCharsetUTF8)
+// return c.Status(code).SendString(err.Error())
+// }
+// app := fiber.New(cfg)
type ErrorHandler = func(*Ctx, error) error
// Error represents an error that occurred while handling a request.
@@ -323,6 +325,13 @@ type Config struct {
// Default: json.Unmarshal
JSONDecoder utils.JSONUnmarshal `json:"-"`
+ // XMLEncoder set by an external client of Fiber it will use the provided implementation of a
+ // XMLMarshal
+ //
+ // Allowing for flexibility in using another XML library for encoding
+ // Default: xml.Marshal
+ XMLEncoder utils.XMLMarshal `json:"-"`
+
// Known networks are "tcp", "tcp4" (IPv4-only), "tcp6" (IPv6-only)
// WARNING: When prefork is set to true, only "tcp4" and "tcp6" can be chose.
//
@@ -438,12 +447,15 @@ var DefaultErrorHandler = func(c *Ctx, err error) error {
}
// New creates a new Fiber named instance.
-// app := fiber.New()
+//
+// app := fiber.New()
+//
// You can pass optional configuration options by passing a Config struct:
-// app := fiber.New(fiber.Config{
-// Prefork: true,
-// ServerHeader: "Fiber",
-// })
+//
+// app := fiber.New(fiber.Config{
+// Prefork: true,
+// ServerHeader: "Fiber",
+// })
func New(config ...Config) *App {
// Create a new app
app := &App{
@@ -509,6 +521,9 @@ func New(config ...Config) *App {
if app.config.JSONDecoder == nil {
app.config.JSONDecoder = json.Unmarshal
}
+ if app.config.XMLEncoder == nil {
+ app.config.XMLEncoder = xml.Marshal
+ }
if app.config.Network == "" {
app.config.Network = NetworkTCP4
}
@@ -609,15 +624,15 @@ func (app *App) GetRoute(name string) Route {
// Use registers a middleware route that will match requests
// with the provided prefix (which is optional and defaults to "/").
//
-// app.Use(func(c *fiber.Ctx) error {
-// return c.Next()
-// })
-// app.Use("/api", func(c *fiber.Ctx) error {
-// return c.Next()
-// })
-// app.Use("/api", handler, func(c *fiber.Ctx) error {
-// return c.Next()
-// })
+// app.Use(func(c *fiber.Ctx) error {
+// return c.Next()
+// })
+// app.Use("/api", func(c *fiber.Ctx) error {
+// return c.Next()
+// })
+// app.Use("/api", handler, func(c *fiber.Ctx) error {
+// return c.Next()
+// })
//
// This method will match all HTTP verbs: GET, POST, PUT, HEAD etc...
func (app *App) Use(args ...interface{}) Router {
@@ -710,8 +725,9 @@ func (app *App) All(path string, handlers ...Handler) Router {
}
// Group is used for Routes with common prefix to define a new sub-router with optional middleware.
-// api := app.Group("/api")
-// api.Get("/users", handler)
+//
+// api := app.Group("/api")
+// api.Get("/users", handler)
func (app *App) Group(prefix string, handlers ...Handler) Router {
if len(handlers) > 0 {
app.register(methodUse, prefix, handlers...)
diff --git a/ctx.go b/ctx.go
index 004cf3ec7a..30aed27682 100644
--- a/ctx.go
+++ b/ctx.go
@@ -518,12 +518,7 @@ func (c *Ctx) Format(body interface{}) error {
case "txt":
return c.SendString(b)
case "xml":
- raw, err := xml.Marshal(body)
- if err != nil {
- return fmt.Errorf("error serializing xml: %v", body)
- }
- c.fasthttp.Response.SetBody(raw)
- return nil
+ return c.XML(body)
}
return c.SendString(b)
}
@@ -736,6 +731,18 @@ func (c *Ctx) JSONP(data interface{}, callback ...string) error {
return c.SendString(result)
}
+// XML converts any interface or string to XML.
+// This method also sets the content header to application/xml.
+func (c *Ctx) XML(data interface{}) error {
+ raw, err := c.app.config.XMLEncoder(data)
+ if err != nil {
+ return err
+ }
+ c.fasthttp.Response.SetBodyRaw(raw)
+ c.fasthttp.Response.Header.SetContentType(MIMEApplicationXML)
+ return nil
+}
+
// Links joins the links followed by the property to populate the response's Link HTTP header field.
func (c *Ctx) Links(link ...string) {
if len(link) == 0 {
diff --git a/ctx_test.go b/ctx_test.go
index 325b67d78b..3af6e806b0 100644
--- a/ctx_test.go
+++ b/ctx_test.go
@@ -12,6 +12,7 @@ import (
"bytes"
"compress/gzip"
"context"
+ "encoding/xml"
"errors"
"fmt"
"io"
@@ -2134,6 +2135,65 @@ func Benchmark_Ctx_JSONP(b *testing.B) {
utils.AssertEqual(b, `emit({"Name":"Grame","Age":20});`, string(c.Response().Body()))
}
+// go test -run Test_Ctx_XML
+func Test_Ctx_XML(t *testing.T) {
+ t.Parallel()
+ app := New()
+ c := app.AcquireCtx(&fasthttp.RequestCtx{})
+ defer app.ReleaseCtx(c)
+
+ utils.AssertEqual(t, true, c.JSON(complex(1, 1)) != nil)
+
+ type xmlResult struct {
+ XMLName xml.Name `xml:"Users"`
+ Names []string `xml:"Names"`
+ Ages []int `xml:"Ages"`
+ }
+
+ c.XML(xmlResult{
+ Names: []string{"Grame", "John"},
+ Ages: []int{1, 12, 20},
+ })
+
+ utils.AssertEqual(t, `GrameJohn11220`, string(c.Response().Body()))
+ utils.AssertEqual(t, "application/xml", string(c.Response().Header.Peek("content-type")))
+
+ testEmpty := func(v interface{}, r string) {
+ err := c.XML(v)
+ utils.AssertEqual(t, nil, err)
+ utils.AssertEqual(t, r, string(c.Response().Body()))
+ }
+
+ testEmpty(nil, "")
+ testEmpty("", ``)
+ testEmpty(0, "0")
+ testEmpty([]int{}, "")
+}
+
+// go test -run=^$ -bench=Benchmark_Ctx_XML -benchmem -count=4
+func Benchmark_Ctx_XML(b *testing.B) {
+ app := New()
+ c := app.AcquireCtx(&fasthttp.RequestCtx{})
+ defer app.ReleaseCtx(c)
+ type SomeStruct struct {
+ Name string `xml:"Name"`
+ Age uint8 `xml:"Age"`
+ }
+ data := SomeStruct{
+ Name: "Grame",
+ Age: 20,
+ }
+ var err error
+ b.ReportAllocs()
+ b.ResetTimer()
+ for n := 0; n < b.N; n++ {
+ err = c.XML(data)
+ }
+
+ utils.AssertEqual(b, nil, err)
+ utils.AssertEqual(b, `Grame20`, string(c.Response().Body()))
+}
+
// go test -run Test_Ctx_Links
func Test_Ctx_Links(t *testing.T) {
t.Parallel()
diff --git a/utils/xml.go b/utils/xml.go
new file mode 100644
index 0000000000..cc6a024a3b
--- /dev/null
+++ b/utils/xml.go
@@ -0,0 +1,4 @@
+package utils
+
+// XMLMarshal returns the XML encoding of v.
+type XMLMarshal func(v interface{}) ([]byte, error)
diff --git a/utils/xml_test.go b/utils/xml_test.go
new file mode 100644
index 0000000000..bbb11708d6
--- /dev/null
+++ b/utils/xml_test.go
@@ -0,0 +1,59 @@
+package utils
+
+import (
+ "encoding/xml"
+ "testing"
+)
+
+type serversXMLStructure struct {
+ XMLName xml.Name `xml:"servers"`
+ Version string `xml:"version,attr"`
+ Servers []serverXMLStructure `xml:"server"`
+}
+
+type serverXMLStructure struct {
+ XMLName xml.Name `xml:"server"`
+ Name string `xml:"name"`
+}
+
+var xmlString = `fiber onefiber two`
+
+func Test_GolangXMLEncoder(t *testing.T) {
+ t.Parallel()
+
+ var (
+ ss = &serversXMLStructure{
+ Version: "1",
+ Servers: []serverXMLStructure{
+ {Name: "fiber one"},
+ {Name: "fiber two"},
+ },
+ }
+ xmlEncoder XMLMarshal = xml.Marshal
+ )
+
+ raw, err := xmlEncoder(ss)
+ AssertEqual(t, err, nil)
+
+ AssertEqual(t, string(raw), xmlString)
+}
+
+func Test_DefaultXMLEncoder(t *testing.T) {
+ t.Parallel()
+
+ var (
+ ss = &serversXMLStructure{
+ Version: "1",
+ Servers: []serverXMLStructure{
+ {Name: "fiber one"},
+ {Name: "fiber two"},
+ },
+ }
+ xmlEncoder XMLMarshal = xml.Marshal
+ )
+
+ raw, err := xmlEncoder(ss)
+ AssertEqual(t, err, nil)
+
+ AssertEqual(t, string(raw), xmlString)
+}