Skip to content

Commit b5b94ae

Browse files
authored
Templating header manipulation (#1200)
* Templating helper function to set headers * Removed my startup file * clean up test data * more cleanup
1 parent 99e862b commit b5b94ae

File tree

7 files changed

+107
-4
lines changed

7 files changed

+107
-4
lines changed

core/templating/template_helpers.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -498,6 +498,24 @@ func (t templateHelpers) setStatusCode(statusCode string, options *raymond.Optio
498498
return ""
499499
}
500500

501+
func (t templateHelpers) setHeader(headerName string, headerValue string, options *raymond.Options) string {
502+
if headerName == "" {
503+
log.Error("header name cannot be empty")
504+
return ""
505+
}
506+
internalVars := options.ValueFromAllCtx("InternalVars").(map[string]interface{})
507+
var headers map[string][]string
508+
if h, ok := internalVars["setHeaders"]; ok {
509+
headers = h.(map[string][]string)
510+
} else {
511+
headers = make(map[string][]string)
512+
}
513+
// Replace or add the header
514+
headers[headerName] = []string{headerValue}
515+
internalVars["setHeaders"] = headers
516+
return ""
517+
}
518+
501519
func (t templateHelpers) sum(numbers []string, format string) string {
502520
return sumNumbers(numbers, format)
503521
}

core/templating/template_helpers_test.go

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,24 @@ package templating
33
import (
44
"testing"
55
"time"
6-
76
. "github.com/onsi/gomega"
87
)
98

9+
10+
11+
// mockRaymondOptions is a minimal mock for raymond.Options for testing
12+
type mockRaymondOptions struct {
13+
internalVars map[string]interface{}
14+
}
15+
16+
func (m *mockRaymondOptions) ValueFromAllCtx(key string) interface{} {
17+
if key == "InternalVars" {
18+
return m.internalVars
19+
}
20+
return nil
21+
}
22+
23+
1024
func testNow() time.Time {
1125
parsedTime, _ := time.Parse("2006-01-02T15:04:05Z", "2018-01-01T00:00:00Z")
1226
return parsedTime

core/templating/templating.go

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ func NewEnrichedTemplator(journal *journal.Journal) *Templator {
105105
helperMethodMap["journal"] = t.parseJournalBasedOnIndex
106106
helperMethodMap["hasJournalKey"] = t.hasJournalKey
107107
helperMethodMap["setStatusCode"] = t.setStatusCode
108+
helperMethodMap["setHeader"] = t.setHeader
108109
helperMethodMap["sum"] = t.sum
109110
helperMethodMap["add"] = t.add
110111
helperMethodMap["subtract"] = t.subtract
@@ -146,11 +147,20 @@ func (t *Templator) RenderTemplate(tpl *raymond.Template, requestDetails *models
146147

147148
ctx := t.NewTemplatingData(requestDetails, literals, vars, state)
148149
result, err := tpl.Exec(ctx)
149-
if err == nil {
150-
statusCode, ok := ctx.InternalVars["statusCode"]
151-
if ok && response != nil {
150+
if err == nil && response != nil {
151+
// Set status code if present
152+
if statusCode, ok := ctx.InternalVars["statusCode"]; ok {
152153
response.Status = statusCode.(int)
153154
}
155+
// Set headers if present
156+
if setHeaders, ok := ctx.InternalVars["setHeaders"]; ok {
157+
if response.Headers == nil {
158+
response.Headers = make(map[string][]string)
159+
}
160+
for k, v := range setHeaders.(map[string][]string) {
161+
response.Headers[k] = v
162+
}
163+
}
154164
}
155165
return result, err
156166
}

core/templating/templating_test.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -917,6 +917,22 @@ func Test_ApplyTemplate_setStatusCode_should_handle_nil_response(t *testing.T) {
917917
Expect(template).To(Equal(""))
918918
}
919919

920+
func Test_ApplyTemplate_setHeader(t *testing.T) {
921+
RegisterTestingT(t)
922+
923+
templator := templating.NewTemplator()
924+
925+
template, err := templator.ParseTemplate(`{{ setHeader "X-Test-Header" "HeaderValue" }}`)
926+
Expect(err).To(BeNil())
927+
928+
response := &models.ResponseDetails{Headers: map[string][]string{}}
929+
result, err := templator.RenderTemplate(template, &models.RequestDetails{}, response, &models.Literals{}, &models.Variables{}, make(map[string]string))
930+
931+
Expect(err).To(BeNil())
932+
Expect(result).To(Equal(""))
933+
Expect(response.Headers).To(HaveKeyWithValue("X-Test-Header", []string{"HeaderValue"}))
934+
}
935+
920936
func toInterfaceSlice(arguments []string) []interface{} {
921937
argumentsArray := make([]interface{}, len(arguments))
922938

docs/pages/keyconcepts/templating/templating.rst

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -654,6 +654,51 @@ To learn about more advanced templating functionality, such as looping and condi
654654

655655
Global Literals and Variables
656656
-----------------------------
657+
Setting properties on the response
658+
----------------------------------
659+
660+
Hoverfly provides helper functions to set properties on the HTTP response directly from your templates. This allows you to dynamically control the status code and headers based on request data or logic in your template.
661+
662+
Setting the Status Code
663+
~~~~~~~~~~~~~~~~~~~~~~~
664+
You can set the HTTP status code of the response using the ``setStatusCode`` helper. This is useful for conditional logic, such as returning a 404 if a resource is not found, or a 200 if an operation succeeds.
665+
666+
.. code:: handlebars
667+
668+
{{ setStatusCode 404 }}
669+
670+
You can use this helper inside conditional blocks:
671+
672+
.. code:: handlebars
673+
674+
{{#equal (csvDeleteRows 'pets' 'category' 'cats' true) '0'}}
675+
{{ setStatusCode 404 }}
676+
{"Message":"Error no cats found"}
677+
{{else}}
678+
{{ setStatusCode 200 }}
679+
{"Message":"All cats deleted"}
680+
{{/equal}}
681+
682+
If you provide an invalid status code (e.g., outside the range 100-599), it will be ignored.
683+
684+
Setting Response Headers
685+
~~~~~~~~~~~~~~~~~~~~~~~
686+
You can set or override HTTP response headers using the ``setHeader`` helper. This is useful for adding custom headers, controlling caching, or setting content types dynamically.
687+
688+
.. code:: handlebars
689+
690+
{{ setHeader "X-Custom-Header" "HeaderValue" }}
691+
692+
You can use this helper multiple times to set different headers, or inside conditional blocks to set headers based on logic:
693+
694+
.. code:: handlebars
695+
696+
{{ setHeader "Content-Type" "application/json" }}
697+
{{ setHeader "X-Request-Id" (randomUuid) }}
698+
699+
If the header already exists, it will be overwritten with the new value.
700+
701+
Both helpers do not output anything to the template result; they only affect the response properties.
657702
You can define global literals and variables for templated response. This comes in handy when you
658703
have a lot of templated responses that share the same constant values or helper methods.
659704

functional-tests/hoverctl/.hoverfly/.gitkeep

Whitespace-only changes.

functional-tests/hoverctl/.hoverfly/cache/.gitkeep

Whitespace-only changes.

0 commit comments

Comments
 (0)