Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Avoid copying the journal data into context data for template rendering #1148

Merged
merged 1 commit into from
Aug 28, 2024
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
5 changes: 3 additions & 2 deletions core/hoverfly.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,15 +53,16 @@ func NewHoverfly() *Hoverfly {

authBackend := backends.NewCacheBasedAuthBackend(cache.NewInMemoryCache(), cache.NewInMemoryCache())

newJournal := journal.NewJournal()
hoverfly := &Hoverfly{
Simulation: models.NewSimulation(),
Authentication: authBackend,
Counter: metrics.NewModeCounter([]string{modes.Simulate, modes.Synthesize, modes.Modify, modes.Capture, modes.Spy, modes.Diff}),
StoreLogsHook: NewStoreLogsHook(),
Journal: journal.NewJournal(),
Journal: newJournal,
Cfg: InitSettings(),
state: state.NewState(),
templator: templating.NewTemplator(),
templator: templating.NewEnrichedTemplator(newJournal),
responsesDiff: make(map[v2.SimpleRequestDefinitionView][]v2.DiffReport),
PostServeActionDetails: action.NewPostServeActionDetails(),
}
Expand Down
6 changes: 3 additions & 3 deletions core/hoverfly_funcs.go
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ func (hf *Hoverfly) applyTransitionsStateTemplating(requestDetails *models.Reque
state := make(map[string]string)

for k, v := range stateTemplates {
state[k], err = hf.templator.RenderTemplate(v, requestDetails, response, hf.Simulation.Literals, hf.Simulation.Vars, hf.state.State, hf.Journal)
state[k], err = hf.templator.RenderTemplate(v, requestDetails, response, hf.Simulation.Literals, hf.Simulation.Vars, hf.state.State)
if err != nil {
return nil, err
}
Expand All @@ -245,7 +245,7 @@ func (hf *Hoverfly) applyBodyTemplating(requestDetails *models.RequestDetails, r
}
}

return hf.templator.RenderTemplate(template, requestDetails, response, hf.Simulation.Literals, hf.Simulation.Vars, hf.state.State, hf.Journal)
return hf.templator.RenderTemplate(template, requestDetails, response, hf.Simulation.Literals, hf.Simulation.Vars, hf.state.State)
}

func (hf *Hoverfly) applyHeadersTemplating(requestDetails *models.RequestDetails, response *models.ResponseDetails, cachedResponse *models.CachedResponse) (map[string][]string, error) {
Expand Down Expand Up @@ -280,7 +280,7 @@ func (hf *Hoverfly) applyHeadersTemplating(requestDetails *models.RequestDetails
for k, v := range headersTemplates {
header = make([]string, len(v))
for i, h := range v {
header[i], err = hf.templator.RenderTemplate(h, requestDetails, response, hf.Simulation.Literals, hf.Simulation.Vars, hf.state.State, hf.Journal)
header[i], err = hf.templator.RenderTemplate(h, requestDetails, response, hf.Simulation.Literals, hf.Simulation.Vars, hf.state.State)

if err != nil {
return nil, err
Expand Down
26 changes: 13 additions & 13 deletions core/templating/template_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ type templateHelpers struct {
now func() time.Time
fakerSource *gofakeit.Faker
TemplateDataSource *TemplateDataSource
journal *journal.Journal
}

func (t templateHelpers) nowHelper(offset string, format string) string {
Expand Down Expand Up @@ -466,8 +467,7 @@ func (t templateHelpers) csvSqlCommand(commandString string) []RowMap {
}

func (t templateHelpers) parseJournalBasedOnIndex(indexName, keyValue, dataSource, queryType, lookupQuery string, options *raymond.Options) interface{} {
journalDetails := options.Value("Journal").(Journal)
if journalEntry, err := getIndexEntry(journalDetails, indexName, keyValue); err == nil {
if journalEntry, err := getIndexEntry(t.journal, indexName, keyValue); err == nil {
if body := getBodyDataToParse(dataSource, journalEntry); body != "" {
data := util.FetchFromRequestBody(queryType, lookupQuery, body)
if _, ok := data.(error); ok {
Expand All @@ -481,9 +481,9 @@ func (t templateHelpers) parseJournalBasedOnIndex(indexName, keyValue, dataSourc
return getEvaluationString("journal", options)
}

func (t templateHelpers) hasJournalKey(indexName, keyValue string, options *raymond.Options) bool {
journalDetails := options.Value("Journal").(Journal)
journalEntry, _ := getIndexEntry(journalDetails, indexName, keyValue)
func (t templateHelpers) hasJournalKey(indexName, keyValue string) bool {

journalEntry, _ := getIndexEntry(t.journal, indexName, keyValue)

return journalEntry != nil
}
Expand Down Expand Up @@ -615,25 +615,25 @@ func formatNumber(number float64, format string) string {
return fmt.Sprintf("%."+strconv.Itoa(decimalPlaces)+"f", rounded)
}

func getIndexEntry(journalIndexDetails Journal, indexName, indexValue string) (*JournalEntry, error) {
func getIndexEntry(journal *journal.Journal, indexName, indexValue string) (*journal.JournalEntry, error) {

for _, index := range journalIndexDetails.indexes {
if index.name == indexName {
if journalEntry, exists := index.entries[indexValue]; exists {
return &journalEntry, nil
for _, index := range journal.Indexes {
if index.Name == indexName {
if journalEntry, exists := index.Entries[indexValue]; exists {
return journalEntry, nil
}
}
}
return nil, fmt.Errorf("no entry found for index %s", indexName)
}

func getBodyDataToParse(source string, journalEntry *JournalEntry) string {
func getBodyDataToParse(source string, journalEntry *journal.JournalEntry) string {

if strings.EqualFold(source, "request") {
return journalEntry.requestBody
return journalEntry.Request.Body
}
if strings.EqualFold(source, "response") {
return journalEntry.responseBody
return journalEntry.Response.Body
}
return ""
}
Expand Down
52 changes: 8 additions & 44 deletions core/templating/templating.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ type TemplatingData struct {
CurrentDateTime func(string, string, string) string
Literals map[string]interface{}
Vars map[string]interface{}
Journal Journal
Kvs map[string]interface{}
InternalVars map[string]interface{} // data store used internally by templating helpers
}
Expand All @@ -42,19 +41,6 @@ type Request struct {
Host string
}

type JournalEntry struct {
requestBody string
responseBody string
}

type Journal struct {
indexes []JournalIndex
}

type JournalIndex struct {
name string
entries map[string]JournalEntry
}

type Templator struct {
SupportedMethodMap map[string]interface{}
Expand All @@ -65,13 +51,18 @@ var helpersRegistered = false
var helperMethodMap = make(map[string]interface{})

func NewTemplator() *Templator {
return NewEnrichedTemplator(journal.NewJournal())
}

func NewEnrichedTemplator(journal *journal.Journal) *Templator {

templateDataSource := NewTemplateDataSource()

t := templateHelpers{
now: time.Now,
fakerSource: gofakeit.New(0),
TemplateDataSource: templateDataSource,
journal: journal,
}

helperMethodMap["now"] = t.nowHelper
Expand Down Expand Up @@ -148,12 +139,12 @@ func (*Templator) ParseTemplate(responseBody string) (*raymond.Template, error)
return raymond.Parse(responseBody)
}

func (t *Templator) RenderTemplate(tpl *raymond.Template, requestDetails *models.RequestDetails, response *models.ResponseDetails, literals *models.Literals, vars *models.Variables, state map[string]string, journal *journal.Journal) (string, error) {
func (t *Templator) RenderTemplate(tpl *raymond.Template, requestDetails *models.RequestDetails, response *models.ResponseDetails, literals *models.Literals, vars *models.Variables, state map[string]string) (string, error) {
if tpl == nil {
return "", fmt.Errorf("template cannot be nil")
}

ctx := t.NewTemplatingData(requestDetails, response, literals, vars, state, journal)
ctx := t.NewTemplatingData(requestDetails, literals, vars, state)
result, err := tpl.Exec(ctx)
if err == nil {
statusCode, ok := ctx.InternalVars["statusCode"]
Expand All @@ -168,7 +159,7 @@ func (templator *Templator) GetSupportedMethodMap() map[string]interface{} {
return templator.SupportedMethodMap
}

func (t *Templator) NewTemplatingData(requestDetails *models.RequestDetails, response *models.ResponseDetails, literals *models.Literals, vars *models.Variables, state map[string]string, journal *journal.Journal) *TemplatingData {
func (t *Templator) NewTemplatingData(requestDetails *models.RequestDetails, literals *models.Literals, vars *models.Variables, state map[string]string) *TemplatingData {

literalMap := make(map[string]interface{})
if literals != nil {
Expand All @@ -178,40 +169,13 @@ func (t *Templator) NewTemplatingData(requestDetails *models.RequestDetails, res
}

variableMap := t.getVariables(vars, requestDetails)
templateJournal := Journal{}
if journal != nil {

indexes := make([]JournalIndex, 0, len(journal.Indexes))
for _, index := range journal.Indexes {

journalIndexEntries := make(map[string]JournalEntry)
for indexKey, entry := range index.Entries {

journalEntry := JournalEntry{
requestBody: entry.Request.Body,
responseBody: entry.Response.Body,
}
journalIndexEntries[indexKey] = journalEntry
}
journalIndex := JournalIndex{
name: index.Name,
entries: journalIndexEntries,
}
indexes = append(indexes, journalIndex)
}
templateJournal = Journal{
indexes: indexes,
}

}

kvs := make(map[string]interface{})
return &TemplatingData{
Request: getRequest(requestDetails),
Literals: literalMap,
Vars: variableMap,
State: state,
Journal: templateJournal,
CurrentDateTime: func(a1, a2, a3 string) string {
return a1 + " " + a2 + " " + a3
},
Expand Down
30 changes: 14 additions & 16 deletions core/templating/templating_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ package templating_test
import (
"testing"

"github.com/SpectoLabs/hoverfly/core/journal"

"github.com/SpectoLabs/hoverfly/core/models"
"github.com/SpectoLabs/hoverfly/core/templating"
. "github.com/onsi/gomega"
Expand Down Expand Up @@ -297,7 +295,7 @@ func Test_ShouldCreateTemplatingDataPathsFromRequest(t *testing.T) {
Scheme: "http",
Destination: "test.com",
Path: "/foo/bar",
}, nil, &models.Literals{}, &models.Variables{}, make(map[string]string), &journal.Journal{})
}, &models.Literals{}, &models.Variables{}, make(map[string]string))

Expect(actual.Request.Path).To(ConsistOf("foo", "bar"))
}
Expand All @@ -308,7 +306,7 @@ func Test_ShouldCreateTemplatingDataPathsFromRequestWithNoPaths(t *testing.T) {
actual := templating.NewTemplator().NewTemplatingData(&models.RequestDetails{
Scheme: "http",
Destination: "test.com",
}, nil, &models.Literals{}, &models.Variables{}, make(map[string]string), &journal.Journal{})
}, &models.Literals{}, &models.Variables{}, make(map[string]string))

Expect(actual.Request.Path).To(BeEmpty())
}
Expand All @@ -323,7 +321,7 @@ func Test_ShouldCreateTemplatingDataQueryParamsFromRequest(t *testing.T) {
"cheese": {"1", "3"},
"ham": {"2"},
},
}, nil, &models.Literals{}, &models.Variables{}, make(map[string]string), &journal.Journal{})
}, &models.Literals{}, &models.Variables{}, make(map[string]string))

Expect(actual.Request.QueryParam).To(HaveKeyWithValue("cheese", []string{"1", "3"}))
Expect(actual.Request.QueryParam).To(HaveKeyWithValue("ham", []string{"2"}))
Expand All @@ -336,7 +334,7 @@ func Test_ShouldCreateTemplatingDataQueryParamsFromRequestWithNoQueryParams(t *t
actual := templating.NewTemplator().NewTemplatingData(&models.RequestDetails{
Scheme: "http",
Destination: "test.com",
}, nil, &models.Literals{}, &models.Variables{}, make(map[string]string), &journal.Journal{})
}, &models.Literals{}, &models.Variables{}, make(map[string]string))

Expect(actual.Request.QueryParam).To(BeEmpty())
}
Expand All @@ -347,7 +345,7 @@ func Test_ShouldCreateTemplatingDataHttpScheme(t *testing.T) {
actual := templating.NewTemplator().NewTemplatingData(&models.RequestDetails{
Scheme: "http",
Destination: "test.com",
}, nil, &models.Literals{}, &models.Variables{}, make(map[string]string), &journal.Journal{})
}, &models.Literals{}, &models.Variables{}, make(map[string]string))

Expect(actual.Request.Scheme).To(Equal("http"))
}
Expand All @@ -362,7 +360,7 @@ func Test_ShouldCreateTemplatingDataHeaderFromRequest(t *testing.T) {
"cheese": {"1", "3"},
"ham": {"2"},
},
}, nil, &models.Literals{}, &models.Variables{}, make(map[string]string), &journal.Journal{})
}, &models.Literals{}, &models.Variables{}, make(map[string]string))

Expect(actual.Request.Header).To(HaveKeyWithValue("cheese", []string{"1", "3"}))
Expect(actual.Request.Header).To(HaveKeyWithValue("ham", []string{"2"}))
Expand All @@ -375,7 +373,7 @@ func Test_ShouldCreateTemplatingDataHeaderFromRequestWithNoHeader(t *testing.T)
actual := templating.NewTemplator().NewTemplatingData(&models.RequestDetails{
Scheme: "http",
Destination: "test.com",
}, nil, &models.Literals{}, &models.Variables{}, make(map[string]string), &journal.Journal{})
}, &models.Literals{}, &models.Variables{}, make(map[string]string))

Expect(actual.Request.Header).To(BeEmpty())
}
Expand Down Expand Up @@ -702,7 +700,7 @@ func Test_VarSetToNilInCaseOfInvalidArgsPassed(t *testing.T) {
actual := templator.NewTemplatingData(&models.RequestDetails{
Scheme: "http",
Destination: "test.com",
}, nil, &models.Literals{}, vars, make(map[string]string), &journal.Journal{})
}, &models.Literals{}, vars, make(map[string]string))

Expect(actual.Vars["varOne"]).To(BeNil())

Expand All @@ -722,7 +720,7 @@ func Test_VarSetToProperValueInCaseOfRequestDetailsPassedAsArgument(t *testing.T

actual := templator.NewTemplatingData(&models.RequestDetails{
Path: "/part1/foo,bar",
}, nil, &models.Literals{}, vars, make(map[string]string), &journal.Journal{})
}, &models.Literals{}, vars, make(map[string]string))

Expect(actual.Vars["splitRequestPath"]).ToNot(BeNil())
Expect(len(actual.Vars["splitRequestPath"].([]string))).To(Equal(2))
Expand Down Expand Up @@ -823,13 +821,13 @@ func Test_ApplyTemplate_Arithmetic_Ops_With_Each_Block(t *testing.T) {

template, _ := templator.ParseTemplate(responseBody)
state := make(map[string]string)
result, err := templator.RenderTemplate(template, requestDetails, nil, &models.Literals{}, &models.Variables{}, state, &journal.Journal{})
result, err := templator.RenderTemplate(template, requestDetails, nil, &models.Literals{}, &models.Variables{}, state)

Expect(err).To(BeNil())
Expect(result).To(Equal(` 3.5 9 total: 12.50`))

// Running the second time should produce the same result because each execution has its own context data.
result, err = templator.RenderTemplate(template, requestDetails, nil, &models.Literals{}, &models.Variables{}, state, &journal.Journal{})
result, err = templator.RenderTemplate(template, requestDetails, nil, &models.Literals{}, &models.Variables{}, state)
Expect(err).To(BeNil())
Expect(result).To(Equal(` 3.5 9 total: 12.50`))
}
Expand Down Expand Up @@ -867,7 +865,7 @@ func Test_ApplyTemplate_setStatusCode(t *testing.T) {
Expect(err).To(BeNil())

response := &models.ResponseDetails{}
result, err := templator.RenderTemplate(template, &models.RequestDetails{}, response, &models.Literals{}, &models.Variables{}, make(map[string]string), &journal.Journal{})
result, err := templator.RenderTemplate(template, &models.RequestDetails{}, response, &models.Literals{}, &models.Variables{}, make(map[string]string))

Expect(err).To(BeNil())
Expect(result).To(Equal(""))
Expand All @@ -883,7 +881,7 @@ func Test_ApplyTemplate_setStatusCode_should_ignore_invalid_code(t *testing.T) {
Expect(err).To(BeNil())

response := &models.ResponseDetails{}
result, err := templator.RenderTemplate(template, &models.RequestDetails{}, response, &models.Literals{}, &models.Variables{}, make(map[string]string), &journal.Journal{})
result, err := templator.RenderTemplate(template, &models.RequestDetails{}, response, &models.Literals{}, &models.Variables{}, make(map[string]string))

Expect(err).To(BeNil())
Expect(result).To(Equal(""))
Expand Down Expand Up @@ -920,7 +918,7 @@ func ApplyTemplate(requestDetails *models.RequestDetails, state map[string]strin
func renderTemplate(requestDetails *models.RequestDetails, state map[string]string, responseBody string, templator *templating.Templator) (string, error) {
template, err := templator.ParseTemplate(responseBody)
Expect(err).To(BeNil())
return templator.RenderTemplate(template, requestDetails, nil, &models.Literals{}, &models.Variables{}, state, &journal.Journal{})
return templator.RenderTemplate(template, requestDetails, nil, &models.Literals{}, &models.Variables{}, state)
}

func initiateTemplator() *templating.Templator {
Expand Down
Loading