Skip to content

Commit

Permalink
Read Binary body with application/octet-stream Content-Type header
Browse files Browse the repository at this point in the history
  • Loading branch information
EwenQuim committed Apr 5, 2024
1 parent d228d23 commit 7197cba
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 4 deletions.
22 changes: 18 additions & 4 deletions ctx.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"fmt"
"html/template"
"io"
"io/fs"
"log/slog"
"net/http"
Expand Down Expand Up @@ -124,8 +125,10 @@ type ContextNoBody struct {
readOptions readOptions
}

var _ ctx[any] = ContextNoBody{} // Check that ContextNoBody implements Ctx.
var _ context.Context = ContextNoBody{} // Check that ContextNoBody implements context.Context.
var (
_ ctx[any] = ContextNoBody{} // Check that ContextNoBody implements Ctx.
_ context.Context = ContextNoBody{} // Check that ContextNoBody implements context.Context.
)

func (c ContextNoBody) Body() (any, error) {
slog.Warn("this method should not be called. It probably happened because you passed the context to another controller.")
Expand Down Expand Up @@ -423,9 +426,20 @@ func body[B any](c ContextNoBody) (B, error) {
case "application/x-www-form-urlencoded", "multipart/form-data":
body, err = readURLEncoded[B](c.Req, c.readOptions)
case "application/xml":
return readXML[B](c.Req.Context(), c.Req.Body, c.readOptions)
body, err = readXML[B](c.Req.Context(), c.Req.Body, c.readOptions)
case "application/x-yaml":
return readYAML[B](c.Req.Context(), c.Req.Body, c.readOptions)
body, err = readYAML[B](c.Req.Context(), c.Req.Body, c.readOptions)
case "application/octet-stream":
// Read c.Req Body to bytes
bytes, err := io.ReadAll(c.Req.Body)
if err != nil {
return body, err
}
respBytes, ok := any(bytes).(B)
if !ok {
return body, fmt.Errorf("could not convert bytes to %T. To read binary data from the request, use []byte as the body type", body)
}
body = respBytes
case "application/json":
fallthrough
default:
Expand Down
48 changes: 48 additions & 0 deletions ctx_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,23 @@ func TestContext_Body(t *testing.T) {
require.Equal(t, 30, body.Age)
})

t.Run("can read JSON body with Content-Type application/json", func(t *testing.T) {
// Create new Reader
a := strings.NewReader(`{"name":"John","age":30}`)

// Test an http request
w := httptest.NewRecorder()
r := httptest.NewRequest("GET", "http://example.com/foo", a)
r.Header.Add("Content-Type", "application/json")

c := NewContext[testStruct](w, r, readOptions{})

body, err := c.Body()
require.NoError(t, err)
require.Equal(t, "John", body.Name)
require.Equal(t, 30, body.Age)
})

t.Run("can read JSON body twice", func(t *testing.T) {
a := strings.NewReader(`{"name":"John","age":30}`)

Expand Down Expand Up @@ -251,6 +268,37 @@ func TestContext_Body(t *testing.T) {
require.Equal(t, 30, body.Age)
})

t.Run("can read bytes", func(t *testing.T) {
// Create new Reader with pure bytes from an image
a := bytes.NewReader([]byte(`image`))

// Test an http request
w := httptest.NewRecorder()
r := httptest.NewRequest("GET", "http://example.com/foo", a)
r.Header.Add("Content-Type", "application/octet-stream")

c := NewContext[[]byte](w, r, readOptions{})
body, err := c.Body()
require.NoError(t, err)
require.Equal(t, []byte(`image`), body)
})

t.Run("cannot read bytes if expected type is different than bytes", func(t *testing.T) {
// Create new Reader with pure bytes from an image
a := bytes.NewReader([]byte(`image`))

// Test an http request
w := httptest.NewRecorder()
r := httptest.NewRequest("GET", "http://example.com/foo", a)
r.Header.Add("Content-Type", "application/octet-stream")

c := NewContext[*struct{}](w, r, readOptions{})
body, err := c.Body()
require.Error(t, err)
require.ErrorContains(t, err, "use []byte as the body type")
require.Equal(t, (*struct{})(nil), body)
})

t.Run("can read XML body", func(t *testing.T) {
a := bytes.NewReader([]byte(`
<TestStruct>
Expand Down
17 changes: 17 additions & 0 deletions documentation/docs/guides/serialization.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,23 @@ Fuego automatically serializes and deserializes inputs and outputs with standard

<FlowChart selected="resp" />

## Deserialize binary data

If you just want to read the body of the request as a byte slice, you can use the `[]byte` receiver type.

Don't forget to set the request `Content-Type` header to `application/octet-stream`.

```go
fuego.Put(s, "/blob", func(c *fuego.ContextWithBody[[]byte]) (any, error) {
body, err := c.Body()
if err != nil {
return nil, err
}

return body, nil
})
```

## Custom serialization

But you can also use the `Serialize` and `Deserialize` functions to manually serialize and deserialize data.
Expand Down

0 comments on commit 7197cba

Please sign in to comment.