Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 41 additions & 13 deletions render.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,15 @@ type HTMLOptions struct {
Layout string
// Funcs added to Options.Funcs.
Funcs template.FuncMap
// ContentType overrides Options.HTMLContentType for this call only. Charset is not appended.
ContentType string
}

// CallOptions is a struct for overriding rendering Options on a per-call basis.
type CallOptions struct {
// ContentType overrides the instance-level content type for this call only.
// Charset is not appended.
ContentType string
}

// Render is a service that provides functions for easily writing JSON, XML,
Expand Down Expand Up @@ -443,6 +452,8 @@ func (r *Render) prepareHTMLOptions(htmlOpt []HTMLOptions) HTMLOptions {
layout := r.opt.Layout
funcs := template.FuncMap{}

var contentType string

for _, tmp := range r.opt.Funcs {
for k, v := range tmp {
funcs[k] = v
Expand All @@ -458,11 +469,14 @@ func (r *Render) prepareHTMLOptions(htmlOpt []HTMLOptions) HTMLOptions {
for k, v := range opt.Funcs {
funcs[k] = v
}

contentType = opt.ContentType
}

return HTMLOptions{
Layout: layout,
Funcs: funcs,
Layout: layout,
Funcs: funcs,
ContentType: contentType,
}
}

Expand All @@ -476,10 +490,19 @@ func (r *Render) Render(w io.Writer, e Engine, data interface{}) error {
return err
}

// resolveContentType returns the override ContentType if one was provided, otherwise the default.
func resolveContentType(defaultCT string, opts []CallOptions) string {
if len(opts) > 0 && opts[0].ContentType != "" {
return opts[0].ContentType
}

return defaultCT
}

// Data writes out the raw bytes as binary data.
func (r *Render) Data(w io.Writer, status int, v []byte) error {
func (r *Render) Data(w io.Writer, status int, v []byte, opts ...CallOptions) error {
head := Head{
ContentType: r.opt.BinaryContentType,
ContentType: resolveContentType(r.opt.BinaryContentType, opts),
Status: status,
}

Expand Down Expand Up @@ -515,8 +538,13 @@ func (r *Render) HTML(w io.Writer, status int, name string, binding interface{},
}
}

ct := r.opt.HTMLContentType + r.compiledCharset
if opt.ContentType != "" {
ct = opt.ContentType
}

head := Head{
ContentType: r.opt.HTMLContentType + r.compiledCharset,
ContentType: ct,
Status: status,
}

Expand All @@ -531,9 +559,9 @@ func (r *Render) HTML(w io.Writer, status int, name string, binding interface{},
}

// JSON marshals the given interface object and writes the JSON response.
func (r *Render) JSON(w io.Writer, status int, v interface{}) error {
func (r *Render) JSON(w io.Writer, status int, v interface{}, opts ...CallOptions) error {
head := Head{
ContentType: r.opt.JSONContentType + r.compiledCharset,
ContentType: resolveContentType(r.opt.JSONContentType+r.compiledCharset, opts),
Status: status,
}

Expand All @@ -550,9 +578,9 @@ func (r *Render) JSON(w io.Writer, status int, v interface{}) error {
}

// JSONP marshals the given interface object and writes the JSON response.
func (r *Render) JSONP(w io.Writer, status int, callback string, v interface{}) error {
func (r *Render) JSONP(w io.Writer, status int, callback string, v interface{}, opts ...CallOptions) error {
head := Head{
ContentType: r.opt.JSONPContentType + r.compiledCharset,
ContentType: resolveContentType(r.opt.JSONPContentType+r.compiledCharset, opts),
Status: status,
}

Expand All @@ -566,9 +594,9 @@ func (r *Render) JSONP(w io.Writer, status int, callback string, v interface{})
}

// Text writes out a string as plain text.
func (r *Render) Text(w io.Writer, status int, v string) error {
func (r *Render) Text(w io.Writer, status int, v string, opts ...CallOptions) error {
head := Head{
ContentType: r.opt.TextContentType + r.compiledCharset,
ContentType: resolveContentType(r.opt.TextContentType+r.compiledCharset, opts),
Status: status,
}

Expand All @@ -580,9 +608,9 @@ func (r *Render) Text(w io.Writer, status int, v string) error {
}

// XML marshals the given interface object and writes the XML response.
func (r *Render) XML(w io.Writer, status int, v interface{}) error {
func (r *Render) XML(w io.Writer, status int, v interface{}, opts ...CallOptions) error {
head := Head{
ContentType: r.opt.XMLContentType + r.compiledCharset,
ContentType: resolveContentType(r.opt.XMLContentType+r.compiledCharset, opts),
Status: status,
}

Expand Down
21 changes: 21 additions & 0 deletions render_data_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,24 @@ func TestDataCustomContentType(t *testing.T) {
expect(t, res.Header().Get(ContentType), "image/png")
expect(t, res.Body.String(), "..png data..")
}

func TestDataPerCallContentType(t *testing.T) {
render := New()

var err error

h := http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
err = render.Data(w, http.StatusOK, []byte("..jpeg data.."), CallOptions{
ContentType: "image/jpeg",
})
})

res := httptest.NewRecorder()
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, "/foo", nil)
h.ServeHTTP(res, req)

expectNil(t, err)
expect(t, res.Code, http.StatusOK)
expect(t, res.Header().Get(ContentType), "image/jpeg")
expect(t, res.Body.String(), "..jpeg data..")
}
23 changes: 23 additions & 0 deletions render_html_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -501,3 +501,26 @@ func TestHTMLTemplateOptionError(t *testing.T) {
expectNotNil(t, err)
expect(t, strings.Contains(err.Error(), "map has no entry for key"), true)
}

func TestHTMLPerCallContentType(t *testing.T) {
render := New(Options{
Directory: "testdata/basic",
})

var err error

h := http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
err = render.HTML(w, http.StatusOK, "hello", "gophers", HTMLOptions{
ContentType: "application/xhtml+xml",
})
})

res := httptest.NewRecorder()
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, "/foo", nil)
h.ServeHTTP(res, req)

expectNil(t, err)
expect(t, res.Code, http.StatusOK)
expect(t, res.Header().Get(ContentType), "application/xhtml+xml")
expect(t, res.Body.String(), "<h1>Hello gophers</h1>\n")
}
21 changes: 21 additions & 0 deletions render_json_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -412,3 +412,24 @@ func TestJSONEncoderStream(t *testing.T) {
expect(t, res.Header().Get(ContentType), ContentJSON+"; charset=UTF-8")
expect(t, res.Body.String(), TestEncoder{}.String())
}

func TestJSONPerCallContentType(t *testing.T) {
render := New()

var err error

h := http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
err = render.JSON(w, http.StatusOK, Greeting{"hello", "world"}, CallOptions{
ContentType: "application/vnd.api+json",
})
})

res := httptest.NewRecorder()
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, "/foo", nil)
h.ServeHTTP(res, req)

expectNil(t, err)
expect(t, res.Code, http.StatusOK)
expect(t, res.Header().Get(ContentType), "application/vnd.api+json")
expect(t, res.Body.String(), "{\"one\":\"hello\",\"two\":\"world\"}")
}
21 changes: 21 additions & 0 deletions render_jsonp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,3 +110,24 @@ func TestJSONPDisabledCharset(t *testing.T) {
expect(t, res.Header().Get(ContentType), ContentJSONP)
expect(t, res.Body.String(), "helloCallback({\"one\":\"hello\",\"two\":\"world\"});")
}

func TestJSONPPerCallContentType(t *testing.T) {
render := New()

var err error

h := http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
err = render.JSONP(w, http.StatusOK, "helloCallback", GreetingP{"hello", "world"}, CallOptions{
ContentType: "application/vnd.api+json",
})
})

res := httptest.NewRecorder()
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, "/foo", nil)
h.ServeHTTP(res, req)

expectNil(t, err)
expect(t, res.Code, http.StatusOK)
expect(t, res.Header().Get(ContentType), "application/vnd.api+json")
expect(t, res.Body.String(), "helloCallback({\"one\":\"hello\",\"two\":\"world\"});")
}
21 changes: 21 additions & 0 deletions render_text_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,3 +111,24 @@ func TestTextDisabledCharset(t *testing.T) {
expect(t, res.Header().Get(ContentType), ContentText)
expect(t, res.Body.String(), "Hello Text!")
}

func TestTextPerCallContentType(t *testing.T) {
render := New()

var err error

h := http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
err = render.Text(w, http.StatusOK, "Hello Text!", CallOptions{
ContentType: "text/css",
})
})

res := httptest.NewRecorder()
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, "/foo", nil)
h.ServeHTTP(res, req)

expectNil(t, err)
expect(t, res.Code, http.StatusOK)
expect(t, res.Header().Get(ContentType), "text/css")
expect(t, res.Body.String(), "Hello Text!")
}
21 changes: 21 additions & 0 deletions render_xml_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,3 +137,24 @@ func TestXMLDisabledCharset(t *testing.T) {
expect(t, res.Header().Get(ContentType), ContentXML)
expect(t, res.Body.String(), "<greeting one=\"hello\" two=\"world\"></greeting>")
}

func TestXMLPerCallContentType(t *testing.T) {
render := New()

var err error

h := http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
err = render.XML(w, http.StatusOK, GreetingXML{One: "hello", Two: "world"}, CallOptions{
ContentType: "application/vnd.api+xml",
})
})

res := httptest.NewRecorder()
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, "/foo", nil)
h.ServeHTTP(res, req)

expectNil(t, err)
expect(t, res.Code, http.StatusOK)
expect(t, res.Header().Get(ContentType), "application/vnd.api+xml")
expect(t, res.Body.String(), "<greeting one=\"hello\" two=\"world\"></greeting>")
}
Loading