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

🔥 feat: Add support for CBOR encoding #3173

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
22 changes: 21 additions & 1 deletion app.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ import (
"sync"
"time"

"github.com/fxamacker/cbor/v2"
"github.com/gofiber/fiber/v3/log"
"github.com/gofiber/utils/v2"

"github.com/valyala/fasthttp"
)

Expand Down Expand Up @@ -318,6 +318,20 @@ type Config struct { //nolint:govet // Aligning the struct fields is not necessa
// Default: json.Unmarshal
JSONDecoder utils.JSONUnmarshal `json:"-"`

// When set by an external client of Fiber it will use the provided implementation of a
// CBORMarshal
//
// Allowing for flexibility in using another cbor library for encoding
// Default: cbor.Marshal
CBOREncoder utils.CBORMarshal `json:"-"`

// When set by an external client of Fiber it will use the provided implementation of a
// CBORUnmarshal
//
// Allowing for flexibility in using another cbor library for decoding
// Default: cbor.Unmarshal
CBORDecoder utils.CBORUnmarshal `json:"-"`

// XMLEncoder set by an external client of Fiber it will use the provided implementation of a
// XMLMarshal
//
Expand Down Expand Up @@ -535,6 +549,12 @@ func New(config ...Config) *App {
if app.config.JSONDecoder == nil {
app.config.JSONDecoder = json.Unmarshal
}
if app.config.CBOREncoder == nil {
app.config.CBOREncoder = cbor.Marshal
}
if app.config.CBORDecoder == nil {
app.config.CBORDecoder = cbor.Unmarshal
}
if app.config.XMLEncoder == nil {
app.config.XMLEncoder = xml.Marshal
}
Expand Down
1 change: 1 addition & 0 deletions constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const (
MIMETextCSS = "text/css"
MIMEApplicationXML = "application/xml"
MIMEApplicationJSON = "application/json"
MIMEApplicationCBOR = "application/cbor"
// Deprecated: use MIMETextJavaScript instead
MIMEApplicationJavaScript = "application/javascript"
MIMEApplicationForm = "application/x-www-form-urlencoded"
Expand Down
18 changes: 18 additions & 0 deletions ctx.go
Original file line number Diff line number Diff line change
Expand Up @@ -883,6 +883,24 @@ func (c *DefaultCtx) JSON(data any, ctype ...string) error {
return nil
}

// CBOR converts any interface or string to cbor encoded bytes.
// If the ctype parameter is given, this method will set the
// Content-Type header equal to ctype. If ctype is not given,
// The Content-Type header will be set to application/cbor.
func (c *DefaultCtx) CBOR(data any, ctype ...string) error {
raw, err := c.app.config.CBOREncoder(data)
if err != nil {
return err
}
c.fasthttp.Response.SetBodyRaw(raw)
if len(ctype) > 0 {
c.fasthttp.Response.Header.SetContentType(ctype[0])
} else {
c.fasthttp.Response.Header.SetContentType(MIMEApplicationCBOR)
}
return nil
}

// JSONP sends a JSON response with JSONP support.
// This method is identical to JSON, except that it opts-in to JSONP callback support.
// By default, the callback name is simply callback.
Expand Down
5 changes: 5 additions & 0 deletions ctx_interface_gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

86 changes: 86 additions & 0 deletions ctx_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"context"
"crypto/tls"
"embed"
"encoding/hex"
"encoding/xml"
"errors"
"fmt"
Expand Down Expand Up @@ -3572,6 +3573,91 @@ func Benchmark_Ctx_JSON(b *testing.B) {
require.Equal(b, `{"Name":"Grame","Age":20}`, string(c.Response().Body()))
}

// go test -run Test_Ctx_CBOR
func Test_Ctx_CBOR(t *testing.T) {
t.Parallel()
app := New()
c := app.AcquireCtx(&fasthttp.RequestCtx{})

require.Error(t, c.CBOR(complex(1, 1)))

Comment on lines +3625 to +3626
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Enhance error handling test coverage

The error test for complex numbers is good, but should be expanded to cover more error cases:

  1. Test encoding of channels (should error)
  2. Test encoding of functions (should error)
  3. Test encoding of recursive structures (should error)
  4. Test encoding of very large numbers that may overflow
// Test invalid types
err = c.CBOR(make(chan int))
require.Error(t, err)

err = c.CBOR(func() {})
require.Error(t, err)

// Test recursive struct
type Recursive struct {
    Next *Recursive
}
r := &Recursive{}
r.Next = r
err = c.CBOR(r)
require.Error(t, err)

type dummyStruct struct {
Name string
Age int
}

// Test without ctype
err := c.CBOR(dummyStruct{ // map has no order
Name: "Grame",
Age: 20,
})
require.NoError(t, err)
require.Equal(t, `a2644e616d65654772616d656341676514`, hex.EncodeToString(c.Response().Body()))
require.Equal(t, "application/cbor", string(c.Response().Header.Peek("content-type")))

// Test with ctype
err = c.CBOR(dummyStruct{ // map has no order
Name: "Grame",
Age: 20,
}, "application/problem+cbor")
require.NoError(t, err)
require.Equal(t, `a2644e616d65654772616d656341676514`, hex.EncodeToString(c.Response().Body()))
require.Equal(t, "application/problem+cbor", string(c.Response().Header.Peek("content-type")))

testEmpty := func(v any, r string) {
err := c.CBOR(v)
require.NoError(t, err)
require.Equal(t, r, hex.EncodeToString(c.Response().Body()))
}

testEmpty(nil, "f6")
testEmpty("", `60`)
testEmpty(0, "00")
testEmpty([]int{}, "80")

t.Run("custom cbor encoder", func(t *testing.T) {
t.Parallel()

app := New(Config{
CBOREncoder: func(_ any) ([]byte, error) {
return []byte(hex.EncodeToString([]byte("random"))), nil
},
})
c := app.AcquireCtx(&fasthttp.RequestCtx{})

err := c.CBOR(Map{ // map has no order
"Name": "Grame",
"Age": 20,
})
require.NoError(t, err)
require.Equal(t, `72616e646f6d`, string(c.Response().Body()))
require.Equal(t, "application/cbor", string(c.Response().Header.Peek("content-type")))
})
}
gaby marked this conversation as resolved.
Show resolved Hide resolved

// go test -run=^$ -bench=Benchmark_Ctx_CBOR -benchmem -count=4
func Benchmark_Ctx_CBOR(b *testing.B) {
app := New()
c := app.AcquireCtx(&fasthttp.RequestCtx{})

type SomeStruct struct {
Name string
Age uint8
}
data := SomeStruct{
Name: "Grame",
Age: 20,
}
var err error
b.ReportAllocs()
b.ResetTimer()
for n := 0; n < b.N; n++ {
err = c.CBOR(data)
}
require.NoError(b, err)
require.Equal(b, `a2644e616d65654772616d656341676514`, hex.EncodeToString(c.Response().Body()))
}
gaby marked this conversation as resolved.
Show resolved Hide resolved

// go test -run=^$ -bench=Benchmark_Ctx_JSON_Ctype -benchmem -count=4
func Benchmark_Ctx_JSON_Ctype(b *testing.B) {
app := New()
Expand Down
3 changes: 3 additions & 0 deletions docs/whats_new.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,8 @@ DRAFT section

- Cookie now allows Partitioned cookies for [CHIPS](https://developers.google.com/privacy-sandbox/3pcd/chips) support. CHIPS (Cookies Having Independent Partitioned State) is a feature that improves privacy by allowing cookies to be partitioned by top-level site, mitigating cross-site tracking.

- Introducing [CBOR](https://cbor.io/) binary encoding format for response body. CBOR is a binary data serialization format that is both compact and efficient, making it ideal for use in web applications.

### new methods

- AutoFormat -> ExpressJs like
Expand All @@ -208,6 +210,7 @@ DRAFT section
- SendString -> ExpressJs like
- String -> ExpressJs like
- ViewBind -> instead of Bind
- CBOR -> for CBOR encoding

### removed methods

Expand Down
4 changes: 3 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ go 1.22

require (
github.com/gofiber/schema v1.2.0
github.com/gofiber/utils/v2 v2.0.0-beta.6
github.com/gofiber/utils/v2 v2.0.0-beta.7
github.com/google/uuid v1.6.0
github.com/mattn/go-colorable v0.1.13
github.com/mattn/go-isatty v0.0.20
Expand All @@ -17,10 +17,12 @@ require (
require (
github.com/andybalholm/brotli v1.1.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fxamacker/cbor/v2 v2.7.0 // direct
github.com/klauspost/compress v1.17.9 // indirect
github.com/philhofer/fwd v1.1.3-0.20240612014219-fbbf4953d986 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/valyala/tcplisten v1.0.0 // indirect
github.com/x448/float16 v0.8.4 // indirect
golang.org/x/net v0.29.0 // indirect
golang.org/x/sys v0.25.0 // indirect
golang.org/x/text v0.18.0 // indirect
Expand Down
8 changes: 6 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
github.com/gofiber/schema v1.2.0 h1:j+ZRrNnUa/0ZuWrn/6kAtAufEr4jCJ+JuTURAMxNSZg=
github.com/gofiber/schema v1.2.0/go.mod h1:YYwj01w3hVfaNjhtJzaqetymL56VW642YS3qZPhuE6c=
github.com/gofiber/utils/v2 v2.0.0-beta.6 h1:ED62bOmpRXdgviPlfTmf0Q+AXzhaTUAFtdWjgx+XkYI=
github.com/gofiber/utils/v2 v2.0.0-beta.6/go.mod h1:3Kz8Px3jInKFvqxDzDeoSygwEOO+3uyubTmUa6PqY+0=
github.com/gofiber/utils/v2 v2.0.0-beta.7 h1:NnHFrRHvhrufPABdWajcKZejz9HnCWmT/asoxRsiEbQ=
github.com/gofiber/utils/v2 v2.0.0-beta.7/go.mod h1:J/M03s+HMdZdvhAeyh76xT72IfVqBzuz/OJkrMa7cwU=
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/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
Expand All @@ -29,6 +31,8 @@ github.com/valyala/fasthttp v1.56.0 h1:bEZdJev/6LCBlpdORfrLu/WOZXXxvrUQSiyniuaoW
github.com/valyala/fasthttp v1.56.0/go.mod h1:sReBt3XZVnudxuLOx4J/fMrJVorWRiWY2koQKgABiVI=
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
Expand Down
6 changes: 6 additions & 0 deletions middleware/cache/manager_msgp.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading