diff --git a/core/templating/template_helpers.go b/core/templating/template_helpers.go index f228099cd..a6bfc043e 100644 --- a/core/templating/template_helpers.go +++ b/core/templating/template_helpers.go @@ -3,6 +3,7 @@ package templating import ( "fmt" "github.com/SpectoLabs/hoverfly/core/journal" + "math" "reflect" "strconv" "strings" @@ -183,6 +184,71 @@ func (t templateHelpers) parseJournalBasedOnIndex(indexName, keyValue, dataSourc return getEvaluationString("journal", options) } +func (t templateHelpers) sum(numbers []string, format string) string { + return sumNumbers(numbers, format) +} + +func (t templateHelpers) add(val1 string, val2 string, format string) string { + return sumNumbers([]string{val1, val2}, format) +} + +func (t templateHelpers) subtract(val1 string, val2 string, format string) string { + f1, err1 := strconv.ParseFloat(val1, 64) + f2, err2 := strconv.ParseFloat(val2, 64) + if err1 != nil || err2 != nil { + return "NaN" + } + return formatNumber(f1-f2, format) +} + +func (t templateHelpers) multiply(val1 string, val2 string, format string) string { + f1, err1 := strconv.ParseFloat(val1, 64) + f2, err2 := strconv.ParseFloat(val2, 64) + if err1 != nil || err2 != nil { + return "NaN" + } + return formatNumber(f1*f2, format) +} + +func (t templateHelpers) divide(val1 string, val2 string, format string) string { + f1, err1 := strconv.ParseFloat(val1, 64) + f2, err2 := strconv.ParseFloat(val2, 64) + if err1 != nil || err2 != nil { + return "NaN" + } + return formatNumber(f1/f2, format) +} + +func sumNumbers(numbers []string, format string) string { + var sum float64 = 0 + for _, number := range numbers { + value, err := strconv.ParseFloat(number, 64) + if err != nil { + log.Error(err) + return "NaN" + } + sum += value + } + + return formatNumber(sum, format) +} + +func formatNumber(number float64, format string) string { + if format == "" { + return strings.TrimRight(strings.TrimRight(fmt.Sprintf("%f", number), "0"), ".") + } + + decimalPlaces := 0 + parts := strings.Split(format, ".") + if len(parts) == 2 { + decimalPlaces = len(parts[1]) + } + + multiplier := math.Pow(10, float64(decimalPlaces)) + rounded := math.Round(number*multiplier) / multiplier + return fmt.Sprintf("%."+strconv.Itoa(decimalPlaces)+"f", rounded) +} + func getIndexEntry(journalIndexDetails Journal, indexName, indexValue string) (*JournalEntry, error) { for _, index := range journalIndexDetails.indexes { diff --git a/core/templating/templating.go b/core/templating/templating.go index 8061c90e7..f9424c29b 100644 --- a/core/templating/templating.go +++ b/core/templating/templating.go @@ -87,6 +87,11 @@ func NewTemplator() *Templator { helperMethodMap["requestBody"] = t.requestBody helperMethodMap["csv"] = t.parseCsv helperMethodMap["journal"] = t.parseJournalBasedOnIndex + helperMethodMap["sum"] = t.sum + helperMethodMap["add"] = t.add + helperMethodMap["subtract"] = t.subtract + helperMethodMap["multiply"] = t.multiply + helperMethodMap["divide"] = t.divide if !helpersRegistered { raymond.RegisterHelpers(helperMethodMap) helpersRegistered = true diff --git a/core/templating/templating_test.go b/core/templating/templating_test.go index 415c9334a..57df99a5b 100644 --- a/core/templating/templating_test.go +++ b/core/templating/templating_test.go @@ -554,6 +554,86 @@ func Test_VarSetToProperValueInCaseOfRequestDetailsPassedAsArgument(t *testing.T } +func Test_ApplyTemplate_add_integers(t *testing.T) { + RegisterTestingT(t) + + template, err := ApplyTemplate(&models.RequestDetails{}, make(map[string]string), `{{ add '1' '2' '0'}}`) + + Expect(err).To(BeNil()) + + Expect(template).To(Equal("3")) +} + +func Test_ApplyTemplate_add_floats(t *testing.T) { + RegisterTestingT(t) + + template, err := ApplyTemplate(&models.RequestDetails{}, make(map[string]string), `{{ add '0.1' '1.34' '0.00'}}`) + + Expect(err).To(BeNil()) + + Expect(template).To(Equal("1.44")) +} + +func Test_ApplyTemplate_add_floats_withRoundUp(t *testing.T) { + RegisterTestingT(t) + + template, err := ApplyTemplate(&models.RequestDetails{}, make(map[string]string), `{{ add '0.1' '1.34' '0.0'}} and {{ add '0.1' '1.56' '0.0'}}`) + + Expect(err).To(BeNil()) + + Expect(template).To(Equal("1.4 and 1.7")) +} + +func Test_ApplyTemplate_add_number_without_format(t *testing.T) { + RegisterTestingT(t) + + template, err := ApplyTemplate(&models.RequestDetails{}, make(map[string]string), `{{ add '0.1' '1.34' ''}} and {{ add '1' '2' ''}} and {{ add '0' '0' ''}}`) + + Expect(err).To(BeNil()) + + Expect(template).To(Equal("1.44 and 3 and 0")) +} + +func Test_ApplyTemplate_add_NotNumber(t *testing.T) { + RegisterTestingT(t) + + template, err := ApplyTemplate(&models.RequestDetails{}, make(map[string]string), `{{ add 'a' 'b' '0.00'}}`) + + Expect(err).To(BeNil()) + + Expect(template).To(Equal("NaN")) +} + +func Test_ApplyTemplate_subtract_numbers(t *testing.T) { + RegisterTestingT(t) + + template, err := ApplyTemplate(&models.RequestDetails{}, make(map[string]string), `{{ subtract '10' '0.99' ''}}`) + + Expect(err).To(BeNil()) + + Expect(template).To(Equal("9.01")) +} + +func Test_ApplyTemplate_mutiply_numbers(t *testing.T) { + RegisterTestingT(t) + + template, err := ApplyTemplate(&models.RequestDetails{}, make(map[string]string), `{{ multiply '10' '0.99' ''}}`) + + Expect(err).To(BeNil()) + + Expect(template).To(Equal("9.9")) +} + +func Test_ApplyTemplate_divide_numbers(t *testing.T) { + RegisterTestingT(t) + + template, err := ApplyTemplate(&models.RequestDetails{}, make(map[string]string), `{{ divide '10' '2.5' ''}}`) + + Expect(err).To(BeNil()) + + Expect(template).To(Equal("4")) +} + func toInterfaceSlice(arguments []string) []interface{} { argumentsArray := make([]interface{}, len(arguments))