remove Parameter type, only keep method parseParameter()

This commit is contained in:
Adrien Marquès 2020-03-21 14:45:39 +01:00
parent 149ec9a9a0
commit 9a5b0dd6e3
Signed by: xdrm-brackets
GPG Key ID: D75243CA236D825E
4 changed files with 113 additions and 201 deletions

View File

@ -1,95 +0,0 @@
package reqdata
import (
"encoding/json"
"fmt"
"reflect"
)
// Parameter represents an http request parameter
// that can be of type URL, GET, or FORM (multipart, json, urlencoded)
type Parameter struct {
// whether the value has been json-parsed
// for optimisation purpose, parameters are only parsed
// if they are required by the current service
Parsed bool
// whether the value is a file
File bool
// the actual parameter value
Value interface{}
}
// Parse parameter (json-like) if not already done
func (i *Parameter) Parse() {
/* ignore already parsed or nil*/
if i.Parsed || i.Value == nil {
return
}
/* parse value */
i.Parsed = true
i.Value = parseParameter(i.Value)
}
// parseParameter parses http URI/GET/POST data
// - []string : return array of json elements
// - string : return json if valid, else return raw string
func parseParameter(data interface{}) interface{} {
dtype := reflect.TypeOf(data)
dvalue := reflect.ValueOf(data)
switch dtype.Kind() {
/* (1) []string -> recursive */
case reflect.Slice:
// 1. ignore empty
if dvalue.Len() == 0 {
return data
}
// 2. parse each element recursively
result := make([]interface{}, dvalue.Len())
for i, l := 0, dvalue.Len(); i < l; i++ {
element := dvalue.Index(i)
result[i] = parseParameter(element.Interface())
}
return result
/* (2) string -> parse */
case reflect.String:
// build json wrapper
wrapper := fmt.Sprintf("{\"wrapped\":%s}", dvalue.String())
// try to parse as json
var result interface{}
err := json.Unmarshal([]byte(wrapper), &result)
// return if success
if err != nil {
return dvalue.String()
}
mapval, ok := result.(map[string]interface{})
if !ok {
return dvalue.String()
}
wrapped, ok := mapval["wrapped"]
if !ok {
return dvalue.String()
}
return wrapped
}
/* (3) NIL if unknown type */
return dvalue.Interface()
}

View File

@ -6,15 +6,9 @@ import (
) )
func TestSimpleString(t *testing.T) { func TestSimpleString(t *testing.T) {
p := Parameter{Value: "some-string"} p := parseParameter("some-string")
p.Parse()
if !p.Parsed { cast, canCast := p.(string)
t.Errorf("expected parameter to be parsed")
t.FailNow()
}
cast, canCast := p.Value.(string)
if !canCast { if !canCast {
t.Errorf("expected parameter to be a string") t.Errorf("expected parameter to be a string")
t.FailNow() t.FailNow()
@ -31,15 +25,9 @@ func TestSimpleFloat(t *testing.T) {
for i, tcase := range tcases { for i, tcase := range tcases {
t.Run("case "+string(i), func(t *testing.T) { t.Run("case "+string(i), func(t *testing.T) {
p := Parameter{Parsed: false, File: false, Value: tcase} p := parseParameter(tcase)
p.Parse()
if !p.Parsed { cast, canCast := p.(float64)
t.Errorf("expected parameter to be parsed")
t.FailNow()
}
cast, canCast := p.Value.(float64)
if !canCast { if !canCast {
t.Errorf("expected parameter to be a float64") t.Errorf("expected parameter to be a float64")
t.FailNow() t.FailNow()
@ -58,16 +46,9 @@ func TestSimpleBool(t *testing.T) {
for i, tcase := range tcases { for i, tcase := range tcases {
t.Run("case "+string(i), func(t *testing.T) { t.Run("case "+string(i), func(t *testing.T) {
p := Parameter{Parsed: false, File: false, Value: tcase} p := parseParameter(tcase)
p.Parse() cast, canCast := p.(bool)
if !p.Parsed {
t.Errorf("expected parameter to be parsed")
t.FailNow()
}
cast, canCast := p.Value.(bool)
if !canCast { if !canCast {
t.Errorf("expected parameter to be a bool") t.Errorf("expected parameter to be a bool")
t.FailNow() t.FailNow()
@ -82,15 +63,9 @@ func TestSimpleBool(t *testing.T) {
} }
func TestJsonStringSlice(t *testing.T) { func TestJsonStringSlice(t *testing.T) {
p := Parameter{Parsed: false, File: false, Value: `["str1", "str2"]`} p := parseParameter(`["str1", "str2"]`)
p.Parse()
if !p.Parsed { slice, canCast := p.([]interface{})
t.Errorf("expected parameter to be parsed")
t.FailNow()
}
slice, canCast := p.Value.([]interface{})
if !canCast { if !canCast {
t.Errorf("expected parameter to be a []interface{}") t.Errorf("expected parameter to be a []interface{}")
t.FailNow() t.FailNow()
@ -120,15 +95,9 @@ func TestJsonStringSlice(t *testing.T) {
} }
func TestStringSlice(t *testing.T) { func TestStringSlice(t *testing.T) {
p := Parameter{Parsed: false, File: false, Value: []string{"str1", "str2"}} p := parseParameter([]string{"str1", "str2"})
p.Parse()
if !p.Parsed { slice, canCast := p.([]interface{})
t.Errorf("expected parameter to be parsed")
t.FailNow()
}
slice, canCast := p.Value.([]interface{})
if !canCast { if !canCast {
t.Errorf("expected parameter to be a []interface{}") t.Errorf("expected parameter to be a []interface{}")
t.FailNow() t.FailNow()
@ -168,15 +137,9 @@ func TestJsonPrimitiveBool(t *testing.T) {
for i, tcase := range tcases { for i, tcase := range tcases {
t.Run("case "+string(i), func(t *testing.T) { t.Run("case "+string(i), func(t *testing.T) {
p := Parameter{Parsed: false, File: false, Value: tcase.Raw} p := parseParameter(tcase.Raw)
p.Parse()
if !p.Parsed { cast, canCast := p.(bool)
t.Errorf("expected parameter to be parsed")
t.FailNow()
}
cast, canCast := p.Value.(bool)
if !canCast { if !canCast {
t.Errorf("expected parameter to be a bool") t.Errorf("expected parameter to be a bool")
t.FailNow() t.FailNow()
@ -211,15 +174,9 @@ func TestJsonPrimitiveFloat(t *testing.T) {
for i, tcase := range tcases { for i, tcase := range tcases {
t.Run("case "+string(i), func(t *testing.T) { t.Run("case "+string(i), func(t *testing.T) {
p := Parameter{Parsed: false, File: false, Value: tcase.Raw} p := parseParameter(tcase.Raw)
p.Parse()
if !p.Parsed { cast, canCast := p.(float64)
t.Errorf("expected parameter to be parsed")
t.FailNow()
}
cast, canCast := p.Value.(float64)
if !canCast { if !canCast {
t.Errorf("expected parameter to be a float64") t.Errorf("expected parameter to be a float64")
t.FailNow() t.FailNow()
@ -235,15 +192,9 @@ func TestJsonPrimitiveFloat(t *testing.T) {
} }
func TestJsonBoolSlice(t *testing.T) { func TestJsonBoolSlice(t *testing.T) {
p := Parameter{Parsed: false, File: false, Value: []string{"true", "false"}} p := parseParameter([]string{"true", "false"})
p.Parse()
if !p.Parsed { slice, canCast := p.([]interface{})
t.Errorf("expected parameter to be parsed")
t.FailNow()
}
slice, canCast := p.Value.([]interface{})
if !canCast { if !canCast {
t.Errorf("expected parameter to be a []interface{}") t.Errorf("expected parameter to be a []interface{}")
t.FailNow() t.FailNow()
@ -273,15 +224,9 @@ func TestJsonBoolSlice(t *testing.T) {
} }
func TestBoolSlice(t *testing.T) { func TestBoolSlice(t *testing.T) {
p := Parameter{Parsed: false, File: false, Value: []bool{true, false}} p := parseParameter([]bool{true, false})
p.Parse()
if !p.Parsed { slice, canCast := p.([]interface{})
t.Errorf("expected parameter to be parsed")
t.FailNow()
}
slice, canCast := p.Value.([]interface{})
if !canCast { if !canCast {
t.Errorf("expected parameter to be a []interface{}") t.Errorf("expected parameter to be a []interface{}")
t.FailNow() t.FailNow()

View File

@ -4,6 +4,7 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
"reflect"
"git.xdrm.io/go/aicra/internal/config" "git.xdrm.io/go/aicra/internal/config"
"git.xdrm.io/go/aicra/internal/multipart" "git.xdrm.io/go/aicra/internal/multipart"
@ -26,14 +27,14 @@ type Set struct {
// - FORM: no prefix // - FORM: no prefix
// - URL: '{uri_var}' // - URL: '{uri_var}'
// - GET: 'GET@' followed by the key in GET // - GET: 'GET@' followed by the key in GET
Data map[string]*Parameter Data map[string]interface{}
} }
// New creates a new empty store. // New creates a new empty store.
func New(service *config.Service) *Set { func New(service *config.Service) *Set {
return &Set{ return &Set{
service: service, service: service,
Data: make(map[string]*Parameter), Data: make(map[string]interface{}),
} }
} }
@ -53,17 +54,17 @@ func (i *Set) ExtractURI(req *http.Request) error {
return fmt.Errorf("%s: %w", capture.Name, ErrUnknownType) return fmt.Errorf("%s: %w", capture.Name, ErrUnknownType)
} }
// parse parameter
parsed := parseParameter(value)
// check type // check type
cast, valid := capture.Ref.Validator(value) cast, valid := capture.Ref.Validator(parsed)
if !valid { if !valid {
return fmt.Errorf("%s: %w", capture.Name, ErrInvalidType) return fmt.Errorf("%s: %w", capture.Name, ErrInvalidType)
} }
// store cast value in 'Set' // store cast value in 'Set'
i.Data[capture.Ref.Rename] = &Parameter{ i.Data[capture.Ref.Rename] = cast
Value: cast,
}
} }
return nil return nil
@ -86,16 +87,17 @@ func (i *Set) ExtractQuery(req *http.Request) error {
continue continue
} }
// parse parameter
parsed := parseParameter(value)
// check type // check type
cast, valid := param.Validator(value) cast, valid := param.Validator(parsed)
if !valid { if !valid {
return fmt.Errorf("%s: %w", name, ErrInvalidType) return fmt.Errorf("%s: %w", name, ErrInvalidType)
} }
// store value // store cast value
i.Data[param.Rename] = &Parameter{ i.Data[param.Rename] = cast
Value: cast,
}
} }
return nil return nil
@ -167,10 +169,8 @@ func (i *Set) parseJSON(req *http.Request) error {
return fmt.Errorf("%s: %w", name, ErrInvalidType) return fmt.Errorf("%s: %w", name, ErrInvalidType)
} }
// store value // store cast value
i.Data[param.Rename] = &Parameter{ i.Data[param.Rename] = cast
Value: cast,
}
} }
return nil return nil
@ -197,16 +197,17 @@ func (i *Set) parseUrlencoded(req *http.Request) error {
continue continue
} }
// parse parameter
parsed := parseParameter(value)
// check type // check type
cast, valid := param.Validator(value) cast, valid := param.Validator(parsed)
if !valid { if !valid {
return fmt.Errorf("%s: %w", name, ErrInvalidType) return fmt.Errorf("%s: %w", name, ErrInvalidType)
} }
// store value // store cast value
i.Data[param.Rename] = &Parameter{ i.Data[param.Rename] = cast
Value: cast,
}
} }
return nil return nil
@ -244,18 +245,79 @@ func (i *Set) parseMultipart(req *http.Request) error {
continue continue
} }
// parse parameter
parsed := parseParameter(string(component.Data))
// fail on invalid type // fail on invalid type
cast, valid := param.Validator(string(component.Data)) cast, valid := param.Validator(parsed)
if !valid { if !valid {
return fmt.Errorf("%s: %w", name, ErrInvalidType) return fmt.Errorf("%s: %w", name, ErrInvalidType)
} }
// store value // store cast value
i.Data[param.Rename] = &Parameter{ i.Data[param.Rename] = cast
Value: cast,
}
} }
return nil return nil
} }
// parseParameter parses http URI/GET/POST data
// - []string : return array of json elements
// - string : return json if valid, else return raw string
func parseParameter(data interface{}) interface{} {
dtype := reflect.TypeOf(data)
dvalue := reflect.ValueOf(data)
switch dtype.Kind() {
/* (1) []string -> recursive */
case reflect.Slice:
// 1. ignore empty
if dvalue.Len() == 0 {
return data
}
// 2. parse each element recursively
result := make([]interface{}, dvalue.Len())
for i, l := 0, dvalue.Len(); i < l; i++ {
element := dvalue.Index(i)
result[i] = parseParameter(element.Interface())
}
return result
/* (2) string -> parse */
case reflect.String:
// build json wrapper
wrapper := fmt.Sprintf("{\"wrapped\":%s}", dvalue.String())
// try to parse as json
var result interface{}
err := json.Unmarshal([]byte(wrapper), &result)
// return if success
if err != nil {
return dvalue.String()
}
mapval, ok := result.(map[string]interface{})
if !ok {
return dvalue.String()
}
wrapped, ok := mapval["wrapped"]
if !ok {
return dvalue.String()
}
return wrapped
}
/* (3) NIL if unknown type */
return dvalue.Interface()
}

View File

@ -280,7 +280,7 @@ func TestExtractQuery(t *testing.T) {
t.FailNow() t.FailNow()
} }
cast, canCast := param.Value.([]string) cast, canCast := param.([]interface{})
if !canCast { if !canCast {
t.Errorf("should return a []string (got '%v')", cast) t.Errorf("should return a []string (got '%v')", cast)
t.FailNow() t.FailNow()
@ -458,9 +458,9 @@ func TestExtractFormUrlEncoded(t *testing.T) {
t.FailNow() t.FailNow()
} }
cast, canCast := param.Value.([]string) cast, canCast := param.([]interface{})
if !canCast { if !canCast {
t.Errorf("should return a []string (got '%v')", cast) t.Errorf("should return a []interface{} (got '%v')", cast)
t.FailNow() t.FailNow()
} }
@ -606,8 +606,8 @@ func TestJsonParameters(t *testing.T) {
valueType := reflect.TypeOf(value) valueType := reflect.TypeOf(value)
paramValue := param.Value paramValue := param
paramValueType := reflect.TypeOf(param.Value) paramValueType := reflect.TypeOf(param)
if valueType != paramValueType { if valueType != paramValueType {
t.Errorf("should be of type %v (got '%v')", valueType, paramValueType) t.Errorf("should be of type %v (got '%v')", valueType, paramValueType)
@ -762,8 +762,8 @@ x
valueType := reflect.TypeOf(value) valueType := reflect.TypeOf(value)
paramValue := param.Value paramValue := param
paramValueType := reflect.TypeOf(param.Value) paramValueType := reflect.TypeOf(param)
if valueType != paramValueType { if valueType != paramValueType {
t.Errorf("should be of type %v (got '%v')", valueType, paramValueType) t.Errorf("should be of type %v (got '%v')", valueType, paramValueType)