From 017bfd45b42610d912925b238f8c6ebc72a39239 Mon Sep 17 00:00:00 2001 From: xdrm-brackets Date: Thu, 21 Nov 2019 22:58:03 +0100 Subject: [PATCH 01/21] test multipart body paramters --- internal/reqdata/store_test.go | 235 +++++++++++++++++++++++++++++++++ 1 file changed, 235 insertions(+) diff --git a/internal/reqdata/store_test.go b/internal/reqdata/store_test.go index 6866a8f..2694e6b 100644 --- a/internal/reqdata/store_test.go +++ b/internal/reqdata/store_test.go @@ -545,3 +545,238 @@ func TestJsonParameters(t *testing.T) { } } + +func TestMultipartParameters(t *testing.T) { + tests := []struct { + RawMultipart string + + InvalidNames []string + ParamNames []string + ParamValues []interface{} + }{ + // no need to fully check json because it is parsed with the standard library + { + RawMultipart: ``, + InvalidNames: []string{}, + ParamNames: []string{}, + ParamValues: []interface{}{}, + }, + { + RawMultipart: `--xxx + `, + InvalidNames: []string{}, + ParamNames: []string{}, + ParamValues: []interface{}{}, + }, + { + RawMultipart: `--xxx +--xxx--`, + InvalidNames: []string{}, + ParamNames: []string{}, + ParamValues: []interface{}{}, + }, + { + RawMultipart: `--xxx +Content-Disposition: form-data; name="a" + +b +--xxx--`, + InvalidNames: []string{}, + ParamNames: []string{"a"}, + ParamValues: []interface{}{"b"}, + }, + { + RawMultipart: `--xxx +Content-Disposition: form-data; name="a" + +b +--xxx +Content-Disposition: form-data; name="c" + +d +--xxx--`, + InvalidNames: []string{}, + ParamNames: []string{"a", "c"}, + ParamValues: []interface{}{"b", "d"}, + }, + { + RawMultipart: `--xxx +Content-Disposition: form-data; name="_invalid" + +x +--xxx--`, + InvalidNames: []string{"_invalid"}, + ParamNames: []string{"_invalid"}, + ParamValues: []interface{}{nil}, + }, + { + RawMultipart: `--xxx +Content-Disposition: form-data; name="a" + +b +--xxx +Content-Disposition: form-data; name="_invalid" + +x +--xxx--`, + InvalidNames: []string{"_invalid"}, + ParamNames: []string{"a", "_invalid"}, + ParamValues: []interface{}{"b", nil}, + }, + + { + RawMultipart: `--xxx +Content-Disposition: form-data; name="invalid_" + +x +--xxx--`, + InvalidNames: []string{"invalid_"}, + ParamNames: []string{"invalid_"}, + ParamValues: []interface{}{nil}, + }, + { + RawMultipart: `--xxx +Content-Disposition: form-data; name="a" + +b +--xxx +Content-Disposition: form-data; name="invalid_" + +x +--xxx--`, + InvalidNames: []string{"invalid_"}, + ParamNames: []string{"a", "invalid_"}, + ParamValues: []interface{}{"b", nil}, + }, + + { + RawMultipart: `--xxx +Content-Disposition: form-data; name="GET@injection" + +x +--xxx--`, + InvalidNames: []string{"GET@injection"}, + ParamNames: []string{"GET@injection"}, + ParamValues: []interface{}{nil}, + }, + { + RawMultipart: `--xxx +Content-Disposition: form-data; name="a" + +b +--xxx +Content-Disposition: form-data; name="GET@injection" + +x +--xxx--`, + InvalidNames: []string{"GET@injection"}, + ParamNames: []string{"a", "GET@injection"}, + ParamValues: []interface{}{"b", nil}, + }, + + { + RawMultipart: `--xxx +Content-Disposition: form-data; name="URL#injection" + +x +--xxx--`, + InvalidNames: []string{"URL#injection"}, + ParamNames: []string{"URL#injection"}, + ParamValues: []interface{}{nil}, + }, + { + RawMultipart: `--xxx +Content-Disposition: form-data; name="a" + +b +--xxx +Content-Disposition: form-data; name="URL#injection" + +x +--xxx--`, + InvalidNames: []string{"URL#injection"}, + ParamNames: []string{"a", "URL#injection"}, + ParamValues: []interface{}{"b", nil}, + }, + // json parse error + { + RawMultipart: "{ \"a\": \"b\", }", + InvalidNames: []string{}, + ParamNames: []string{}, + ParamValues: []interface{}{}, + }, + } + + for i, test := range tests { + t.Run(fmt.Sprintf("request.%d", i), func(t *testing.T) { + body := bytes.NewBufferString(test.RawMultipart) + req := httptest.NewRequest(http.MethodPost, "http://host.com", body) + req.Header.Add("Content-Type", "multipart/form-data; boundary=xxx") + defer req.Body.Close() + store := New(nil, req) + + if test.ParamNames == nil || test.ParamValues == nil { + if len(store.Set) != 0 { + t.Errorf("expected no JSON parameters and got %d", len(store.Get)) + t.Failed() + } + + // no param to check + return + } + + if len(test.ParamNames) != len(test.ParamValues) { + t.Errorf("invalid test: names and values differ in size (%d vs %d)", len(test.ParamNames), len(test.ParamValues)) + t.Failed() + } + + for pi, pName := range test.ParamNames { + key := pName + value := test.ParamValues[pi] + + isNameValid := true + for _, invalid := range test.InvalidNames { + if pName == invalid { + isNameValid = false + } + } + + t.Run(key, func(t *testing.T) { + + param, isset := store.Set[key] + if !isset { + if isNameValid { + t.Errorf("store should contain element with key '%s'", key) + t.Failed() + } + return + } + + // if should be invalid + if isset && !isNameValid { + t.Errorf("store should NOT contain element with key '%s' (invalid name)", key) + t.Failed() + } + + valueType := reflect.TypeOf(value) + + paramValue := param.Value + paramValueType := reflect.TypeOf(param.Value) + + if valueType != paramValueType { + t.Errorf("should be of type %v (got '%v')", valueType, paramValueType) + t.Failed() + } + + if paramValue != value { + t.Errorf("should return %v (got '%v')", value, paramValue) + t.Failed() + } + + }) + + } + }) + } + +} -- 2.40.1 From ee34abd424a5443823be0080d84ccde2c8b0db65 Mon Sep 17 00:00:00 2001 From: xdrm-brackets Date: Thu, 21 Nov 2019 23:00:42 +0100 Subject: [PATCH 02/21] replace bytes.NewBufferString() with strings.NewReader() --- internal/reqdata/store_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/reqdata/store_test.go b/internal/reqdata/store_test.go index 2694e6b..7216753 100644 --- a/internal/reqdata/store_test.go +++ b/internal/reqdata/store_test.go @@ -1,11 +1,11 @@ package reqdata import ( - "bytes" "fmt" "net/http" "net/http/httptest" "reflect" + "strings" "testing" ) @@ -301,7 +301,7 @@ func TestStoreWithUrlEncodedForm(t *testing.T) { for i, test := range tests { t.Run(fmt.Sprintf("request.%d", i), func(t *testing.T) { - body := bytes.NewBufferString(test.URLEncoded) + body := strings.NewReader(test.URLEncoded) req := httptest.NewRequest(http.MethodPost, "http://host.com", body) req.Header.Add("Content-Type", "application/x-www-form-urlencoded") defer req.Body.Close() @@ -474,7 +474,7 @@ func TestJsonParameters(t *testing.T) { for i, test := range tests { t.Run(fmt.Sprintf("request.%d", i), func(t *testing.T) { - body := bytes.NewBufferString(test.RawJson) + body := strings.NewReader(test.RawJson) req := httptest.NewRequest(http.MethodPost, "http://host.com", body) req.Header.Add("Content-Type", "application/json") defer req.Body.Close() @@ -709,7 +709,7 @@ x for i, test := range tests { t.Run(fmt.Sprintf("request.%d", i), func(t *testing.T) { - body := bytes.NewBufferString(test.RawMultipart) + body := strings.NewReader(test.RawMultipart) req := httptest.NewRequest(http.MethodPost, "http://host.com", body) req.Header.Add("Content-Type", "multipart/form-data; boundary=xxx") defer req.Body.Close() -- 2.40.1 From e2e3799bc37284472a6020b528bf5a346dd070a0 Mon Sep 17 00:00:00 2001 From: xdrm-brackets Date: Thu, 21 Nov 2019 23:21:59 +0100 Subject: [PATCH 03/21] expand store.go coverage to 100% with a tricky test - force http.Request.ParseForm to fail --- internal/reqdata/store_test.go | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/internal/reqdata/store_test.go b/internal/reqdata/store_test.go index 7216753..0805b84 100644 --- a/internal/reqdata/store_test.go +++ b/internal/reqdata/store_test.go @@ -4,6 +4,7 @@ import ( "fmt" "net/http" "net/http/httptest" + "net/url" "reflect" "strings" "testing" @@ -222,7 +223,28 @@ func TestStoreWithGet(t *testing.T) { } } +func TestStoreWithUrlEncodedFormParseError(t *testing.T) { + // http.Request.ParseForm() fails when: + // - http.Request.Method is one of [POST,PUT,PATCH] + // - http.Request.Form is not nil (created manually) + // - http.Request.PostForm is nil (deleted manually) + // - http.Request.Body is nil (deleted manually) + req := httptest.NewRequest(http.MethodPost, "http://host.com/", nil) + req.Header.Add("Content-Type", "application/x-www-form-urlencoded") + + // break everything + req.Body = nil + req.Form = make(url.Values) + req.PostForm = nil + + // defer req.Body.Close() + store := New(nil, req) + if len(store.Form) > 0 { + t.Errorf("expected malformed urlencoded to have failed being parsed (got %d elements)", len(store.Form)) + t.FailNow() + } +} func TestStoreWithUrlEncodedForm(t *testing.T) { tests := []struct { URLEncoded string -- 2.40.1 From 3e718c96e8c7fedb4f18b3609b605a44436b9ac3 Mon Sep 17 00:00:00 2001 From: xdrm-brackets Date: Fri, 28 Feb 2020 17:50:55 +0100 Subject: [PATCH 04/21] return error when parsing parameters --- internal/reqdata/parameter.go | 56 +++++++++++++++++++++++++---------- 1 file changed, 41 insertions(+), 15 deletions(-) diff --git a/internal/reqdata/parameter.go b/internal/reqdata/parameter.go index 4bceb95..ba07fb0 100644 --- a/internal/reqdata/parameter.go +++ b/internal/reqdata/parameter.go @@ -4,8 +4,19 @@ import ( "encoding/json" "fmt" "reflect" + + "git.xdrm.io/go/aicra/internal/cerr" ) +// ErrUnknownType is returned when encountering an unknown type +const ErrUnknownType = cerr.Error("unknown type") + +// ErrInvalidJSON is returned when json parse failed +const ErrInvalidJSON = cerr.Error("invalid json") + +// ErrInvalidRootType is returned when json is a map +const ErrInvalidRootType = cerr.Error("invalid json root type") + // Parameter represents an http request parameter // that can be of type URL, GET, or FORM (multipart, json, urlencoded) type Parameter struct { @@ -22,16 +33,22 @@ type Parameter struct { } // Parse parameter (json-like) if not already done -func (i *Parameter) Parse() { +func (i *Parameter) Parse() error { /* (1) Stop if already parsed or nil*/ if i.Parsed || i.Value == nil { - return + return nil } /* (2) Try to parse value */ - i.Value = parseParameter(i.Value) + parsed, err := parseParameter(i.Value) + if err != nil { + return err + } + i.Value = parsed + + return nil } // parseParameter parses http GET/POST data @@ -39,7 +56,7 @@ func (i *Parameter) Parse() { // - size = 1 : return json of first element // - size > 1 : return array of json elements // - string : return json if valid, else return raw string -func parseParameter(data interface{}) interface{} { +func parseParameter(data interface{}) (interface{}, error) { dtype := reflect.TypeOf(data) dvalue := reflect.ValueOf(data) @@ -50,17 +67,21 @@ func parseParameter(data interface{}) interface{} { // 1. Return nothing if empty if dvalue.Len() == 0 { - return nil + return data, nil } // 2. only return first element if alone if dvalue.Len() == 1 { element := dvalue.Index(0) - if element.Kind() != reflect.String { - return nil + + // try to parse if a string (containing json) + if element.Kind() == reflect.String { + return parseParameter(element.String()) } - return parseParameter(element.String()) + + // already typed + return data, nil } @@ -72,12 +93,17 @@ func parseParameter(data interface{}) interface{} { // ignore non-string if element.Kind() != reflect.String { + result[i] = nil continue } - result[i] = parseParameter(element.String()) + parsed, err := parseParameter(element.String()) + if err != nil { + return data, err + } + result[i] = parsed } - return result + return result, nil /* (2) string -> parse */ case reflect.String: @@ -94,23 +120,23 @@ func parseParameter(data interface{}) interface{} { mapval, ok := result.(map[string]interface{}) if !ok { - return dvalue.String() + return dvalue.String(), ErrInvalidRootType } wrapped, ok := mapval["wrapped"] if !ok { - return dvalue.String() + return dvalue.String(), ErrInvalidJSON } - return wrapped + return wrapped, nil } // else return as string - return dvalue.String() + return dvalue.String(), nil } /* (3) NIL if unknown type */ - return dvalue + return dvalue, ErrUnknownType } -- 2.40.1 From 22947db2b6ae17c6069b466d05533a3407e23aec Mon Sep 17 00:00:00 2001 From: xdrm-brackets Date: Fri, 28 Feb 2020 18:41:05 +0100 Subject: [PATCH 05/21] fix: mark parameter as parsed after successful parsing - avoid parsing multiple times in a row ; it has no side-effect but is useless - actually does not cause any issue, but for consistency sake! --- internal/reqdata/parameter.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/reqdata/parameter.go b/internal/reqdata/parameter.go index ba07fb0..2e9aec7 100644 --- a/internal/reqdata/parameter.go +++ b/internal/reqdata/parameter.go @@ -42,10 +42,11 @@ func (i *Parameter) Parse() error { /* (2) Try to parse value */ parsed, err := parseParameter(i.Value) - if err != nil { return err } + + i.Parsed = true i.Value = parsed return nil -- 2.40.1 From 77dbc5663b5f38f06af2251dadf88ff203034bbb Mon Sep 17 00:00:00 2001 From: xdrm-brackets Date: Sun, 1 Mar 2020 21:34:14 +0100 Subject: [PATCH 06/21] test simple string parameter --- internal/reqdata/parameter_test.go | 58 ++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 internal/reqdata/parameter_test.go diff --git a/internal/reqdata/parameter_test.go b/internal/reqdata/parameter_test.go new file mode 100644 index 0000000..f63f33e --- /dev/null +++ b/internal/reqdata/parameter_test.go @@ -0,0 +1,58 @@ +package reqdata + +import ( + "testing" +) + +func TestSimpleString(t *testing.T) { + p := Parameter{Parsed: false, File: false, Value: "some-string"} + + err := p.Parse() + + if err != nil { + t.Errorf("unexpected error: <%s>", err) + t.FailNow() + } + + if !p.Parsed { + t.Errorf("expected parameter to be parsed") + t.FailNow() + } + + cast, canCast := p.Value.(string) + if !canCast { + t.Errorf("expected parameter to be a string") + t.FailNow() + } + + if cast != "some-string" { + t.Errorf("expected parameter to equal 'some-string', got '%s'", cast) + t.FailNow() + } +} +func TestStringSlice(t *testing.T) { + p := Parameter{Parsed: false, File: false, Value: "some-string"} + + err := p.Parse() + + if err != nil { + t.Errorf("unexpected error: <%s>", err) + t.FailNow() + } + + if !p.Parsed { + t.Errorf("expected parameter to be parsed") + t.FailNow() + } + + cast, canCast := p.Value.(string) + if !canCast { + t.Errorf("expected parameter to be a string") + t.FailNow() + } + + if cast != "some-string" { + t.Errorf("expected parameter to equal 'some-string', got '%s'", cast) + t.FailNow() + } +} -- 2.40.1 From 53eaec0c50cf58f69310da7db4daf9a2343c9701 Mon Sep 17 00:00:00 2001 From: xdrm-brackets Date: Sun, 1 Mar 2020 21:41:20 +0100 Subject: [PATCH 07/21] test regex for builtin string typecheck --- typecheck/builtin/string_test.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/typecheck/builtin/string_test.go b/typecheck/builtin/string_test.go index 048485b..d23b7bf 100644 --- a/typecheck/builtin/string_test.go +++ b/typecheck/builtin/string_test.go @@ -41,6 +41,18 @@ func TestString_AvailableTypes(t *testing.T) { {"string(1 )", false}, {"string( 1 )", false}, + {"string()", false}, + {"string(a)", false}, + {"string(-1)", false}, + + {"string(,)", false}, + {"string(1,b)", false}, + {"string(a,b)", false}, + {"string(a,1)", false}, + {"string(-1,1)", false}, + {"string(1,-1)", false}, + {"string(-1,-1)", false}, + {"string(1,2)", true}, {"string(1, 2)", true}, {"string(1, 2)", false}, -- 2.40.1 From 503f01bdddd2df36d6eb527fefe3d14aba79d70d Mon Sep 17 00:00:00 2001 From: xdrm-brackets Date: Sun, 1 Mar 2020 21:43:28 +0100 Subject: [PATCH 08/21] test uint builtin typecheck overflow values for []byte --- typecheck/builtin/uint_test.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/typecheck/builtin/uint_test.go b/typecheck/builtin/uint_test.go index c1d26aa..4a6ceb8 100644 --- a/typecheck/builtin/uint_test.go +++ b/typecheck/builtin/uint_test.go @@ -96,6 +96,11 @@ func TestUint_Values(t *testing.T) { // strane offset because of how precision works {fmt.Sprintf("%f", float64(math.MaxUint64+1024*3)), false}, + {[]byte(fmt.Sprintf("%d", math.MaxInt64)), true}, + {[]byte(fmt.Sprintf("%d", uint(math.MaxUint64))), true}, + // strane offset because of how precision works + {[]byte(fmt.Sprintf("%f", float64(math.MaxUint64+1024*3))), false}, + {"string", false}, {[]byte("bytes"), false}, {-0.1, false}, -- 2.40.1 From c2e8efc82b8a34d255a4762eafb0a7968d477ae6 Mon Sep 17 00:00:00 2001 From: xdrm-brackets Date: Mon, 2 Mar 2020 21:51:06 +0100 Subject: [PATCH 09/21] test string slice as string ; invalid json but valid when wrapped --- internal/reqdata/parameter_test.go | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/internal/reqdata/parameter_test.go b/internal/reqdata/parameter_test.go index f63f33e..ea5b328 100644 --- a/internal/reqdata/parameter_test.go +++ b/internal/reqdata/parameter_test.go @@ -31,7 +31,7 @@ func TestSimpleString(t *testing.T) { } } func TestStringSlice(t *testing.T) { - p := Parameter{Parsed: false, File: false, Value: "some-string"} + p := Parameter{Parsed: false, File: false, Value: `["str1", "str2"]`} err := p.Parse() @@ -45,14 +45,31 @@ func TestStringSlice(t *testing.T) { t.FailNow() } - cast, canCast := p.Value.(string) + slice, canCast := p.Value.([]interface{}) if !canCast { - t.Errorf("expected parameter to be a string") + t.Errorf("expected parameter to be a []interface{}") t.FailNow() } - if cast != "some-string" { - t.Errorf("expected parameter to equal 'some-string', got '%s'", cast) + if len(slice) != 2 { + t.Errorf("expected 2 values, got %d", len(slice)) t.FailNow() } + + results := []string{"str1", "str2"} + + for i, res := range results { + + cast, canCast := slice[i].(string) + if !canCast { + t.Errorf("expected parameter %d to be a []string", i) + continue + } + if cast != res { + t.Errorf("expected first value to be '%s', got '%s'", res, cast) + continue + } + + } + } -- 2.40.1 From e2bda2b2ddcbb628681a597d2bd856db0fdca607 Mon Sep 17 00:00:00 2001 From: xdrm-brackets Date: Mon, 2 Mar 2020 21:56:05 +0100 Subject: [PATCH 10/21] test json invalid boolean primitives ; only valid when wrapped --- internal/reqdata/parameter_test.go | 40 ++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/internal/reqdata/parameter_test.go b/internal/reqdata/parameter_test.go index ea5b328..38b3419 100644 --- a/internal/reqdata/parameter_test.go +++ b/internal/reqdata/parameter_test.go @@ -30,6 +30,7 @@ func TestSimpleString(t *testing.T) { t.FailNow() } } + func TestStringSlice(t *testing.T) { p := Parameter{Parsed: false, File: false, Value: `["str1", "str2"]`} @@ -73,3 +74,42 @@ func TestStringSlice(t *testing.T) { } } + +func TestJsonPrimitiveBool(t *testing.T) { + tcases := []struct { + Raw string + BoolValue bool + }{ + {"true", true}, + {"false", false}, + } + + for i, tcase := range tcases { + t.Run("case "+string(i), func(t *testing.T) { + p := Parameter{Parsed: false, File: false, Value: tcase.Raw} + + err := p.Parse() + if err != nil { + t.Errorf("unexpected error: <%s>", err) + t.FailNow() + } + + if !p.Parsed { + t.Errorf("expected parameter to be parsed") + t.FailNow() + } + + cast, canCast := p.Value.(bool) + if !canCast { + t.Errorf("expected parameter to be a bool") + t.FailNow() + } + + if cast != tcase.BoolValue { + t.Errorf("expected a value of %T, got %T", tcase.BoolValue, cast) + t.FailNow() + } + }) + } + +} -- 2.40.1 From 09b9eb2ae5a948e3b4491cdb8f9b95594b9dd6fc Mon Sep 17 00:00:00 2001 From: xdrm-brackets Date: Mon, 2 Mar 2020 22:00:39 +0100 Subject: [PATCH 11/21] fix formatting type --- internal/reqdata/parameter_test.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/internal/reqdata/parameter_test.go b/internal/reqdata/parameter_test.go index 38b3419..974991e 100644 --- a/internal/reqdata/parameter_test.go +++ b/internal/reqdata/parameter_test.go @@ -106,7 +106,13 @@ func TestJsonPrimitiveBool(t *testing.T) { } if cast != tcase.BoolValue { - t.Errorf("expected a value of %T, got %T", tcase.BoolValue, cast) + t.Errorf("expected a value of %t, got %t", tcase.BoolValue, cast) + t.FailNow() + } + }) + } + +} t.FailNow() } }) -- 2.40.1 From 0f599e58ced05329d45f28abb86becb386ea5006 Mon Sep 17 00:00:00 2001 From: xdrm-brackets Date: Mon, 2 Mar 2020 22:01:16 +0100 Subject: [PATCH 12/21] test json invalid float primitives ; only valid when wrapped --- internal/reqdata/parameter_test.go | 43 ++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/internal/reqdata/parameter_test.go b/internal/reqdata/parameter_test.go index 974991e..c4a9ce4 100644 --- a/internal/reqdata/parameter_test.go +++ b/internal/reqdata/parameter_test.go @@ -1,6 +1,7 @@ package reqdata import ( + "math" "testing" ) @@ -113,6 +114,48 @@ func TestJsonPrimitiveBool(t *testing.T) { } } + +func TestJsonPrimitiveFloat(t *testing.T) { + tcases := []struct { + Raw string + FloatValue float64 + }{ + {"1", 1}, + {"-1", -1}, + + {"0.001", 0.001}, + {"-0.001", -0.001}, + + {"1.9992", 1.9992}, + {"-1.9992", -1.9992}, + + {"19992", 19992}, + {"-19992", -19992}, + } + + for i, tcase := range tcases { + t.Run("case "+string(i), func(t *testing.T) { + p := Parameter{Parsed: false, File: false, Value: tcase.Raw} + + err := p.Parse() + if err != nil { + t.Errorf("unexpected error: <%s>", err) + t.FailNow() + } + + if !p.Parsed { + t.Errorf("expected parameter to be parsed") + t.FailNow() + } + + cast, canCast := p.Value.(float64) + if !canCast { + t.Errorf("expected parameter to be a float64") + t.FailNow() + } + + if math.Abs(cast-tcase.FloatValue) > 0.00001 { + t.Errorf("expected a value of %f, got %f", tcase.FloatValue, cast) t.FailNow() } }) -- 2.40.1 From 2d1b1aad6d9c09dd6a9fe53d2fbc310ce7d84441 Mon Sep 17 00:00:00 2001 From: xdrm-brackets Date: Mon, 2 Mar 2020 22:19:28 +0100 Subject: [PATCH 13/21] test conversion from 1-sized slice to first element (bool vs json boolean primitive) --- internal/reqdata/parameter_test.go | 106 +++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) diff --git a/internal/reqdata/parameter_test.go b/internal/reqdata/parameter_test.go index c4a9ce4..7e35532 100644 --- a/internal/reqdata/parameter_test.go +++ b/internal/reqdata/parameter_test.go @@ -162,3 +162,109 @@ func TestJsonPrimitiveFloat(t *testing.T) { } } + +func TestOneSliceStringToString(t *testing.T) { + p := Parameter{Parsed: false, File: false, Value: []string{"lonely-string"}} + + if err := p.Parse(); err != nil { + t.Errorf("unexpected error: <%s>", err) + t.FailNow() + } + + if !p.Parsed { + t.Errorf("expected parameter to be parsed") + t.FailNow() + } + + cast, canCast := p.Value.(string) + if !canCast { + t.Errorf("expected parameter to be a string") + t.FailNow() + } + + if cast != "lonely-string" { + t.Errorf("expected a value of '%s', got '%s'", "lonely-string", cast) + t.FailNow() + } +} + +func TestOneSliceBoolToBool(t *testing.T) { + tcases := []struct { + Raw bool + }{ + {true}, + {false}, + } + + for i, tcase := range tcases { + + t.Run("case "+string(i), func(t *testing.T) { + + p := Parameter{Parsed: false, File: false, Value: []bool{tcase.Raw}} + + if err := p.Parse(); err != nil { + t.Errorf("unexpected error: <%s>", err) + t.FailNow() + } + + if !p.Parsed { + t.Errorf("expected parameter to be parsed") + t.FailNow() + } + + cast, canCast := p.Value.(bool) + if !canCast { + t.Errorf("expected parameter to be a bool") + t.FailNow() + } + + if cast != tcase.Raw { + t.Errorf("expected a value of '%t', got '%t'", tcase.Raw, cast) + t.FailNow() + } + + }) + } + +} + +func TestOneSliceJsonBoolToBool(t *testing.T) { + tcases := []struct { + Raw string + BoolValue bool + }{ + {"true", true}, + {"false", false}, + } + + for i, tcase := range tcases { + + t.Run("case "+string(i), func(t *testing.T) { + + p := Parameter{Parsed: false, File: false, Value: []string{tcase.Raw}} + + if err := p.Parse(); err != nil { + t.Errorf("unexpected error: <%s>", err) + t.FailNow() + } + + if !p.Parsed { + t.Errorf("expected parameter to be parsed") + t.FailNow() + } + + cast, canCast := p.Value.(bool) + if !canCast { + t.Errorf("expected parameter to be a bool") + t.FailNow() + } + + if cast != tcase.BoolValue { + t.Errorf("expected a value of '%t', got '%t'", tcase.BoolValue, cast) + t.FailNow() + } + + }) + } + +} -- 2.40.1 From 8779f9ca3a57f081f10f2a1662437d87dbea32b6 Mon Sep 17 00:00:00 2001 From: xdrm-brackets Date: Mon, 2 Mar 2020 22:20:28 +0100 Subject: [PATCH 14/21] fix returning first element of 1-sized slice instead of the slice --- internal/reqdata/parameter.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/reqdata/parameter.go b/internal/reqdata/parameter.go index 2e9aec7..09d48a3 100644 --- a/internal/reqdata/parameter.go +++ b/internal/reqdata/parameter.go @@ -82,7 +82,7 @@ func parseParameter(data interface{}) (interface{}, error) { } // already typed - return data, nil + return element.Interface(), nil } -- 2.40.1 From 9bf7860dc901b8e1d1811fccbcdb0a4907de956a Mon Sep 17 00:00:00 2001 From: xdrm-brackets Date: Mon, 2 Mar 2020 22:24:36 +0100 Subject: [PATCH 15/21] [breaking] do not return first element of 1-sized slices as it, return a slice - it is more consistent and does not rely of a "hidden" assomption. - for consistency, it is also a better practice to always the same type when waiting to receive a slice ; the 1 element case should not break anything --- internal/reqdata/parameter.go | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/internal/reqdata/parameter.go b/internal/reqdata/parameter.go index 09d48a3..033cbfe 100644 --- a/internal/reqdata/parameter.go +++ b/internal/reqdata/parameter.go @@ -66,27 +66,12 @@ func parseParameter(data interface{}) (interface{}, error) { /* (1) []string -> recursive */ case reflect.Slice: - // 1. Return nothing if empty + // 1. ignore empty if dvalue.Len() == 0 { return data, nil } - // 2. only return first element if alone - if dvalue.Len() == 1 { - - element := dvalue.Index(0) - - // try to parse if a string (containing json) - if element.Kind() == reflect.String { - return parseParameter(element.String()) - } - - // already typed - return element.Interface(), nil - - } - - // 3. Return all elements if more than 1 + // 2. parse each element recursively result := make([]interface{}, dvalue.Len()) for i, l := 0, dvalue.Len(); i < l; i++ { -- 2.40.1 From 4b89ed6421eaecdd65544913f7adc172b80a9216 Mon Sep 17 00:00:00 2001 From: xdrm-brackets Date: Mon, 2 Mar 2020 22:34:34 +0100 Subject: [PATCH 16/21] remove outdated tests --- internal/reqdata/parameter_test.go | 106 ----------------------------- 1 file changed, 106 deletions(-) diff --git a/internal/reqdata/parameter_test.go b/internal/reqdata/parameter_test.go index 7e35532..c4a9ce4 100644 --- a/internal/reqdata/parameter_test.go +++ b/internal/reqdata/parameter_test.go @@ -162,109 +162,3 @@ func TestJsonPrimitiveFloat(t *testing.T) { } } - -func TestOneSliceStringToString(t *testing.T) { - p := Parameter{Parsed: false, File: false, Value: []string{"lonely-string"}} - - if err := p.Parse(); err != nil { - t.Errorf("unexpected error: <%s>", err) - t.FailNow() - } - - if !p.Parsed { - t.Errorf("expected parameter to be parsed") - t.FailNow() - } - - cast, canCast := p.Value.(string) - if !canCast { - t.Errorf("expected parameter to be a string") - t.FailNow() - } - - if cast != "lonely-string" { - t.Errorf("expected a value of '%s', got '%s'", "lonely-string", cast) - t.FailNow() - } -} - -func TestOneSliceBoolToBool(t *testing.T) { - tcases := []struct { - Raw bool - }{ - {true}, - {false}, - } - - for i, tcase := range tcases { - - t.Run("case "+string(i), func(t *testing.T) { - - p := Parameter{Parsed: false, File: false, Value: []bool{tcase.Raw}} - - if err := p.Parse(); err != nil { - t.Errorf("unexpected error: <%s>", err) - t.FailNow() - } - - if !p.Parsed { - t.Errorf("expected parameter to be parsed") - t.FailNow() - } - - cast, canCast := p.Value.(bool) - if !canCast { - t.Errorf("expected parameter to be a bool") - t.FailNow() - } - - if cast != tcase.Raw { - t.Errorf("expected a value of '%t', got '%t'", tcase.Raw, cast) - t.FailNow() - } - - }) - } - -} - -func TestOneSliceJsonBoolToBool(t *testing.T) { - tcases := []struct { - Raw string - BoolValue bool - }{ - {"true", true}, - {"false", false}, - } - - for i, tcase := range tcases { - - t.Run("case "+string(i), func(t *testing.T) { - - p := Parameter{Parsed: false, File: false, Value: []string{tcase.Raw}} - - if err := p.Parse(); err != nil { - t.Errorf("unexpected error: <%s>", err) - t.FailNow() - } - - if !p.Parsed { - t.Errorf("expected parameter to be parsed") - t.FailNow() - } - - cast, canCast := p.Value.(bool) - if !canCast { - t.Errorf("expected parameter to be a bool") - t.FailNow() - } - - if cast != tcase.BoolValue { - t.Errorf("expected a value of '%t', got '%t'", tcase.BoolValue, cast) - t.FailNow() - } - - }) - } - -} -- 2.40.1 From e1cdd1c2a33618a358397e2f1c7ef11cbc3bdf57 Mon Sep 17 00:00:00 2001 From: xdrm-brackets Date: Mon, 2 Mar 2020 22:42:17 +0100 Subject: [PATCH 17/21] fix parsing non-string slice values - only string were parsed using wrapped json - now we also keep primitive types --- internal/reqdata/parameter.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/reqdata/parameter.go b/internal/reqdata/parameter.go index 033cbfe..315d59f 100644 --- a/internal/reqdata/parameter.go +++ b/internal/reqdata/parameter.go @@ -79,7 +79,7 @@ func parseParameter(data interface{}) (interface{}, error) { // ignore non-string if element.Kind() != reflect.String { - result[i] = nil + result[i] = element.Interface() continue } -- 2.40.1 From c2a88f0d2d9aa5486dcb1601eac31c15f1e091f4 Mon Sep 17 00:00:00 2001 From: xdrm-brackets Date: Mon, 2 Mar 2020 22:42:45 +0100 Subject: [PATCH 18/21] test string slice vs. json string slice --- internal/reqdata/parameter_test.go | 46 +++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/internal/reqdata/parameter_test.go b/internal/reqdata/parameter_test.go index c4a9ce4..5e0c753 100644 --- a/internal/reqdata/parameter_test.go +++ b/internal/reqdata/parameter_test.go @@ -32,7 +32,7 @@ func TestSimpleString(t *testing.T) { } } -func TestStringSlice(t *testing.T) { +func TestJsonStringSlice(t *testing.T) { p := Parameter{Parsed: false, File: false, Value: `["str1", "str2"]`} err := p.Parse() @@ -76,6 +76,50 @@ func TestStringSlice(t *testing.T) { } +func TestStringSlice(t *testing.T) { + p := Parameter{Parsed: false, File: false, Value: []string{"str1", "str2"}} + + err := p.Parse() + + if err != nil { + t.Errorf("unexpected error: <%s>", err) + t.FailNow() + } + + if !p.Parsed { + t.Errorf("expected parameter to be parsed") + t.FailNow() + } + + slice, canCast := p.Value.([]interface{}) + if !canCast { + t.Errorf("expected parameter to be a []interface{}") + t.FailNow() + } + + if len(slice) != 2 { + t.Errorf("expected 2 values, got %d", len(slice)) + t.FailNow() + } + + results := []string{"str1", "str2"} + + for i, res := range results { + + cast, canCast := slice[i].(string) + if !canCast { + t.Errorf("expected parameter %d to be a []string", i) + continue + } + if cast != res { + t.Errorf("expected first value to be '%s', got '%s'", res, cast) + continue + } + + } + +} + func TestJsonPrimitiveBool(t *testing.T) { tcases := []struct { Raw string -- 2.40.1 From feec6e96d00ba2ebda5b55735f9a2e7fc01faf9c Mon Sep 17 00:00:00 2001 From: xdrm-brackets Date: Mon, 2 Mar 2020 22:42:59 +0100 Subject: [PATCH 19/21] test bool slice vs. json bool slice --- internal/reqdata/parameter_test.go | 88 ++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/internal/reqdata/parameter_test.go b/internal/reqdata/parameter_test.go index 5e0c753..c77ded2 100644 --- a/internal/reqdata/parameter_test.go +++ b/internal/reqdata/parameter_test.go @@ -206,3 +206,91 @@ func TestJsonPrimitiveFloat(t *testing.T) { } } + +func TestJsonBoolSlice(t *testing.T) { + p := Parameter{Parsed: false, File: false, Value: []string{"true", "false"}} + + err := p.Parse() + + if err != nil { + t.Errorf("unexpected error: <%s>", err) + t.FailNow() + } + + if !p.Parsed { + t.Errorf("expected parameter to be parsed") + t.FailNow() + } + + slice, canCast := p.Value.([]interface{}) + if !canCast { + t.Errorf("expected parameter to be a []interface{}") + t.FailNow() + } + + if len(slice) != 2 { + t.Errorf("expected 2 values, got %d", len(slice)) + t.FailNow() + } + + results := []bool{true, false} + + for i, res := range results { + + cast, canCast := slice[i].(bool) + if !canCast { + t.Errorf("expected parameter %d to be a []bool", i) + continue + } + if cast != res { + t.Errorf("expected first value to be '%t', got '%t'", res, cast) + continue + } + + } + +} + +func TestBoolSlice(t *testing.T) { + p := Parameter{Parsed: false, File: false, Value: []bool{true, false}} + + err := p.Parse() + + if err != nil { + t.Errorf("unexpected error: <%s>", err) + t.FailNow() + } + + if !p.Parsed { + t.Errorf("expected parameter to be parsed") + t.FailNow() + } + + slice, canCast := p.Value.([]interface{}) + if !canCast { + t.Errorf("expected parameter to be a []interface{}") + t.FailNow() + } + + if len(slice) != 2 { + t.Errorf("expected 2 values, got %d", len(slice)) + t.FailNow() + } + + results := []bool{true, false} + + for i, res := range results { + + cast, canCast := slice[i].(bool) + if !canCast { + t.Errorf("expected parameter %d to be a bool, got %v", i, slice[i]) + continue + } + if cast != res { + t.Errorf("expected first value to be '%t', got '%t'", res, cast) + continue + } + + } + +} -- 2.40.1 From dfa6cba137a4b5bfccec34d81d1ac5b990f294f7 Mon Sep 17 00:00:00 2001 From: xdrm-brackets Date: Mon, 2 Mar 2020 22:45:58 +0100 Subject: [PATCH 20/21] fix parsing primitive types instead of erroring unknown type --- internal/reqdata/parameter.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/reqdata/parameter.go b/internal/reqdata/parameter.go index 315d59f..2c80455 100644 --- a/internal/reqdata/parameter.go +++ b/internal/reqdata/parameter.go @@ -123,6 +123,6 @@ func parseParameter(data interface{}) (interface{}, error) { } /* (3) NIL if unknown type */ - return dvalue, ErrUnknownType + return dvalue.Interface(), nil } -- 2.40.1 From 9189babe209946073c87b7983db7157ca94da279 Mon Sep 17 00:00:00 2001 From: xdrm-brackets Date: Mon, 2 Mar 2020 22:49:17 +0100 Subject: [PATCH 21/21] test primitive types : bool, float64 --- internal/reqdata/parameter_test.go | 62 ++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/internal/reqdata/parameter_test.go b/internal/reqdata/parameter_test.go index c77ded2..29bcbec 100644 --- a/internal/reqdata/parameter_test.go +++ b/internal/reqdata/parameter_test.go @@ -32,6 +32,68 @@ func TestSimpleString(t *testing.T) { } } +func TestSimpleFloat(t *testing.T) { + tcases := []float64{12.3456789, -12.3456789, 0.0000001, -0.0000001} + + for i, tcase := range tcases { + t.Run("case "+string(i), func(t *testing.T) { + p := Parameter{Parsed: false, File: false, Value: tcase} + + if err := p.Parse(); err != nil { + t.Errorf("unexpected error: <%s>", err) + t.FailNow() + } + + if !p.Parsed { + t.Errorf("expected parameter to be parsed") + t.FailNow() + } + + cast, canCast := p.Value.(float64) + if !canCast { + t.Errorf("expected parameter to be a float64") + t.FailNow() + } + + if math.Abs(cast-tcase) > 0.00000001 { + t.Errorf("expected parameter to equal '%f', got '%f'", tcase, cast) + t.FailNow() + } + }) + } +} + +func TestSimpleBool(t *testing.T) { + tcases := []bool{true, false} + + for i, tcase := range tcases { + t.Run("case "+string(i), func(t *testing.T) { + p := Parameter{Parsed: false, File: false, Value: tcase} + + if err := p.Parse(); err != nil { + t.Errorf("unexpected error: <%s>", err) + t.FailNow() + } + + if !p.Parsed { + t.Errorf("expected parameter to be parsed") + t.FailNow() + } + + cast, canCast := p.Value.(bool) + if !canCast { + t.Errorf("expected parameter to be a bool") + t.FailNow() + } + + if cast != tcase { + t.Errorf("expected parameter to equal '%t', got '%t'", tcase, cast) + t.FailNow() + } + }) + } +} + func TestJsonStringSlice(t *testing.T) { p := Parameter{Parsed: false, File: false, Value: `["str1", "str2"]`} -- 2.40.1