diff --git a/CHANGELOG.md b/CHANGELOG.md index c53c0db3b..a77bbdda9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ Unreleased changes are available as `coupergateway/couper:edge` container. * [`Server-Timing` header](https://docs.couper.io/configuration/block/settings) only reporting last requests/proxies of [endpoint sequences](https://docs.couper.io/configuration/block/endpoint#endpoint-sequence) ([#751](https://github.com/coupergateway/couper/pull/751)) * Selecting of appropriate [error handler](https://docs.couper.io/configuration/block/error_handler) in two cases ([#753](https://github.com/coupergateway/couper/pull/753)) * Storing of digit-starting string object keys in [request context](https://docs.couper.io/configuration/variables#request) and of digit-starting string header field names in [request](https://docs.couper.io/configuration/variables#request) variable ([#799](https://github.com/coupergateway/couper/pull/799)) + * Use of boolean values for the `headers` attribute or [modifiers](https://docs.couper.io/configuration/modifiers) ([#805](https://github.com/coupergateway/couper/pull/805)) * **Dependencies** * build with go 1.21 ([#800](https://github.com/coupergateway/couper/pull/800)) diff --git a/eval/http.go b/eval/http.go index 0e9060b96..e3b996efe 100644 --- a/eval/http.go +++ b/eval/http.go @@ -535,6 +535,8 @@ func deleteHeader(val cty.Value, headerCtx http.Header) { func toSlice(val interface{}) []string { switch v := val.(type) { + case bool: + return []string{strconv.FormatBool(v)} case float64: return []string{strconv.FormatFloat(v, 'f', 0, 64)} case string: diff --git a/internal/test/test_backend.go b/internal/test/test_backend.go index b984c532f..caa6b7276 100644 --- a/internal/test/test_backend.go +++ b/internal/test/test_backend.go @@ -91,7 +91,7 @@ func registerHTTPHandler(b *Backend) { func createAnythingHandler(status int) func(rw http.ResponseWriter, req *http.Request) { return func(rw http.ResponseWriter, req *http.Request) { type anything struct { - Args, Query url.Values + Args, Query, PostForm url.Values Body string Headers http.Header Host string @@ -113,6 +113,7 @@ func createAnythingHandler(status int) func(rw http.ResponseWriter, req *http.Re Host: req.Host, Method: req.Method, Path: req.URL.Path, + PostForm: req.PostForm, Query: req.URL.Query(), RawQuery: req.URL.RawQuery, RemoteAddr: req.RemoteAddr, diff --git a/server/http_endpoints_test.go b/server/http_endpoints_test.go index e2a7aedba..5529e6742 100644 --- a/server/http_endpoints_test.go +++ b/server/http_endpoints_test.go @@ -1245,3 +1245,97 @@ func TestEndpointWildcardProxyPathWildcard(t *testing.T) { }) } } + +func Test_toSlice1(t *testing.T) { + shutdown, _ := newCouper("testdata/endpoints/22_couper.hcl", test.New(t)) + defer shutdown() + + expected := map[string][]string{ + "B": {"true"}, + "B2": {"false"}, + "Ba": {"", ""}, // TODO fix: "true", "false" + "Ba2": {"", ""}, // TODO fix: "false", "true" + "N": {"1"}, + "N2": {"2"}, + "Na": {"1", "2"}, + "Na2": {"3", "4"}, + "S": {"str"}, + "S2": {"asdf"}, + "Sa": {"s1", "s2"}, + "Sa2": {"s3", "s4"}, + } + helper := test.New(t) + res, err := http.PostForm("http://localhost:8080/1", url.Values{}) + helper.Must(err) + + if res.StatusCode != http.StatusOK { + t.Errorf("Unexpected status: want: 200, got %d", res.StatusCode) + } + + b, err := io.ReadAll(res.Body) + helper.Must(res.Body.Close()) + helper.Must(err) + + type result struct { + PostForm, Query, Headers map[string][]string + } + r := result{} + helper.Must(json.Unmarshal(b, &r)) + if diff := cmp.Diff(r.Query, expected); diff != "" { + t.Error(diff) + } + if diff := cmp.Diff(r.PostForm, expected); diff != "" { + t.Error(diff) + } + for k, v := range expected { + if diff := cmp.Diff(r.Headers[k], v); diff != "" { + t.Error(diff) + } + } + for k, v := range expected { + if diff := cmp.Diff(res.Header[k], v); diff != "" { + t.Error(diff) + } + } +} + +func Test_toSlice2(t *testing.T) { + shutdown, _ := newCouper("testdata/endpoints/22_couper.hcl", test.New(t)) + defer shutdown() + + expected := map[string][]string{ + "B": {"true"}, + "Ba": {"", ""}, // TODO fix: "true", "false" + "N": {"1"}, + "Na": {"1", "2"}, + "S": {"str"}, + "Sa": {"s1", "s2"}, + } + helper := test.New(t) + res, err := http.Get("http://localhost:8080/2") + helper.Must(err) + + if res.StatusCode != http.StatusOK { + t.Errorf("Unexpected status: want: 200, got %d", res.StatusCode) + } + + b, err := io.ReadAll(res.Body) + helper.Must(res.Body.Close()) + helper.Must(err) + + type result struct { + Headers map[string][]string + } + r := result{} + helper.Must(json.Unmarshal(b, &r)) + for k, v := range expected { + if diff := cmp.Diff(r.Headers[k], v); diff != "" { + t.Error(diff) + } + } + for k, v := range expected { + if diff := cmp.Diff(res.Header[k], v); diff != "" { + t.Error(diff) + } + } +} diff --git a/server/http_integration_test.go b/server/http_integration_test.go index f6ca53211..ad07ab46a 100644 --- a/server/http_integration_test.go +++ b/server/http_integration_test.go @@ -4250,7 +4250,7 @@ func TestFunctions(t *testing.T) { "X-Default-9": "", "X-Default-10": "", "X-Default-11": "0", - "X-Default-12": "", + "X-Default-12": "false", "X-Default-13": `{"a":1}`, "X-Default-14": `{"a":1}`, "X-Default-15": `[1,2]`, diff --git a/server/testdata/endpoints/22_couper.hcl b/server/testdata/endpoints/22_couper.hcl new file mode 100644 index 000000000..2ea2188af --- /dev/null +++ b/server/testdata/endpoints/22_couper.hcl @@ -0,0 +1,106 @@ +server "cl" { + api { + endpoint "/1" { + proxy { + backend = "be" + } + set_query_params = { + B = true + N = 1 + S = "str" + Ba = [true, false] + Na = [1, 2] + Sa = ["s1", "s2"] + } + add_query_params = { + B2 = false + N2 = 2 + S2 = "asdf" + Ba2 = [false, true] + Na2 = [3, 4] + Sa2 = ["s3", "s4"] + } + set_form_params = { + B = true + N = 1 + S = "str" + Ba = [true, false] + Na = [1, 2] + Sa = ["s1", "s2"] + } + add_form_params = { + B2 = false + N2 = 2 + S2 = "asdf" + Ba2 = [false, true] + Na2 = [3, 4] + Sa2 = ["s3", "s4"] + } + set_request_headers = { + B = true + N = 1 + S = "str" + Ba = [true, false] + Na = [1, 2] + Sa = ["s1", "s2"] + } + add_request_headers = { + B2 = false + N2 = 2 + S2 = "asdf" + Ba2 = [false, true] + Na2 = [3, 4] + Sa2 = ["s3", "s4"] + } + set_response_headers = { + B = true + N = 1 + S = "str" + Ba = [true, false] + Na = [1, 2] + Sa = ["s1", "s2"] + } + add_response_headers = { + B2 = false + N2 = 2 + S2 = "asdf" + Ba2 = [false, true] + Na2 = [3, 4] + Sa2 = ["s3", "s4"] + } + } + + endpoint "/2" { + request { + url = "/anything" + backend = "be" + headers = { + B = true + N = 1 + S = "str" + Ba = [true, false] + Na = [1, 2] + Sa = ["s1", "s2"] + } + } + response { + headers = { + B = true + N = 1 + S = "str" + Ba = [true, false] + Na = [1, 2] + Sa = ["s1", "s2"] + } + body = backend_responses.default.body + } + } + } +} + +definitions { + backend "be" { + origin = "${env.COUPER_TEST_BACKEND_ADDR}" + path = "/anything" + } +}