Repository: avenga/couper
Pull request #581 (2022-09-08 10:27:07)

Add 1.9 fixes
pick #580 from unreleased release.1.9 branch

---
<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 f449df97..e30ab47a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -16,6 +16,7 @@ Unreleased changes are available as `avenga/couper:edge` container.
  * Renamed `-debug` to `-pprof` and made debug port configurable via `-pprof-port`. Both [command line options](https://docs.couper.io/configuration/command-line#run-options) can also be specified via the respective [`settings`](https://docs.couper.io/configuration/block/settings). ([#577](https://github.com/avenga/couper/pull/577))
 
 * **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))
   * Disallow empty path parameters ([#526](https://github.com/avenga/couper/pull/526))
   * Basic Auth client authentication with OAuth2 (client ID and secret must be URL encoded) ([#537](https://github.com/avenga/couper/pull/537))
   * Config validation, e.g. label-uniqueness checks ([#563](https://github.com/avenga/couper/pull/563))
diff --git a/internal/seetie/convert.go b/internal/seetie/convert.go
index b0c43b81..7af31c79 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()
@@ -50,7 +50,7 @@ func ValueToMap(val cty.Value) map[string]interface{} {
 				result[k] = ValueToMap(v)
 				continue
 			}
-			if isTuple(v) {
+			if isListOrTuple(v) {
 				result[k] = ValueToStringSlice(v)
 				continue
 			}
@@ -281,8 +281,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:
@@ -293,7 +293,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)
 				}
 			}
@@ -303,15 +303,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:
@@ -322,7 +322,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))
 				}
 			}
@@ -364,18 +364,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)
+			}
+		})
+	}
+}