Skip to content

Commit 2a32ad3

Browse files
committed
feat: add per-call ContentType override via CallOptions
1 parent 5f7ed70 commit 2a32ad3

7 files changed

Lines changed: 167 additions & 13 deletions

‎render.go‎

Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,15 @@ type HTMLOptions struct {
134134
Layout string
135135
// Funcs added to Options.Funcs.
136136
Funcs template.FuncMap
137+
// ContentType overrides Options.HTMLContentType for this call only. Charset is not appended.
138+
ContentType string
139+
}
140+
141+
// CallOptions is a struct for overriding rendering Options on a per-call basis.
142+
type CallOptions struct {
143+
// ContentType overrides the instance-level content type for this call only.
144+
// Charset is not appended.
145+
ContentType string
137146
}
138147

139148
// Render is a service that provides functions for easily writing JSON, XML,
@@ -442,6 +451,7 @@ func (r *Render) layoutFuncs(templates *template.Template, name string, binding
442451
func (r *Render) prepareHTMLOptions(htmlOpt []HTMLOptions) HTMLOptions {
443452
layout := r.opt.Layout
444453
funcs := template.FuncMap{}
454+
var contentType string
445455

446456
for _, tmp := range r.opt.Funcs {
447457
for k, v := range tmp {
@@ -458,11 +468,14 @@ func (r *Render) prepareHTMLOptions(htmlOpt []HTMLOptions) HTMLOptions {
458468
for k, v := range opt.Funcs {
459469
funcs[k] = v
460470
}
471+
472+
contentType = opt.ContentType
461473
}
462474

463475
return HTMLOptions{
464-
Layout: layout,
465-
Funcs: funcs,
476+
Layout: layout,
477+
Funcs: funcs,
478+
ContentType: contentType,
466479
}
467480
}
468481

@@ -476,10 +489,18 @@ func (r *Render) Render(w io.Writer, e Engine, data interface{}) error {
476489
return err
477490
}
478491

492+
// resolveContentType returns the override ContentType if one was provided, otherwise the default.
493+
func resolveContentType(defaultCT string, opts []CallOptions) string {
494+
if len(opts) > 0 && opts[0].ContentType != "" {
495+
return opts[0].ContentType
496+
}
497+
return defaultCT
498+
}
499+
479500
// Data writes out the raw bytes as binary data.
480-
func (r *Render) Data(w io.Writer, status int, v []byte) error {
501+
func (r *Render) Data(w io.Writer, status int, v []byte, opts ...CallOptions) error {
481502
head := Head{
482-
ContentType: r.opt.BinaryContentType,
503+
ContentType: resolveContentType(r.opt.BinaryContentType, opts),
483504
Status: status,
484505
}
485506

@@ -515,8 +536,13 @@ func (r *Render) HTML(w io.Writer, status int, name string, binding interface{},
515536
}
516537
}
517538

539+
ct := r.opt.HTMLContentType + r.compiledCharset
540+
if opt.ContentType != "" {
541+
ct = opt.ContentType
542+
}
543+
518544
head := Head{
519-
ContentType: r.opt.HTMLContentType + r.compiledCharset,
545+
ContentType: ct,
520546
Status: status,
521547
}
522548

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

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

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

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

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

568594
// Text writes out a string as plain text.
569-
func (r *Render) Text(w io.Writer, status int, v string) error {
595+
func (r *Render) Text(w io.Writer, status int, v string, opts ...CallOptions) error {
570596
head := Head{
571-
ContentType: r.opt.TextContentType + r.compiledCharset,
597+
ContentType: resolveContentType(r.opt.TextContentType+r.compiledCharset, opts),
572598
Status: status,
573599
}
574600

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

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

‎render_data_test.go‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,3 +69,24 @@ func TestDataCustomContentType(t *testing.T) {
6969
expect(t, res.Header().Get(ContentType), "image/png")
7070
expect(t, res.Body.String(), "..png data..")
7171
}
72+
73+
func TestDataPerCallContentType(t *testing.T) {
74+
render := New()
75+
76+
var err error
77+
78+
h := http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
79+
err = render.Data(w, http.StatusOK, []byte("..jpeg data.."), CallOptions{
80+
ContentType: "image/jpeg",
81+
})
82+
})
83+
84+
res := httptest.NewRecorder()
85+
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, "/foo", nil)
86+
h.ServeHTTP(res, req)
87+
88+
expectNil(t, err)
89+
expect(t, res.Code, http.StatusOK)
90+
expect(t, res.Header().Get(ContentType), "image/jpeg")
91+
expect(t, res.Body.String(), "..jpeg data..")
92+
}

‎render_html_test.go‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -501,3 +501,26 @@ func TestHTMLTemplateOptionError(t *testing.T) {
501501
expectNotNil(t, err)
502502
expect(t, strings.Contains(err.Error(), "map has no entry for key"), true)
503503
}
504+
505+
func TestHTMLPerCallContentType(t *testing.T) {
506+
render := New(Options{
507+
Directory: "testdata/basic",
508+
})
509+
510+
var err error
511+
512+
h := http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
513+
err = render.HTML(w, http.StatusOK, "hello", "gophers", HTMLOptions{
514+
ContentType: "application/xhtml+xml",
515+
})
516+
})
517+
518+
res := httptest.NewRecorder()
519+
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, "/foo", nil)
520+
h.ServeHTTP(res, req)
521+
522+
expectNil(t, err)
523+
expect(t, res.Code, http.StatusOK)
524+
expect(t, res.Header().Get(ContentType), "application/xhtml+xml")
525+
expect(t, res.Body.String(), "<h1>Hello gophers</h1>\n")
526+
}

‎render_json_test.go‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -412,3 +412,24 @@ func TestJSONEncoderStream(t *testing.T) {
412412
expect(t, res.Header().Get(ContentType), ContentJSON+"; charset=UTF-8")
413413
expect(t, res.Body.String(), TestEncoder{}.String())
414414
}
415+
416+
func TestJSONPerCallContentType(t *testing.T) {
417+
render := New()
418+
419+
var err error
420+
421+
h := http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
422+
err = render.JSON(w, http.StatusOK, Greeting{"hello", "world"}, CallOptions{
423+
ContentType: "application/vnd.api+json",
424+
})
425+
})
426+
427+
res := httptest.NewRecorder()
428+
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, "/foo", nil)
429+
h.ServeHTTP(res, req)
430+
431+
expectNil(t, err)
432+
expect(t, res.Code, http.StatusOK)
433+
expect(t, res.Header().Get(ContentType), "application/vnd.api+json")
434+
expect(t, res.Body.String(), "{\"one\":\"hello\",\"two\":\"world\"}")
435+
}

‎render_jsonp_test.go‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,3 +110,24 @@ func TestJSONPDisabledCharset(t *testing.T) {
110110
expect(t, res.Header().Get(ContentType), ContentJSONP)
111111
expect(t, res.Body.String(), "helloCallback({\"one\":\"hello\",\"two\":\"world\"});")
112112
}
113+
114+
func TestJSONPPerCallContentType(t *testing.T) {
115+
render := New()
116+
117+
var err error
118+
119+
h := http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
120+
err = render.JSONP(w, http.StatusOK, "helloCallback", GreetingP{"hello", "world"}, CallOptions{
121+
ContentType: "application/vnd.api+json",
122+
})
123+
})
124+
125+
res := httptest.NewRecorder()
126+
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, "/foo", nil)
127+
h.ServeHTTP(res, req)
128+
129+
expectNil(t, err)
130+
expect(t, res.Code, http.StatusOK)
131+
expect(t, res.Header().Get(ContentType), "application/vnd.api+json")
132+
expect(t, res.Body.String(), "helloCallback({\"one\":\"hello\",\"two\":\"world\"});")
133+
}

‎render_text_test.go‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,3 +111,24 @@ func TestTextDisabledCharset(t *testing.T) {
111111
expect(t, res.Header().Get(ContentType), ContentText)
112112
expect(t, res.Body.String(), "Hello Text!")
113113
}
114+
115+
func TestTextPerCallContentType(t *testing.T) {
116+
render := New()
117+
118+
var err error
119+
120+
h := http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
121+
err = render.Text(w, http.StatusOK, "Hello Text!", CallOptions{
122+
ContentType: "text/css",
123+
})
124+
})
125+
126+
res := httptest.NewRecorder()
127+
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, "/foo", nil)
128+
h.ServeHTTP(res, req)
129+
130+
expectNil(t, err)
131+
expect(t, res.Code, http.StatusOK)
132+
expect(t, res.Header().Get(ContentType), "text/css")
133+
expect(t, res.Body.String(), "Hello Text!")
134+
}

‎render_xml_test.go‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,3 +137,24 @@ func TestXMLDisabledCharset(t *testing.T) {
137137
expect(t, res.Header().Get(ContentType), ContentXML)
138138
expect(t, res.Body.String(), "<greeting one=\"hello\" two=\"world\"></greeting>")
139139
}
140+
141+
func TestXMLPerCallContentType(t *testing.T) {
142+
render := New()
143+
144+
var err error
145+
146+
h := http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
147+
err = render.XML(w, http.StatusOK, GreetingXML{One: "hello", Two: "world"}, CallOptions{
148+
ContentType: "application/vnd.api+xml",
149+
})
150+
})
151+
152+
res := httptest.NewRecorder()
153+
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, "/foo", nil)
154+
h.ServeHTTP(res, req)
155+
156+
expectNil(t, err)
157+
expect(t, res.Code, http.StatusOK)
158+
expect(t, res.Header().Get(ContentType), "application/vnd.api+xml")
159+
expect(t, res.Body.String(), "<greeting one=\"hello\" two=\"world\"></greeting>")
160+
}

0 commit comments

Comments
 (0)