remove Parameter type, only keep method parseParameter()
This commit is contained in:
parent
149ec9a9a0
commit
9a5b0dd6e3
|
@ -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()
|
|
||||||
|
|
||||||
}
|
|
|
@ -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()
|
|
@ -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()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue