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

✨ feature: add XML to context. #2003

Merged
merged 2 commits into from Aug 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
70 changes: 43 additions & 27 deletions app.go
Expand Up @@ -23,6 +23,7 @@ import (
"time"

"encoding/json"
"encoding/xml"

"github.com/gofiber/fiber/v2/utils"
"github.com/valyala/fasthttp"
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
//
Expand Down Expand Up @@ -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{
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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...)
Expand Down
19 changes: 13 additions & 6 deletions ctx.go
Expand Up @@ -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)
}
Expand Down Expand Up @@ -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 {
efectn marked this conversation as resolved.
Show resolved Hide resolved
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 {
Expand Down
60 changes: 60 additions & 0 deletions ctx_test.go
Expand Up @@ -12,6 +12,7 @@ import (
"bytes"
"compress/gzip"
"context"
"encoding/xml"
"errors"
"fmt"
"io"
Expand Down Expand Up @@ -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, `<Users><Names>Grame</Names><Names>John</Names><Ages>1</Ages><Ages>12</Ages><Ages>20</Ages></Users>`, 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("", `<string></string>`)
testEmpty(0, "<int>0</int>")
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, `<SomeStruct><Name>Grame</Name><Age>20</Age></SomeStruct>`, string(c.Response().Body()))
}

// go test -run Test_Ctx_Links
func Test_Ctx_Links(t *testing.T) {
t.Parallel()
Expand Down
4 changes: 4 additions & 0 deletions utils/xml.go
@@ -0,0 +1,4 @@
package utils

// XMLMarshal returns the XML encoding of v.
type XMLMarshal func(v interface{}) ([]byte, error)
59 changes: 59 additions & 0 deletions 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 = `<servers version="1"><server><name>fiber one</name></server><server><name>fiber two</name></server></servers>`

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)
}