rework reqdata api and remove redundant comments

This commit is contained in:
Adrien Marquès 2020-04-04 14:56:15 +02:00
parent 35ede5e266
commit 990bb86919
Signed by: xdrm-brackets
GPG Key ID: D75243CA236D825E
3 changed files with 68 additions and 125 deletions

View File

@ -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()
} }

View File

@ -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) {

View File

@ -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
} }