refactor-test #15
|
@ -13,33 +13,29 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Set represents all data that can be caught:
|
// T represents all data that can be caught from an http request for a specific
|
||||||
|
// configuration Service; it features:
|
||||||
// - URI (from the URI)
|
// - URI (from the URI)
|
||||||
// - GET (default url data)
|
// - GET (standard url data)
|
||||||
// - POST (from json, form-data, url-encoded)
|
// - POST (from json, form-data, url-encoded)
|
||||||
// - 'application/json' => key-value pair is parsed as json into the map
|
// - 'application/json' => key-value pair is parsed as json into the map
|
||||||
// - 'application/x-www-form-urlencoded' => standard parameters as QUERY parameters
|
// - 'application/x-www-form-urlencoded' => standard parameters as QUERY parameters
|
||||||
// - 'multipart/form-data' => parse form-data format
|
// - 'multipart/form-data' => parse form-data format
|
||||||
type Set struct {
|
type T struct {
|
||||||
service *config.Service
|
service *config.Service
|
||||||
|
|
||||||
// contains URL+GET+FORM data with prefixes:
|
|
||||||
// - FORM: no prefix
|
|
||||||
// - URL: '{uri_var}'
|
|
||||||
// - GET: 'GET@' followed by the key in GET
|
|
||||||
Data map[string]interface{}
|
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) *T {
|
||||||
return &Set{
|
return &T{
|
||||||
service: service,
|
service: service,
|
||||||
Data: make(map[string]interface{}),
|
Data: map[string]interface{}{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExtractURI fills 'Set' with creating pointers inside 'Url'
|
// GetURI parameters
|
||||||
func (i *Set) ExtractURI(req http.Request) error {
|
func (i *T) GetURI(req http.Request) error {
|
||||||
uriparts := config.SplitURL(req.URL.RequestURI())
|
uriparts := config.SplitURL(req.URL.RequestURI())
|
||||||
|
|
||||||
for _, capture := range i.service.Captures {
|
for _, capture := range i.service.Captures {
|
||||||
|
@ -54,122 +50,97 @@ 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)
|
parsed := parseParameter(value)
|
||||||
|
|
||||||
// check type
|
|
||||||
cast, valid := capture.Ref.Validator(parsed)
|
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'
|
|
||||||
i.Data[capture.Ref.Rename] = cast
|
i.Data[capture.Ref.Rename] = cast
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExtractQuery data from the url query parameters
|
// GetQuery data from the url query parameters
|
||||||
func (i *Set) ExtractQuery(req http.Request) error {
|
func (i *T) GetQuery(req http.Request) error {
|
||||||
query := req.URL.Query()
|
query := req.URL.Query()
|
||||||
|
|
||||||
for name, param := range i.service.Query {
|
for name, param := range i.service.Query {
|
||||||
value, exist := query[name]
|
value, exist := query[name]
|
||||||
|
|
||||||
// fail on missing required
|
|
||||||
if !exist && !param.Optional {
|
if !exist && !param.Optional {
|
||||||
return fmt.Errorf("%s: %w", name, ErrMissingRequiredParam)
|
return fmt.Errorf("%s: %w", name, ErrMissingRequiredParam)
|
||||||
}
|
}
|
||||||
|
|
||||||
// optional
|
|
||||||
if !exist {
|
if !exist {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse parameter
|
|
||||||
parsed := parseParameter(value)
|
parsed := parseParameter(value)
|
||||||
|
|
||||||
// check type
|
|
||||||
cast, valid := param.Validator(parsed)
|
cast, valid := param.Validator(parsed)
|
||||||
if !valid {
|
if !valid {
|
||||||
return fmt.Errorf("%s: %w", name, ErrInvalidType)
|
return fmt.Errorf("%s: %w", name, ErrInvalidType)
|
||||||
}
|
}
|
||||||
|
|
||||||
// store cast value
|
|
||||||
i.Data[param.Rename] = cast
|
i.Data[param.Rename] = cast
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExtractForm data from request
|
// GetForm parameters the from request
|
||||||
//
|
|
||||||
// - parse 'form-data' if not supported for non-POST requests
|
// - parse 'form-data' if not supported for non-POST requests
|
||||||
// - parse 'x-www-form-urlencoded'
|
// - parse 'x-www-form-urlencoded'
|
||||||
// - parse 'application/json'
|
// - parse 'application/json'
|
||||||
func (i *Set) ExtractForm(req http.Request) error {
|
func (i *T) GetForm(req http.Request) error {
|
||||||
|
|
||||||
// ignore GET method
|
|
||||||
if req.Method == http.MethodGet {
|
if req.Method == http.MethodGet {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
contentType := req.Header.Get("Content-Type")
|
ct := req.Header.Get("Content-Type")
|
||||||
|
switch {
|
||||||
// parse json
|
case strings.HasPrefix(ct, "application/json"):
|
||||||
if strings.HasPrefix(contentType, "application/json") {
|
|
||||||
return i.parseJSON(req)
|
return i.parseJSON(req)
|
||||||
}
|
|
||||||
|
|
||||||
// parse urlencoded
|
case strings.HasPrefix(ct, "application/x-www-form-urlencoded"):
|
||||||
if strings.HasPrefix(contentType, "application/x-www-form-urlencoded") {
|
|
||||||
return i.parseUrlencoded(req)
|
return i.parseUrlencoded(req)
|
||||||
}
|
|
||||||
|
|
||||||
// parse multipart
|
case strings.HasPrefix(ct, "multipart/form-data; boundary="):
|
||||||
if strings.HasPrefix(contentType, "multipart/form-data; boundary=") {
|
|
||||||
return i.parseMultipart(req)
|
return i.parseMultipart(req)
|
||||||
}
|
|
||||||
|
|
||||||
// nothing to parse
|
default:
|
||||||
return nil
|
return nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseJSON parses JSON from the request body inside 'Form'
|
// parseJSON parses JSON from the request body inside 'Form'
|
||||||
// and 'Set'
|
// and 'Set'
|
||||||
func (i *Set) parseJSON(req http.Request) error {
|
func (i *T) parseJSON(req http.Request) error {
|
||||||
|
var parsed map[string]interface{}
|
||||||
parsed := make(map[string]interface{}, 0)
|
|
||||||
|
|
||||||
decoder := json.NewDecoder(req.Body)
|
decoder := json.NewDecoder(req.Body)
|
||||||
if err := decoder.Decode(&parsed); err != nil {
|
err := decoder.Decode(&parsed)
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
if err != nil {
|
||||||
return fmt.Errorf("%s: %w", err, ErrInvalidJSON)
|
return fmt.Errorf("%s: %w", err, ErrInvalidJSON)
|
||||||
}
|
}
|
||||||
|
|
||||||
for name, param := range i.service.Form {
|
for name, param := range i.service.Form {
|
||||||
value, exist := parsed[name]
|
value, exist := parsed[name]
|
||||||
|
|
||||||
// fail on missing required
|
|
||||||
if !exist && !param.Optional {
|
if !exist && !param.Optional {
|
||||||
return fmt.Errorf("%s: %w", name, ErrMissingRequiredParam)
|
return fmt.Errorf("%s: %w", name, ErrMissingRequiredParam)
|
||||||
}
|
}
|
||||||
|
|
||||||
// optional
|
|
||||||
if !exist {
|
if !exist {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// fail on invalid type
|
|
||||||
cast, valid := param.Validator(value)
|
cast, valid := param.Validator(value)
|
||||||
if !valid {
|
if !valid {
|
||||||
return fmt.Errorf("%s: %w", name, ErrInvalidType)
|
return fmt.Errorf("%s: %w", name, ErrInvalidType)
|
||||||
}
|
}
|
||||||
|
|
||||||
// store cast value
|
|
||||||
i.Data[param.Rename] = cast
|
i.Data[param.Rename] = cast
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -178,8 +149,7 @@ func (i *Set) parseJSON(req http.Request) error {
|
||||||
|
|
||||||
// parseUrlencoded parses urlencoded from the request body inside 'Form'
|
// parseUrlencoded parses urlencoded from the request body inside 'Form'
|
||||||
// and 'Set'
|
// and 'Set'
|
||||||
func (i *Set) parseUrlencoded(req http.Request) error {
|
func (i *T) parseUrlencoded(req http.Request) error {
|
||||||
// use http.Request interface
|
|
||||||
if err := req.ParseForm(); err != nil {
|
if err := req.ParseForm(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -187,26 +157,19 @@ func (i *Set) parseUrlencoded(req http.Request) error {
|
||||||
for name, param := range i.service.Form {
|
for name, param := range i.service.Form {
|
||||||
value, exist := req.PostForm[name]
|
value, exist := req.PostForm[name]
|
||||||
|
|
||||||
// fail on missing required
|
|
||||||
if !exist && !param.Optional {
|
if !exist && !param.Optional {
|
||||||
return fmt.Errorf("%s: %w", name, ErrMissingRequiredParam)
|
return fmt.Errorf("%s: %w", name, ErrMissingRequiredParam)
|
||||||
}
|
}
|
||||||
|
|
||||||
// optional
|
|
||||||
if !exist {
|
if !exist {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse parameter
|
|
||||||
parsed := parseParameter(value)
|
parsed := parseParameter(value)
|
||||||
|
|
||||||
// check type
|
|
||||||
cast, valid := param.Validator(parsed)
|
cast, valid := param.Validator(parsed)
|
||||||
if !valid {
|
if !valid {
|
||||||
return fmt.Errorf("%s: %w", name, ErrInvalidType)
|
return fmt.Errorf("%s: %w", name, ErrInvalidType)
|
||||||
}
|
}
|
||||||
|
|
||||||
// store cast value
|
|
||||||
i.Data[param.Rename] = cast
|
i.Data[param.Rename] = cast
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -215,46 +178,37 @@ func (i *Set) parseUrlencoded(req http.Request) error {
|
||||||
|
|
||||||
// parseMultipart parses multi-part from the request body inside 'Form'
|
// parseMultipart parses multi-part from the request body inside 'Form'
|
||||||
// and 'Set'
|
// and 'Set'
|
||||||
func (i *Set) parseMultipart(req http.Request) error {
|
func (i *T) parseMultipart(req http.Request) error {
|
||||||
|
|
||||||
// 1. create reader
|
|
||||||
boundary := req.Header.Get("Content-Type")[len("multipart/form-data; boundary="):]
|
boundary := req.Header.Get("Content-Type")[len("multipart/form-data; boundary="):]
|
||||||
mpr, err := multipart.NewReader(req.Body, boundary)
|
mpr, err := multipart.NewReader(req.Body, boundary)
|
||||||
if err != nil {
|
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. parse multipart
|
err = mpr.Parse()
|
||||||
if err = mpr.Parse(); err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s: %w", err, ErrInvalidMultipart)
|
return fmt.Errorf("%s: %w", err, ErrInvalidMultipart)
|
||||||
}
|
}
|
||||||
|
|
||||||
for name, param := range i.service.Form {
|
for name, param := range i.service.Form {
|
||||||
component, exist := mpr.Data[name]
|
component, exist := mpr.Data[name]
|
||||||
|
|
||||||
// fail on missing required
|
|
||||||
if !exist && !param.Optional {
|
if !exist && !param.Optional {
|
||||||
return fmt.Errorf("%s: %w", name, ErrMissingRequiredParam)
|
return fmt.Errorf("%s: %w", name, ErrMissingRequiredParam)
|
||||||
}
|
}
|
||||||
|
|
||||||
// optional
|
|
||||||
if !exist {
|
if !exist {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse parameter
|
|
||||||
parsed := parseParameter(string(component.Data))
|
parsed := parseParameter(string(component.Data))
|
||||||
|
|
||||||
// fail on invalid type
|
|
||||||
cast, valid := param.Validator(parsed)
|
cast, valid := param.Validator(parsed)
|
||||||
if !valid {
|
if !valid {
|
||||||
return fmt.Errorf("%s: %w", name, ErrInvalidType)
|
return fmt.Errorf("%s: %w", name, ErrInvalidType)
|
||||||
}
|
}
|
||||||
|
|
||||||
// store cast value
|
|
||||||
i.Data[param.Rename] = cast
|
i.Data[param.Rename] = cast
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -266,58 +220,47 @@ func (i *Set) parseMultipart(req http.Request) error {
|
||||||
// - []string : return array of json elements
|
// - []string : return array of json elements
|
||||||
// - string : return json if valid, else return raw string
|
// - string : return json if valid, else return raw string
|
||||||
func parseParameter(data interface{}) interface{} {
|
func parseParameter(data interface{}) interface{} {
|
||||||
dtype := reflect.TypeOf(data)
|
rt := reflect.TypeOf(data)
|
||||||
dvalue := reflect.ValueOf(data)
|
rv := reflect.ValueOf(data)
|
||||||
|
|
||||||
switch dtype.Kind() {
|
switch rt.Kind() {
|
||||||
|
|
||||||
/* (1) []string -> recursive */
|
// []string -> recursive
|
||||||
case reflect.Slice:
|
case reflect.Slice:
|
||||||
|
if rv.Len() == 0 {
|
||||||
// 1. ignore empty
|
|
||||||
if dvalue.Len() == 0 {
|
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. parse each element recursively
|
slice := make([]interface{}, rv.Len())
|
||||||
result := make([]interface{}, dvalue.Len())
|
for i, l := 0, rv.Len(); i < l; i++ {
|
||||||
|
element := rv.Index(i)
|
||||||
for i, l := 0, dvalue.Len(); i < l; i++ {
|
slice[i] = parseParameter(element.Interface())
|
||||||
element := dvalue.Index(i)
|
|
||||||
result[i] = parseParameter(element.Interface())
|
|
||||||
}
|
}
|
||||||
return result
|
return slice
|
||||||
|
|
||||||
/* (2) string -> parse */
|
// string -> parse as json
|
||||||
|
// keep as string if invalid json
|
||||||
case reflect.String:
|
case reflect.String:
|
||||||
|
var cast interface{}
|
||||||
// build json wrapper
|
wrapper := fmt.Sprintf("{\"wrapped\":%s}", rv.String())
|
||||||
wrapper := fmt.Sprintf("{\"wrapped\":%s}", dvalue.String())
|
err := json.Unmarshal([]byte(wrapper), &cast)
|
||||||
|
|
||||||
// try to parse as json
|
|
||||||
var result interface{}
|
|
||||||
err := json.Unmarshal([]byte(wrapper), &result)
|
|
||||||
|
|
||||||
// return if success
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return dvalue.String()
|
return rv.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
mapval, ok := result.(map[string]interface{})
|
mapval, ok := cast.(map[string]interface{})
|
||||||
if !ok {
|
if !ok {
|
||||||
return dvalue.String()
|
return rv.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
wrapped, ok := mapval["wrapped"]
|
wrapped, ok := mapval["wrapped"]
|
||||||
if !ok {
|
if !ok {
|
||||||
return dvalue.String()
|
return rv.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
return wrapped
|
return wrapped
|
||||||
|
|
||||||
|
// any type -> unchanged
|
||||||
|
default:
|
||||||
|
return rv.Interface()
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (3) NIL if unknown type */
|
|
||||||
return dvalue.Interface()
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -131,7 +131,7 @@ func TestStoreWithUri(t *testing.T) {
|
||||||
store := New(service)
|
store := New(service)
|
||||||
|
|
||||||
req := httptest.NewRequest(http.MethodGet, "http://host.com"+test.URI, nil)
|
req := httptest.NewRequest(http.MethodGet, "http://host.com"+test.URI, nil)
|
||||||
err := store.ExtractURI(*req)
|
err := store.GetURI(*req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if test.Err != nil {
|
if test.Err != nil {
|
||||||
if !errors.Is(err, test.Err) {
|
if !errors.Is(err, test.Err) {
|
||||||
|
@ -242,7 +242,7 @@ func TestExtractQuery(t *testing.T) {
|
||||||
store := New(getServiceWithQuery(test.ServiceParam...))
|
store := New(getServiceWithQuery(test.ServiceParam...))
|
||||||
|
|
||||||
req := httptest.NewRequest(http.MethodGet, fmt.Sprintf("http://host.com?%s", test.Query), nil)
|
req := httptest.NewRequest(http.MethodGet, fmt.Sprintf("http://host.com?%s", test.Query), nil)
|
||||||
err := store.ExtractQuery(*req)
|
err := store.GetQuery(*req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if test.Err != nil {
|
if test.Err != nil {
|
||||||
if !errors.Is(err, test.Err) {
|
if !errors.Is(err, test.Err) {
|
||||||
|
@ -324,7 +324,7 @@ func TestStoreWithUrlEncodedFormParseError(t *testing.T) {
|
||||||
|
|
||||||
// defer req.Body.Close()
|
// defer req.Body.Close()
|
||||||
store := New(nil)
|
store := New(nil)
|
||||||
err := store.ExtractForm(*req)
|
err := store.GetForm(*req)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Errorf("expected malformed urlencoded to have FailNow being parsed (got %d elements)", len(store.Data))
|
t.Errorf("expected malformed urlencoded to have FailNow being parsed (got %d elements)", len(store.Data))
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
|
@ -420,7 +420,7 @@ func TestExtractFormUrlEncoded(t *testing.T) {
|
||||||
defer req.Body.Close()
|
defer req.Body.Close()
|
||||||
|
|
||||||
store := New(getServiceWithForm(test.ServiceParams...))
|
store := New(getServiceWithForm(test.ServiceParams...))
|
||||||
err := store.ExtractForm(*req)
|
err := store.GetForm(*req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if test.Err != nil {
|
if test.Err != nil {
|
||||||
if !errors.Is(err, test.Err) {
|
if !errors.Is(err, test.Err) {
|
||||||
|
@ -563,7 +563,7 @@ func TestJsonParameters(t *testing.T) {
|
||||||
defer req.Body.Close()
|
defer req.Body.Close()
|
||||||
store := New(getServiceWithForm(test.ServiceParams...))
|
store := New(getServiceWithForm(test.ServiceParams...))
|
||||||
|
|
||||||
err := store.ExtractForm(*req)
|
err := store.GetForm(*req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if test.Err != nil {
|
if test.Err != nil {
|
||||||
if !errors.Is(err, test.Err) {
|
if !errors.Is(err, test.Err) {
|
||||||
|
@ -720,7 +720,7 @@ x
|
||||||
defer req.Body.Close()
|
defer req.Body.Close()
|
||||||
store := New(getServiceWithForm(test.ServiceParams...))
|
store := New(getServiceWithForm(test.ServiceParams...))
|
||||||
|
|
||||||
err := store.ExtractForm(*req)
|
err := store.GetForm(*req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if test.Err != nil {
|
if test.Err != nil {
|
||||||
if !errors.Is(err, test.Err) {
|
if !errors.Is(err, test.Err) {
|
||||||
|
|
|
@ -76,23 +76,23 @@ func errorHandler(err api.Error) http.HandlerFunc {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func extractRequestData(service *config.Service, req http.Request) (*reqdata.Set, error) {
|
func extractRequestData(service *config.Service, req http.Request) (*reqdata.T, error) {
|
||||||
dataset := reqdata.New(service)
|
dataset := reqdata.New(service)
|
||||||
|
|
||||||
// 3. extract URI data
|
// 3. extract URI data
|
||||||
err := dataset.ExtractURI(req)
|
err := dataset.GetURI(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. extract query data
|
// 4. extract query data
|
||||||
err = dataset.ExtractQuery(req)
|
err = dataset.GetQuery(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. extract form/json data
|
// 5. extract form/json data
|
||||||
err = dataset.ExtractForm(req)
|
err = dataset.GetForm(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue