Repository: avenga/couper
Pull request #535 (2022-07-05 13:50:05)

Log list or map
Allow `cty.ListVal` and `cty.MapVal` in convert function used for custom logs.

---
<details>
    <summary>Reviewer checklist</summary>
    <ul>
        <li>Read PR description: a summary about the changes is required</li>
        <li>Changelog updated</li>
        <li>Documentation: docs/{Reference, Cli, ...}, Docker and cli help/usage</li>
        <li>Pulled branch, manually tested</li>
        <li>Verified requirements are met</li>
        <li>Reviewed the code</li>
        <li>Reviewed the related tests</li>
    </ul>
</details>



Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 51b9d48f..269b331b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,9 @@
 
 Unreleased changes are available as `avenga/couper:edge` container.
 
+* **Fixed**
+  * [`form_body`, `headers` and `cookies`](./docs/REFERENCE.md#request) can now be properly [custom-logged](./docs/LOGS.md#custom-logging) ([#535](https://github.com/avenga/couper/pull/535))
+
 ---
 
 ## [1.9.2](https://github.com/avenga/couper/releases/tag/v1.9.2)
diff --git a/internal/seetie/convert.go b/internal/seetie/convert.go
index 0b648af6..8ced20b6 100644
--- a/internal/seetie/convert.go
+++ b/internal/seetie/convert.go
@@ -21,7 +21,7 @@ func ValueToMap(val cty.Value) map[string]interface{} {
 		return result
 	}
 	var valMap map[string]cty.Value
-	if isTuple(val) {
+	if isListOrTuple(val) {
 		valMap = val.AsValueSlice()[0].AsValueMap()
 	} else {
 		valMap = val.AsValueMap()
@@ -45,7 +45,7 @@ func ValueToMap(val cty.Value) map[string]interface{} {
 		case cty.Map(cty.NilType):
 			result[k] = nil
 		default:
-			if isTuple(v) {
+			if isListOrTuple(v) {
 				result[k] = ValueToStringSlice(v)
 				continue
 			}
@@ -276,8 +276,8 @@ func ValueToLogFields(val cty.Value) logrus.Fields {
 	fields := logrus.Fields{}
 
 	for k, v := range val.AsValueMap() {
-		if isTuple(v) {
-			fields[k] = ValueToLogFieldsFromTuple(v)
+		if isListOrTuple(v) {
+			fields[k] = valueToLogFieldsFromListOrTuple(v)
 		} else {
 			switch v.Type() {
 			case cty.Bool:
@@ -288,7 +288,7 @@ func ValueToLogFields(val cty.Value) logrus.Fields {
 				f, _ := v.AsBigFloat().Float64()
 				fields[k] = f
 			default:
-				if isObject(v) {
+				if isMapOrObject(v) {
 					fields[k] = ValueToLogFields(v)
 				}
 			}
@@ -298,15 +298,15 @@ func ValueToLogFields(val cty.Value) logrus.Fields {
 	return fields
 }
 
-func ValueToLogFieldsFromTuple(val cty.Value) []interface{} {
-	if !isTuple(val) {
+func valueToLogFieldsFromListOrTuple(val cty.Value) []interface{} {
+	if !isListOrTuple(val) {
 		return nil
 	}
 
 	var values []interface{}
 	for _, v := range val.AsValueSlice() {
-		if isTuple(v) {
-			values = append(values, ValueToLogFieldsFromTuple(v))
+		if isListOrTuple(v) {
+			values = append(values, valueToLogFieldsFromListOrTuple(v))
 		} else {
 			switch v.Type() {
 			case cty.Bool:
@@ -317,7 +317,7 @@ func ValueToLogFieldsFromTuple(val cty.Value) []interface{} {
 				f, _ := v.AsBigFloat().Float64()
 				values = append(values, f)
 			default:
-				if isObject(v) {
+				if isMapOrObject(v) {
 					values = append(values, ValueToLogFields(v))
 				}
 			}
@@ -359,18 +359,18 @@ func ToString(s interface{}) string {
 	}
 }
 
-// isObject checks by type name since object is not comparable by type.
-func isObject(v cty.Value) bool {
+func isMapOrObject(v cty.Value) bool {
 	if v.IsNull() {
 		return false
 	}
-	return v.Type().FriendlyNameForConstraint() == "object"
+	t := v.Type()
+	return t.IsMapType() || t.IsObjectType()
 }
 
-// isTuple checks by type name since tuple is not comparable by type.
-func isTuple(v cty.Value) bool {
+func isListOrTuple(v cty.Value) bool {
 	if v.IsNull() {
 		return false
 	}
-	return v.Type().FriendlyNameForConstraint() == "tuple"
+	t := v.Type()
+	return t.IsListType() || t.IsTupleType()
 }


Test patch:
diff --git a/internal/seetie/convert_test.go b/internal/seetie/convert_test.go
index 03d055f7..9ef31d13 100644
--- a/internal/seetie/convert_test.go
+++ b/internal/seetie/convert_test.go
@@ -2,8 +2,11 @@ package seetie
 
 import (
 	"fmt"
+	"net/http"
 	"testing"
 
+	"github.com/google/go-cmp/cmp"
+	"github.com/sirupsen/logrus"
 	"github.com/zclconf/go-cty/cty"
 )
 
@@ -37,3 +40,36 @@ func Test_stringListToValue(t *testing.T) {
 		})
 	}
 }
+
+func Test_ValueToLogFields(t *testing.T) {
+	type testCase struct {
+		name   string
+		val    cty.Value
+		expLog logrus.Fields
+	}
+	for _, tc := range []testCase{
+		{
+			name:   "form body",
+			val:    ValuesMapToValue(map[string][]string{"a": []string{"b"}}),
+			expLog: logrus.Fields{"v": logrus.Fields{"a": []interface{}{"b"}}},
+		},
+		{
+			name:   "cookies",
+			val:    CookiesToMapValue([]*http.Cookie{&http.Cookie{Name: "c", Value: "d"}}),
+			expLog: logrus.Fields{"v": logrus.Fields{"c": "d"}},
+		},
+		{
+			name:   "headers",
+			val:    HeaderToMapValue(http.Header{"c": []string{"d"}}),
+			expLog: logrus.Fields{"v": logrus.Fields{"c": "d"}},
+		},
+	} {
+		t.Run(tc.name, func(subT *testing.T) {
+			logs := cty.MapVal(map[string]cty.Value{"v": tc.val})
+			lf := ValueToLogFields(logs)
+			if !cmp.Equal(tc.expLog, lf) {
+				t.Errorf("Expected\n%#v, got:\n%#v", tc.expLog, lf)
+			}
+		})
+	}
+}