Repository: avenga/couper
Pull request #125 (2021-02-24 12:57:41)

Unixtime function


Patch:
diff --git a/docs/README.md b/docs/README.md
index 4173e50d..03d9af5d 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -290,12 +290,15 @@ context.
 | `json_encode`      | Returns a JSON serialization of the given value. |
 | `to_lower`         | Converts a given string to lowercase. |
 | `to_upper`         | Converts a given string to uppercase. |
+| `unixtime`         | Retrieves the current UNIX timestamp in seconds. |
 
 Example usage:
 
 ```hcl
 my_attribute = base64_decode("aGVsbG8gd29ybGQK")
 
+iat = unixtime()
+
 my_json = json_encode({
   value-a: beresp.json_body.origin
   value-b: ["item1", "item2"]
diff --git a/eval/context.go b/eval/context.go
index 4e7354a0..dc10b3b6 100644
--- a/eval/context.go
+++ b/eval/context.go
@@ -273,6 +273,7 @@ func newFunctionsMap() map[string]function.Function {
 		"json_encode":   stdlib.JSONEncodeFunc,
 		"to_lower":      stdlib.LowerFunc,
 		"to_upper":      stdlib.UpperFunc,
+		"unixtime":      lib.UnixtimeFunc,
 	}
 }
 
diff --git a/eval/http.go b/eval/http.go
index b3e3cbdc..c843c6ae 100644
--- a/eval/http.go
+++ b/eval/http.go
@@ -2,6 +2,7 @@ package eval
 
 import (
 	"net/http"
+	"strconv"
 	"strings"
 
 	"github.com/hashicorp/hcl/v2"
@@ -209,6 +210,8 @@ func deleteHeader(val cty.Value, headerCtx http.Header) {
 
 func toSlice(val interface{}) []string {
 	switch val.(type) {
+	case float64:
+		return []string{strconv.FormatFloat(val.(float64), 'f', 0, 64)}
 	case string:
 		return []string{val.(string)}
 	case []string:
diff --git a/eval/lib/time.go b/eval/lib/time.go
new file mode 100644
index 00000000..30f96816
--- /dev/null
+++ b/eval/lib/time.go
@@ -0,0 +1,22 @@
+package lib
+
+import (
+	"time"
+
+	"github.com/zclconf/go-cty/cty"
+	"github.com/zclconf/go-cty/cty/function"
+)
+
+var (
+	UnixtimeFunc = newUnixtimeFunction()
+)
+
+func newUnixtimeFunction() function.Function {
+	return function.New(&function.Spec{
+		Params: []function.Parameter{},
+		Type:   function.StaticReturnType(cty.Number),
+		Impl: func(args []cty.Value, _ cty.Type) (ret cty.Value, err error) {
+			return cty.NumberIntVal(time.Now().Unix()), nil
+		},
+	})
+}
diff --git a/internal/seetie/convert.go b/internal/seetie/convert.go
index 7c2fe22b..854d1d90 100644
--- a/internal/seetie/convert.go
+++ b/internal/seetie/convert.go
@@ -135,6 +135,10 @@ func HeaderToMapValue(headers http.Header) cty.Value {
 	ctyMap := make(map[string]cty.Value)
 	for k, v := range headers {
 		if validKey.MatchString(k) {
+			if len(v) == 0 {
+				ctyMap[strings.ToLower(k)] = cty.StringVal("")
+				continue
+			}
 			ctyMap[strings.ToLower(k)] = cty.StringVal(v[0]) // TODO: ListVal??
 		}
 	}


Test patch:
diff --git a/eval/lib/time_test.go b/eval/lib/time_test.go
new file mode 100644
index 00000000..cf7e7217
--- /dev/null
+++ b/eval/lib/time_test.go
@@ -0,0 +1,45 @@
+package lib_test
+
+import (
+	"testing"
+	"time"
+
+	"github.com/zclconf/go-cty/cty"
+
+	"github.com/avenga/couper/config/configload"
+)
+
+func TestUnixtime(t *testing.T) {
+	tests := []struct {
+		name string
+		hcl  string
+		want int64
+	}{
+		{
+			"unixtime",
+			`
+			server "test" {
+			}
+			`,
+			time.Now().Unix(),
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			cf, err := configload.LoadBytes([]byte(tt.hcl), "couper.hcl")
+			now, err := cf.Context.Functions["unixtime"].Call([]cty.Value{})
+			if err != nil {
+				t.Fatal(err)
+			}
+			if !cty.Number.Equals(now.Type()) {
+				t.Errorf("Wrong return type; expected %s, got: %s", cty.Number.FriendlyName(), now.Type().FriendlyName())
+			}
+			bfnow := now.AsBigFloat()
+			inow, _ := bfnow.Int64()
+			if inow != tt.want {
+				t.Errorf("Wrong return value; expected %d, got: %d", tt.want, inow)
+			}
+		})
+	}
+}