diff --git a/examples/full-app-gourmet/views/recipe.go b/examples/full-app-gourmet/views/recipe.go
index c4370b13..97d29665 100644
--- a/examples/full-app-gourmet/views/recipe.go
+++ b/examples/full-app-gourmet/views/recipe.go
@@ -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) {
diff --git a/multi_return.go b/multi_return.go
index afc9259d..38def6d1 100644
--- a/multi_return.go
+++ b/multi_return.go
@@ -4,6 +4,7 @@ import (
"context"
"encoding/json"
"encoding/xml"
+ "fmt"
"io"
"gopkg.in/yaml.v3"
@@ -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) {
@@ -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 {
@@ -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,
}
diff --git a/multi_return_test.go b/multi_return_test.go
index 157e1f7f..83240b43 100644
--- a/multi_return_test.go
+++ b/multi_return_test.go
@@ -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 {
@@ -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(`
` + entity.Name + `
`))
- return err
- },
- },
+ Data: entity,
+ Template: RenderString(`` + entity.Name + `
`),
}, 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(`` + entity.Name + `
`))
- return err
- },
- }), nil
+ RenderString(``+entity.Name+`
`),
+ ), nil
})
t.Run("requests HTML by default", func(t *testing.T) {
diff --git a/serialization.go b/serialization.go
index dacc8940..1880840b 100644
--- a/serialization.go
+++ b/serialization.go
@@ -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 {
@@ -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 {
@@ -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"
@@ -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 {
@@ -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)
@@ -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))
@@ -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"
@@ -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"
}
diff --git a/serialization_test.go b/serialization_test.go
index be9f21d7..e894d25c 100644
--- a/serialization_test.go
+++ b/serialization_test.go
@@ -2,6 +2,7 @@ package fuego
import (
"context"
+ "io"
"net/http/httptest"
"testing"
@@ -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)
+ })
+}
diff --git a/serve.go b/serve.go
index 9837f087..55dfac90 100644
--- a/serve.go
+++ b/serve.go
@@ -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))