feat: url encoded parameters (uri + form) are only considered a slice when multiple values are set
- if `?a=123`, "123" is the value that can be validated as string, int, etc - if `?a=123&a=456`, the slice []type{123,456} is the value that can be validated as slice of strings, ints, etc.
This commit is contained in:
parent
fcc8b39717
commit
1cc24be254
|
@ -66,17 +66,24 @@ func (i *T) GetQuery(req http.Request) error {
|
|||
query := req.URL.Query()
|
||||
|
||||
for name, param := range i.service.Query {
|
||||
value, exist := query[name]
|
||||
|
||||
if !exist && !param.Optional {
|
||||
return fmt.Errorf("%s: %w", name, ErrMissingRequiredParam)
|
||||
}
|
||||
values, exist := query[name]
|
||||
|
||||
if !exist {
|
||||
if !param.Optional {
|
||||
return fmt.Errorf("%s: %w", name, ErrMissingRequiredParam)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
parsed := parseParameter(value)
|
||||
var parsed interface{}
|
||||
|
||||
// consider element instead of slice or elements when only 1
|
||||
if len(values) == 1 {
|
||||
parsed = parseParameter(values[0])
|
||||
} else { // consider slice
|
||||
parsed = parseParameter(values)
|
||||
}
|
||||
|
||||
cast, valid := param.Validator(parsed)
|
||||
if !valid {
|
||||
return fmt.Errorf("%s: %w", name, ErrInvalidType)
|
||||
|
@ -108,6 +115,13 @@ func (i *T) GetForm(req http.Request) error {
|
|||
return i.parseMultipart(req)
|
||||
|
||||
default:
|
||||
|
||||
// fail on at least 1 mandatory form param when there is no body
|
||||
for name, param := range i.service.Form {
|
||||
if !param.Optional {
|
||||
return fmt.Errorf("%s: %w", name, ErrMissingRequiredParam)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
@ -119,21 +133,19 @@ func (i *T) parseJSON(req http.Request) error {
|
|||
|
||||
decoder := json.NewDecoder(req.Body)
|
||||
err := decoder.Decode(&parsed)
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
}
|
||||
if err != io.EOF {
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: %w", err, ErrInvalidJSON)
|
||||
}
|
||||
}
|
||||
|
||||
for name, param := range i.service.Form {
|
||||
value, exist := parsed[name]
|
||||
|
||||
if !exist && !param.Optional {
|
||||
if !exist {
|
||||
if !param.Optional {
|
||||
return fmt.Errorf("%s: %w", name, ErrMissingRequiredParam)
|
||||
}
|
||||
|
||||
if !exist {
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -155,17 +167,24 @@ func (i *T) parseUrlencoded(req http.Request) error {
|
|||
}
|
||||
|
||||
for name, param := range i.service.Form {
|
||||
value, exist := req.PostForm[name]
|
||||
|
||||
if !exist && !param.Optional {
|
||||
return fmt.Errorf("%s: %w", name, ErrMissingRequiredParam)
|
||||
}
|
||||
values, exist := req.PostForm[name]
|
||||
|
||||
if !exist {
|
||||
if !param.Optional {
|
||||
return fmt.Errorf("%s: %w", name, ErrMissingRequiredParam)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
parsed := parseParameter(value)
|
||||
var parsed interface{}
|
||||
|
||||
// consider element instead of slice or elements when only 1
|
||||
if len(values) == 1 {
|
||||
parsed = parseParameter(values[0])
|
||||
} else { // consider slice
|
||||
parsed = parseParameter(values)
|
||||
}
|
||||
|
||||
cast, valid := param.Validator(parsed)
|
||||
if !valid {
|
||||
return fmt.Errorf("%s: %w", name, ErrInvalidType)
|
||||
|
@ -196,11 +215,10 @@ func (i *T) parseMultipart(req http.Request) error {
|
|||
for name, param := range i.service.Form {
|
||||
component, exist := mpr.Data[name]
|
||||
|
||||
if !exist && !param.Optional {
|
||||
if !exist {
|
||||
if !param.Optional {
|
||||
return fmt.Errorf("%s: %w", name, ErrMissingRequiredParam)
|
||||
}
|
||||
|
||||
if !exist {
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
|
@ -135,13 +135,11 @@ func TestStoreWithUri(t *testing.T) {
|
|||
if err != nil {
|
||||
if test.Err != nil {
|
||||
if !errors.Is(err, test.Err) {
|
||||
t.Errorf("expected error <%s>, got <%s>", test.Err, err)
|
||||
t.FailNow()
|
||||
t.Fatalf("expected error <%s>, got <%s>", test.Err, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
t.Errorf("unexpected error <%s>", err)
|
||||
t.FailNow()
|
||||
t.Fatalf("unexpected error <%s>", err)
|
||||
}
|
||||
|
||||
if len(store.Data) != len(service.Input) {
|
||||
|
@ -183,14 +181,14 @@ func TestExtractQuery(t *testing.T) {
|
|||
Query: "a",
|
||||
Err: nil,
|
||||
ParamNames: []string{"a"},
|
||||
ParamValues: [][]string{[]string{""}},
|
||||
ParamValues: [][]string{{""}},
|
||||
},
|
||||
{
|
||||
ServiceParam: []string{"a"},
|
||||
Query: "a&b",
|
||||
Err: nil,
|
||||
ParamNames: []string{"a"},
|
||||
ParamValues: [][]string{[]string{""}},
|
||||
ParamValues: [][]string{{""}},
|
||||
},
|
||||
{
|
||||
ServiceParam: []string{"a", "missing"},
|
||||
|
@ -204,40 +202,40 @@ func TestExtractQuery(t *testing.T) {
|
|||
Query: "a&b",
|
||||
Err: nil,
|
||||
ParamNames: []string{"a", "b"},
|
||||
ParamValues: [][]string{[]string{""}, []string{""}},
|
||||
ParamValues: [][]string{{""}, {""}},
|
||||
},
|
||||
{
|
||||
ServiceParam: []string{"a"},
|
||||
Err: nil,
|
||||
Query: "a=",
|
||||
ParamNames: []string{"a"},
|
||||
ParamValues: [][]string{[]string{""}},
|
||||
ParamValues: [][]string{{""}},
|
||||
},
|
||||
{
|
||||
ServiceParam: []string{"a", "b"},
|
||||
Err: nil,
|
||||
Query: "a=&b=x",
|
||||
ParamNames: []string{"a", "b"},
|
||||
ParamValues: [][]string{[]string{""}, []string{"x"}},
|
||||
ParamValues: [][]string{{""}, {"x"}},
|
||||
},
|
||||
{
|
||||
ServiceParam: []string{"a", "c"},
|
||||
Err: nil,
|
||||
Query: "a=b&c=d",
|
||||
ParamNames: []string{"a", "c"},
|
||||
ParamValues: [][]string{[]string{"b"}, []string{"d"}},
|
||||
ParamValues: [][]string{{"b"}, {"d"}},
|
||||
},
|
||||
{
|
||||
ServiceParam: []string{"a", "c"},
|
||||
Err: nil,
|
||||
Query: "a=b&c=d&a=x",
|
||||
ParamNames: []string{"a", "c"},
|
||||
ParamValues: [][]string{[]string{"b", "x"}, []string{"d"}},
|
||||
ParamValues: [][]string{{"b", "x"}, {"d"}},
|
||||
},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
t.Run(fmt.Sprintf("request.%d", i), func(t *testing.T) {
|
||||
t.Run(fmt.Sprintf("request[%d]", i), func(t *testing.T) {
|
||||
|
||||
store := New(getServiceWithQuery(test.ServiceParam...))
|
||||
|
||||
|
@ -246,19 +244,16 @@ func TestExtractQuery(t *testing.T) {
|
|||
if err != nil {
|
||||
if test.Err != nil {
|
||||
if !errors.Is(err, test.Err) {
|
||||
t.Errorf("expected error <%s>, got <%s>", test.Err, err)
|
||||
t.FailNow()
|
||||
t.Fatalf("expected error <%s>, got <%s>", test.Err, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
t.Errorf("unexpected error <%s>", err)
|
||||
t.FailNow()
|
||||
t.Fatalf("unexpected error <%s>", err)
|
||||
}
|
||||
|
||||
if test.ParamNames == nil || test.ParamValues == nil {
|
||||
if len(store.Data) != 0 {
|
||||
t.Errorf("expected no GET parameters and got %d", len(store.Data))
|
||||
t.FailNow()
|
||||
t.Fatalf("expected no GET parameters and got %d", len(store.Data))
|
||||
}
|
||||
|
||||
// no param to check
|
||||
|
@ -266,8 +261,7 @@ func TestExtractQuery(t *testing.T) {
|
|||
}
|
||||
|
||||
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.FailNow()
|
||||
t.Fatalf("invalid test: names and values differ in size (%d vs %d)", len(test.ParamNames), len(test.ParamValues))
|
||||
}
|
||||
|
||||
for pi, pName := range test.ParamNames {
|
||||
|
@ -276,29 +270,35 @@ func TestExtractQuery(t *testing.T) {
|
|||
t.Run(pName, func(t *testing.T) {
|
||||
param, isset := store.Data[pName]
|
||||
if !isset {
|
||||
t.Errorf("param does not exist")
|
||||
t.FailNow()
|
||||
t.Fatalf("param does not exist")
|
||||
}
|
||||
|
||||
// single value, should return a single element
|
||||
if len(values) == 1 {
|
||||
cast, canCast := param.(string)
|
||||
if !canCast {
|
||||
t.Fatalf("should return a string (got '%v')", cast)
|
||||
}
|
||||
if values[0] != cast {
|
||||
t.Fatalf("should return '%s' (got '%s')", values[0], cast)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// multiple values, should return a slice
|
||||
cast, canCast := param.([]interface{})
|
||||
if !canCast {
|
||||
t.Errorf("should return a []string (got '%v')", cast)
|
||||
t.FailNow()
|
||||
t.Fatalf("should return a []string (got '%v')", cast)
|
||||
}
|
||||
|
||||
if len(cast) != len(values) {
|
||||
t.Errorf("should return %d string(s) (got '%d')", len(values), len(cast))
|
||||
t.FailNow()
|
||||
t.Fatalf("should return %d string(s) (got '%d')", len(values), len(cast))
|
||||
}
|
||||
|
||||
for vi, value := range values {
|
||||
|
||||
t.Run(fmt.Sprintf("value.%d", vi), func(t *testing.T) {
|
||||
if value != cast[vi] {
|
||||
t.Errorf("should return '%s' (got '%s')", value, cast[vi])
|
||||
t.FailNow()
|
||||
t.Fatalf("should return '%s' (got '%s')", value, cast[vi])
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -326,9 +326,7 @@ func TestStoreWithUrlEncodedFormParseError(t *testing.T) {
|
|||
store := New(nil)
|
||||
err := store.GetForm(*req)
|
||||
if err == nil {
|
||||
t.Errorf("expected malformed urlencoded to have FailNow being parsed (got %d elements)", len(store.Data))
|
||||
t.FailNow()
|
||||
|
||||
t.Fatalf("expected malformed urlencoded to have FailNow being parsed (got %d elements)", len(store.Data))
|
||||
}
|
||||
}
|
||||
func TestExtractFormUrlEncoded(t *testing.T) {
|
||||
|
@ -359,14 +357,14 @@ func TestExtractFormUrlEncoded(t *testing.T) {
|
|||
URLEncoded: "a",
|
||||
Err: nil,
|
||||
ParamNames: []string{"a"},
|
||||
ParamValues: [][]string{[]string{""}},
|
||||
ParamValues: [][]string{{""}},
|
||||
},
|
||||
{
|
||||
ServiceParams: []string{"a"},
|
||||
URLEncoded: "a&b",
|
||||
Err: nil,
|
||||
ParamNames: []string{"a"},
|
||||
ParamValues: [][]string{[]string{""}},
|
||||
ParamValues: [][]string{{""}},
|
||||
},
|
||||
{
|
||||
ServiceParams: []string{"a", "missing"},
|
||||
|
@ -380,35 +378,35 @@ func TestExtractFormUrlEncoded(t *testing.T) {
|
|||
URLEncoded: "a&b",
|
||||
Err: nil,
|
||||
ParamNames: []string{"a", "b"},
|
||||
ParamValues: [][]string{[]string{""}, []string{""}},
|
||||
ParamValues: [][]string{{""}, {""}},
|
||||
},
|
||||
{
|
||||
ServiceParams: []string{"a"},
|
||||
Err: nil,
|
||||
URLEncoded: "a=",
|
||||
ParamNames: []string{"a"},
|
||||
ParamValues: [][]string{[]string{""}},
|
||||
ParamValues: [][]string{{""}},
|
||||
},
|
||||
{
|
||||
ServiceParams: []string{"a", "b"},
|
||||
Err: nil,
|
||||
URLEncoded: "a=&b=x",
|
||||
ParamNames: []string{"a", "b"},
|
||||
ParamValues: [][]string{[]string{""}, []string{"x"}},
|
||||
ParamValues: [][]string{{""}, {"x"}},
|
||||
},
|
||||
{
|
||||
ServiceParams: []string{"a", "c"},
|
||||
Err: nil,
|
||||
URLEncoded: "a=b&c=d",
|
||||
ParamNames: []string{"a", "c"},
|
||||
ParamValues: [][]string{[]string{"b"}, []string{"d"}},
|
||||
ParamValues: [][]string{{"b"}, {"d"}},
|
||||
},
|
||||
{
|
||||
ServiceParams: []string{"a", "c"},
|
||||
Err: nil,
|
||||
URLEncoded: "a=b&c=d&a=x",
|
||||
ParamNames: []string{"a", "c"},
|
||||
ParamValues: [][]string{[]string{"b", "x"}, []string{"d"}},
|
||||
ParamValues: [][]string{{"b", "x"}, {"d"}},
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -424,19 +422,16 @@ func TestExtractFormUrlEncoded(t *testing.T) {
|
|||
if err != nil {
|
||||
if test.Err != nil {
|
||||
if !errors.Is(err, test.Err) {
|
||||
t.Errorf("expected error <%s>, got <%s>", test.Err, err)
|
||||
t.FailNow()
|
||||
t.Fatalf("expected error <%s>, got <%s>", test.Err, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
t.Errorf("unexpected error <%s>", err)
|
||||
t.FailNow()
|
||||
t.Fatalf("unexpected error <%s>", err)
|
||||
}
|
||||
|
||||
if test.ParamNames == nil || test.ParamValues == nil {
|
||||
if len(store.Data) != 0 {
|
||||
t.Errorf("expected no GET parameters and got %d", len(store.Data))
|
||||
t.FailNow()
|
||||
t.Fatalf("expected no GET parameters and got %d", len(store.Data))
|
||||
}
|
||||
|
||||
// no param to check
|
||||
|
@ -444,8 +439,7 @@ func TestExtractFormUrlEncoded(t *testing.T) {
|
|||
}
|
||||
|
||||
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.FailNow()
|
||||
t.Fatalf("invalid test: names and values differ in size (%d vs %d)", len(test.ParamNames), len(test.ParamValues))
|
||||
}
|
||||
|
||||
for pi, key := range test.ParamNames {
|
||||
|
@ -454,29 +448,35 @@ func TestExtractFormUrlEncoded(t *testing.T) {
|
|||
t.Run(key, func(t *testing.T) {
|
||||
param, isset := store.Data[key]
|
||||
if !isset {
|
||||
t.Errorf("param does not exist")
|
||||
t.FailNow()
|
||||
t.Fatalf("param does not exist")
|
||||
}
|
||||
|
||||
// single value, should return a single element
|
||||
if len(values) == 1 {
|
||||
cast, canCast := param.(string)
|
||||
if !canCast {
|
||||
t.Fatalf("should return a string (got '%v')", cast)
|
||||
}
|
||||
if values[0] != cast {
|
||||
t.Fatalf("should return '%s' (got '%s')", values[0], cast)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// multiple values, should return a slice
|
||||
cast, canCast := param.([]interface{})
|
||||
if !canCast {
|
||||
t.Errorf("should return a []interface{} (got '%v')", cast)
|
||||
t.FailNow()
|
||||
t.Fatalf("should return a []string (got '%v')", cast)
|
||||
}
|
||||
|
||||
if len(cast) != len(values) {
|
||||
t.Errorf("should return %d string(s) (got '%d')", len(values), len(cast))
|
||||
t.FailNow()
|
||||
t.Fatalf("should return %d string(s) (got '%d')", len(values), len(cast))
|
||||
}
|
||||
|
||||
for vi, value := range values {
|
||||
|
||||
t.Run(fmt.Sprintf("value.%d", vi), func(t *testing.T) {
|
||||
if value != cast[vi] {
|
||||
t.Errorf("should return '%s' (got '%s')", value, cast[vi])
|
||||
t.FailNow()
|
||||
t.Fatalf("should return '%s' (got '%s')", value, cast[vi])
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -567,19 +567,16 @@ func TestJsonParameters(t *testing.T) {
|
|||
if err != nil {
|
||||
if test.Err != nil {
|
||||
if !errors.Is(err, test.Err) {
|
||||
t.Errorf("expected error <%s>, got <%s>", test.Err, err)
|
||||
t.FailNow()
|
||||
t.Fatalf("expected error <%s>, got <%s>", test.Err, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
t.Errorf("unexpected error <%s>", err)
|
||||
t.FailNow()
|
||||
t.Fatalf("unexpected error <%s>", err)
|
||||
}
|
||||
|
||||
if test.ParamNames == nil || test.ParamValues == nil {
|
||||
if len(store.Data) != 0 {
|
||||
t.Errorf("expected no JSON parameters and got %d", len(store.Data))
|
||||
t.FailNow()
|
||||
t.Fatalf("expected no JSON parameters and got %d", len(store.Data))
|
||||
}
|
||||
|
||||
// no param to check
|
||||
|
@ -587,8 +584,7 @@ func TestJsonParameters(t *testing.T) {
|
|||
}
|
||||
|
||||
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.FailNow()
|
||||
t.Fatalf("invalid test: names and values differ in size (%d vs %d)", len(test.ParamNames), len(test.ParamValues))
|
||||
}
|
||||
|
||||
for pi, pName := range test.ParamNames {
|
||||
|
@ -599,8 +595,7 @@ func TestJsonParameters(t *testing.T) {
|
|||
|
||||
param, isset := store.Data[key]
|
||||
if !isset {
|
||||
t.Errorf("store should contain element with key '%s'", key)
|
||||
t.FailNow()
|
||||
t.Fatalf("store should contain element with key '%s'", key)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -610,13 +605,11 @@ func TestJsonParameters(t *testing.T) {
|
|||
paramValueType := reflect.TypeOf(param)
|
||||
|
||||
if valueType != paramValueType {
|
||||
t.Errorf("should be of type %v (got '%v')", valueType, paramValueType)
|
||||
t.FailNow()
|
||||
t.Fatalf("should be of type %v (got '%v')", valueType, paramValueType)
|
||||
}
|
||||
|
||||
if paramValue != value {
|
||||
t.Errorf("should return %v (got '%v')", value, paramValue)
|
||||
t.FailNow()
|
||||
t.Fatalf("should return %v (got '%v')", value, paramValue)
|
||||
}
|
||||
|
||||
})
|
||||
|
@ -724,19 +717,16 @@ x
|
|||
if err != nil {
|
||||
if test.Err != nil {
|
||||
if !errors.Is(err, test.Err) {
|
||||
t.Errorf("expected error <%s>, got <%s>", test.Err, err)
|
||||
t.FailNow()
|
||||
t.Fatalf("expected error <%s>, got <%s>", test.Err, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
t.Errorf("unexpected error <%s>", err)
|
||||
t.FailNow()
|
||||
t.Fatalf("unexpected error <%s>", err)
|
||||
}
|
||||
|
||||
if test.ParamNames == nil || test.ParamValues == nil {
|
||||
if len(store.Data) != 0 {
|
||||
t.Errorf("expected no JSON parameters and got %d", len(store.Data))
|
||||
t.FailNow()
|
||||
t.Fatalf("expected no JSON parameters and got %d", len(store.Data))
|
||||
}
|
||||
|
||||
// no param to check
|
||||
|
@ -744,8 +734,7 @@ x
|
|||
}
|
||||
|
||||
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.FailNow()
|
||||
t.Fatalf("invalid test: names and values differ in size (%d vs %d)", len(test.ParamNames), len(test.ParamValues))
|
||||
}
|
||||
|
||||
for pi, key := range test.ParamNames {
|
||||
|
@ -755,8 +744,7 @@ x
|
|||
|
||||
param, isset := store.Data[key]
|
||||
if !isset {
|
||||
t.Errorf("store should contain element with key '%s'", key)
|
||||
t.FailNow()
|
||||
t.Fatalf("store should contain element with key '%s'", key)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -766,13 +754,11 @@ x
|
|||
paramValueType := reflect.TypeOf(param)
|
||||
|
||||
if valueType != paramValueType {
|
||||
t.Errorf("should be of type %v (got '%v')", valueType, paramValueType)
|
||||
t.FailNow()
|
||||
t.Fatalf("should be of type %v (got '%v')", valueType, paramValueType)
|
||||
}
|
||||
|
||||
if paramValue != value {
|
||||
t.Errorf("should return %v (got '%v')", value, paramValue)
|
||||
t.FailNow()
|
||||
t.Fatalf("should return %v (got '%v')", value, paramValue)
|
||||
}
|
||||
|
||||
})
|
||||
|
|
Loading…
Reference in New Issue