Skip to content

Commit

Permalink
Multi-return
Browse files Browse the repository at this point in the history
  • Loading branch information
EwenQuim committed Jul 9, 2024
1 parent d527949 commit 97643be
Show file tree
Hide file tree
Showing 6 changed files with 92 additions and 33 deletions.
8 changes: 4 additions & 4 deletions examples/full-app-gourmet/views/recipe.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,12 +110,12 @@ func (rs Ressource) showRecipes(c fuego.ContextNoBody) (*fuego.DataOrTemplate[[]
return nil, err
}

return &fuego.DataOrTemplate[[]store.Recipe]{
Data: recipes,
Template: templa.SearchPage(templa.SearchProps{
return fuego.DataOrHTML(
recipes,
templa.SearchPage(templa.SearchProps{
Recipes: recipes,
}),
}, nil
), nil
}

func (rs Ressource) relatedRecipes(c fuego.ContextNoBody) (fuego.Templ, error) {
Expand Down
23 changes: 14 additions & 9 deletions multi_return.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"encoding/json"
"encoding/xml"
"fmt"
"io"

"gopkg.in/yaml.v3"
Expand All @@ -17,10 +18,11 @@ type DataOrTemplate[T any] struct {
}

var (
_ CtxRenderer = DataOrTemplate[any]{} // Can render HTML
_ json.Marshaler = DataOrTemplate[any]{} // Can render JSON
_ xml.Marshaler = DataOrTemplate[any]{} // Can render XML
_ yaml.Marshaler = DataOrTemplate[any]{} // Can render YAML
_ CtxRenderer = DataOrTemplate[any]{} // Can render HTML (template)
_ json.Marshaler = DataOrTemplate[any]{} // Can render JSON (data)
_ xml.Marshaler = DataOrTemplate[any]{} // Can render XML (data)
_ yaml.Marshaler = DataOrTemplate[any]{} // Can render YAML (data)
_ fmt.Stringer = DataOrTemplate[any]{} // Can render string (data)
)

func (m DataOrTemplate[T]) MarshalJSON() ([]byte, error) {
Expand All @@ -32,9 +34,11 @@ func (m DataOrTemplate[T]) MarshalXML(e *xml.Encoder, _ xml.StartElement) error
}

func (m DataOrTemplate[T]) MarshalYAML() (interface{}, error) {
encoder := yaml.NewEncoder(io.Discard)
err := encoder.Encode(m.Data)
return nil, err
return m.Data, nil
}

func (m DataOrTemplate[T]) String() string {
return fmt.Sprintf("%v", m.Data)
}

func (m DataOrTemplate[T]) Render(c context.Context, w io.Writer) error {
Expand All @@ -48,8 +52,9 @@ func (m DataOrTemplate[T]) Render(c context.Context, w io.Writer) error {
}
}

func DataOrHTML[T any](data T, template any) DataOrTemplate[T] {
return DataOrTemplate[T]{
// Helper function to create a DataOrTemplate return item without specifying the type.
func DataOrHTML[T any](data T, template any) *DataOrTemplate[T] {
return &DataOrTemplate[T]{
Data: data,
Template: template,
}
Expand Down
28 changes: 14 additions & 14 deletions multi_return_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,15 @@ type MockCtxRenderer struct {
RenderFunc func(context.Context, io.Writer) error
}

func RenderString(s string) MockCtxRenderer {
return MockCtxRenderer{
RenderFunc: func(c context.Context, w io.Writer) error {
_, err := w.Write([]byte(s))
return err
},
}
}

var _ CtxRenderer = MockCtxRenderer{}

func (m MockCtxRenderer) Render(c context.Context, w io.Writer) error {
Expand All @@ -34,27 +43,18 @@ func TestMultiReturn(t *testing.T) {
entity := MyType{Name: "Ewen"}

return DataOrTemplate[MyType]{
Data: entity,
Template: MockCtxRenderer{
RenderFunc: func(c context.Context, w io.Writer) error {
_, err := w.Write([]byte(`<div>` + entity.Name + `</div>`))
return err
},
},
Data: entity,
Template: RenderString(`<div>` + entity.Name + `</div>`),
}, nil
})

Get(s, "/other", func(c ContextNoBody) (DataOrTemplate[MyType], error) {
Get(s, "/other", func(c ContextNoBody) (*DataOrTemplate[MyType], error) {
entity := MyType{Name: "Ewen"}

return DataOrHTML(
entity,
MockCtxRenderer{
RenderFunc: func(c context.Context, w io.Writer) error {
_, err := w.Write([]byte(`<div>` + entity.Name + `</div>`))
return err
},
}), nil
RenderString(`<div>`+entity.Name+`</div>`),
), nil
})

t.Run("requests HTML by default", func(t *testing.T) {
Expand Down
38 changes: 32 additions & 6 deletions serialization.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@ func Send(w http.ResponseWriter, text string) {
}

// SendYAML sends a YAML response.
func SendYAML(w http.ResponseWriter, ans any) {
// Declared as a variable to be able to override it for clients that need to customize serialization.
var SendYAML = func(w http.ResponseWriter, ans any) {
w.Header().Set("Content-Type", "application/x-yaml")
err := yaml.NewEncoder(w).Encode(ans)
if err != nil {
Expand All @@ -92,7 +93,8 @@ func SendYAML(w http.ResponseWriter, ans any) {
}

// SendJSON sends a JSON response.
func SendJSON(w http.ResponseWriter, ans any) {
// Declared as a variable to be able to override it for clients that need to customize serialization.
var SendJSON = func(w http.ResponseWriter, ans any) {
w.Header().Set("Content-Type", "application/json")
err := json.NewEncoder(w).Encode(ans)
if err != nil {
Expand All @@ -103,7 +105,9 @@ func SendJSON(w http.ResponseWriter, ans any) {
}
}

func SendError(w http.ResponseWriter, r *http.Request, err error) {
// SendError sends an error.
// Declared as a variable to be able to override it for clients that need to customize serialization.
var SendError = func(w http.ResponseWriter, r *http.Request, err error) {
accept := parseAcceptHeader(r.Header.Get("Accept"), nil)
if accept == "" {
accept = "application/json"
Expand Down Expand Up @@ -144,7 +148,8 @@ func SendJSONError(w http.ResponseWriter, err error) {
}

// SendXML sends a XML response.
func SendXML(w http.ResponseWriter, ans any) {
// Declared as a variable to be able to override it for clients that need to customize serialization.
var SendXML = func(w http.ResponseWriter, ans any) {
w.Header().Set("Content-Type", "application/xml")
err := xml.NewEncoder(w).Encode(ans)
if err != nil {
Expand Down Expand Up @@ -181,7 +186,8 @@ func SendHTMLError(ctx context.Context, w http.ResponseWriter, err error) error
}

// SendHTML sends a HTML response.
func SendHTML(ctx context.Context, w http.ResponseWriter, ans any) error {
// Declared as a variable to be able to override it for clients that need to customize serialization.
var SendHTML = func(ctx context.Context, w http.ResponseWriter, ans any) error {
w.Header().Set("Content-Type", "text/html; charset=utf-8")

ctxRenderer, ok := any(ans).(CtxRenderer)
Expand Down Expand Up @@ -215,7 +221,12 @@ func SendText(w http.ResponseWriter, ans any) error {
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
stringToWrite, ok := any(ans).(string)
if !ok {
stringToWrite = *any(ans).(*string)
stringToWritePtr, okPtr := any(ans).(*string)
if okPtr {
stringToWrite = *stringToWritePtr
} else {
stringToWrite = fmt.Sprintf("%v", ans)
}
}
_, err = w.Write([]byte(stringToWrite))

Expand All @@ -228,6 +239,11 @@ func InferAcceptHeaderFromType(ans any) string {
return "text/plain"
}

_, ok = any(ans).(*string)
if ok {
return "text/plain"
}

_, ok = any(ans).(HTML)
if ok {
return "text/html"
Expand All @@ -238,10 +254,20 @@ func InferAcceptHeaderFromType(ans any) string {
return "text/html"
}

_, ok = any(&ans).(*CtxRenderer)
if ok {
return "text/html"
}

_, ok = any(ans).(Renderer)
if ok {
return "text/html"
}

_, ok = any(&ans).(*Renderer)
if ok {
return "text/html"
}

return "application/json"
}
27 changes: 27 additions & 0 deletions serialization_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package fuego

import (
"context"
"io"
"net/http/httptest"
"testing"

Expand Down Expand Up @@ -239,3 +240,29 @@ func TestSend(t *testing.T) {

require.Equal(t, "Hello World", w.Body.String())
}

type templateMock struct{}

func (t templateMock) Render(w io.Writer) error {
return nil
}

var _ Renderer = templateMock{}

func TestInferAcceptHeaderFromType(t *testing.T) {
t.Run("can infer json", func(t *testing.T) {
accept := InferAcceptHeaderFromType(response{})
require.Equal(t, "application/json", accept)
})

t.Run("can infer that type is a template (implements Renderer)", func(t *testing.T) {
accept := InferAcceptHeaderFromType(templateMock{})
require.Equal(t, "text/html", accept)
})

t.Run("can infer that type is a template (implements CtxRenderer)", func(t *testing.T) {

accept := InferAcceptHeaderFromType(MockCtxRenderer{})
require.Equal(t, "text/html", accept)
})
}
1 change: 1 addition & 0 deletions serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ func initContext[Contextable ctx[Body], Body any](baseContext ContextNoBody) Con

// HTTPHandler converts a Fuego controller into a http.HandlerFunc.
func HTTPHandler[ReturnType, Body any, Contextable ctx[Body]](s *Server, controller func(c Contextable) (ReturnType, error)) http.HandlerFunc {
// Just a check, not used at request time
baseContext := *new(Contextable)
if reflect.TypeOf(baseContext) == nil {
slog.Info(fmt.Sprintf("context is nil: %v %T", baseContext, baseContext))
Expand Down

0 comments on commit 97643be

Please sign in to comment.