diff --git a/internal/reqdata/set.go b/internal/reqdata/set.go index 3930927..c8aac46 100644 --- a/internal/reqdata/set.go +++ b/internal/reqdata/set.go @@ -13,33 +13,29 @@ import ( "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) -// - GET (default url data) +// - GET (standard url data) // - POST (from json, form-data, url-encoded) // - 'application/json' => key-value pair is parsed as json into the map // - 'application/x-www-form-urlencoded' => standard parameters as QUERY parameters // - 'multipart/form-data' => parse form-data format -type Set struct { +type T struct { 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. -func New(service *config.Service) *Set { - return &Set{ +func New(service *config.Service) *T { + return &T{ service: service, - Data: make(map[string]interface{}), + Data: map[string]interface{}{}, } } -// ExtractURI fills 'Set' with creating pointers inside 'Url' -func (i *Set) ExtractURI(req http.Request) error { +// GetURI parameters +func (i *T) GetURI(req http.Request) error { uriparts := config.SplitURL(req.URL.RequestURI()) 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) } - // parse parameter parsed := parseParameter(value) - - // check type cast, valid := capture.Ref.Validator(parsed) if !valid { return fmt.Errorf("%s: %w", capture.Name, ErrInvalidType) } - - // store cast value in 'Set' i.Data[capture.Ref.Rename] = cast } return nil } -// ExtractQuery data from the url query parameters -func (i *Set) ExtractQuery(req http.Request) error { +// GetQuery data from the url query parameters +func (i *T) GetQuery(req http.Request) error { query := req.URL.Query() for name, param := range i.service.Query { value, exist := query[name] - // fail on missing required if !exist && !param.Optional { return fmt.Errorf("%s: %w", name, ErrMissingRequiredParam) } - // optional if !exist { continue } - // parse parameter parsed := parseParameter(value) - - // check type cast, valid := param.Validator(parsed) if !valid { return fmt.Errorf("%s: %w", name, ErrInvalidType) } - - // store cast value i.Data[param.Rename] = cast } return nil } -// ExtractForm data from request -// +// GetForm parameters the from request // - parse 'form-data' if not supported for non-POST requests // - parse 'x-www-form-urlencoded' // - parse 'application/json' -func (i *Set) ExtractForm(req http.Request) error { - - // ignore GET method +func (i *T) GetForm(req http.Request) error { if req.Method == http.MethodGet { return nil } - contentType := req.Header.Get("Content-Type") - - // parse json - if strings.HasPrefix(contentType, "application/json") { + ct := req.Header.Get("Content-Type") + switch { + case strings.HasPrefix(ct, "application/json"): return i.parseJSON(req) - } - // parse urlencoded - if strings.HasPrefix(contentType, "application/x-www-form-urlencoded") { + case strings.HasPrefix(ct, "application/x-www-form-urlencoded"): return i.parseUrlencoded(req) - } - // parse multipart - if strings.HasPrefix(contentType, "multipart/form-data; boundary=") { + case strings.HasPrefix(ct, "multipart/form-data; boundary="): return i.parseMultipart(req) - } - // nothing to parse - return nil + default: + return nil + } } // parseJSON parses JSON from the request body inside 'Form' // and 'Set' -func (i *Set) parseJSON(req http.Request) error { - - parsed := make(map[string]interface{}, 0) +func (i *T) parseJSON(req http.Request) error { + var parsed map[string]interface{} decoder := json.NewDecoder(req.Body) - if err := decoder.Decode(&parsed); err != nil { - if err == io.EOF { - return nil - } + err := decoder.Decode(&parsed) + if err == io.EOF { + return nil + } + if err != nil { return fmt.Errorf("%s: %w", err, ErrInvalidJSON) } for name, param := range i.service.Form { value, exist := parsed[name] - // fail on missing required if !exist && !param.Optional { return fmt.Errorf("%s: %w", name, ErrMissingRequiredParam) } - // optional if !exist { continue } - // fail on invalid type cast, valid := param.Validator(value) if !valid { return fmt.Errorf("%s: %w", name, ErrInvalidType) } - - // store cast value 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' // and 'Set' -func (i *Set) parseUrlencoded(req http.Request) error { - // use http.Request interface +func (i *T) parseUrlencoded(req http.Request) error { if err := req.ParseForm(); err != nil { return err } @@ -187,26 +157,19 @@ func (i *Set) parseUrlencoded(req http.Request) error { for name, param := range i.service.Form { value, exist := req.PostForm[name] - // fail on missing required if !exist && !param.Optional { return fmt.Errorf("%s: %w", name, ErrMissingRequiredParam) } - // optional if !exist { continue } - // parse parameter parsed := parseParameter(value) - - // check type cast, valid := param.Validator(parsed) if !valid { return fmt.Errorf("%s: %w", name, ErrInvalidType) } - - // store cast value 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' // and 'Set' -func (i *Set) parseMultipart(req http.Request) error { - - // 1. create reader +func (i *T) parseMultipart(req http.Request) error { boundary := req.Header.Get("Content-Type")[len("multipart/form-data; boundary="):] mpr, err := multipart.NewReader(req.Body, boundary) + if err == io.EOF { + return nil + } if err != nil { - if err == io.EOF { - return nil - } return err } - // 2. parse multipart - if err = mpr.Parse(); err != nil { + err = mpr.Parse() + if err != nil { return fmt.Errorf("%s: %w", err, ErrInvalidMultipart) } for name, param := range i.service.Form { component, exist := mpr.Data[name] - // fail on missing required if !exist && !param.Optional { return fmt.Errorf("%s: %w", name, ErrMissingRequiredParam) } - // optional if !exist { continue } - // parse parameter parsed := parseParameter(string(component.Data)) - - // fail on invalid type cast, valid := param.Validator(parsed) if !valid { return fmt.Errorf("%s: %w", name, ErrInvalidType) } - - // store cast value 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 json if valid, else return raw string func parseParameter(data interface{}) interface{} { - dtype := reflect.TypeOf(data) - dvalue := reflect.ValueOf(data) + rt := reflect.TypeOf(data) + rv := reflect.ValueOf(data) - switch dtype.Kind() { + switch rt.Kind() { - /* (1) []string -> recursive */ + // []string -> recursive case reflect.Slice: - - // 1. ignore empty - if dvalue.Len() == 0 { + if rv.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()) + slice := make([]interface{}, rv.Len()) + for i, l := 0, rv.Len(); i < l; i++ { + element := rv.Index(i) + slice[i] = parseParameter(element.Interface()) } - return result + return slice - /* (2) string -> parse */ + // string -> parse as json + // keep as string if invalid json 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 + var cast interface{} + wrapper := fmt.Sprintf("{\"wrapped\":%s}", rv.String()) + err := json.Unmarshal([]byte(wrapper), &cast) if err != nil { - return dvalue.String() + return rv.String() } - mapval, ok := result.(map[string]interface{}) + mapval, ok := cast.(map[string]interface{}) if !ok { - return dvalue.String() + return rv.String() } wrapped, ok := mapval["wrapped"] if !ok { - return dvalue.String() + return rv.String() } - return wrapped + // any type -> unchanged + default: + return rv.Interface() } - - /* (3) NIL if unknown type */ - return dvalue.Interface() - } diff --git a/internal/reqdata/set_test.go b/internal/reqdata/set_test.go index 7496327..771d863 100644 --- a/internal/reqdata/set_test.go +++ b/internal/reqdata/set_test.go @@ -131,7 +131,7 @@ func TestStoreWithUri(t *testing.T) { store := New(service) req := httptest.NewRequest(http.MethodGet, "http://host.com"+test.URI, nil) - err := store.ExtractURI(*req) + err := store.GetURI(*req) if err != nil { if test.Err != nil { if !errors.Is(err, test.Err) { @@ -242,7 +242,7 @@ func TestExtractQuery(t *testing.T) { store := New(getServiceWithQuery(test.ServiceParam...)) 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 test.Err != nil { if !errors.Is(err, test.Err) { @@ -324,7 +324,7 @@ func TestStoreWithUrlEncodedFormParseError(t *testing.T) { // defer req.Body.Close() store := New(nil) - err := store.ExtractForm(*req) + 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() @@ -420,7 +420,7 @@ func TestExtractFormUrlEncoded(t *testing.T) { defer req.Body.Close() store := New(getServiceWithForm(test.ServiceParams...)) - err := store.ExtractForm(*req) + err := store.GetForm(*req) if err != nil { if test.Err != nil { if !errors.Is(err, test.Err) { @@ -563,7 +563,7 @@ func TestJsonParameters(t *testing.T) { defer req.Body.Close() store := New(getServiceWithForm(test.ServiceParams...)) - err := store.ExtractForm(*req) + err := store.GetForm(*req) if err != nil { if test.Err != nil { if !errors.Is(err, test.Err) { @@ -720,7 +720,7 @@ x defer req.Body.Close() store := New(getServiceWithForm(test.ServiceParams...)) - err := store.ExtractForm(*req) + err := store.GetForm(*req) if err != nil { if test.Err != nil { if !errors.Is(err, test.Err) { diff --git a/server.go b/server.go index 15fd030..12a3fc6 100644 --- a/server.go +++ b/server.go @@ -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) // 3. extract URI data - err := dataset.ExtractURI(req) + err := dataset.GetURI(req) if err != nil { return nil, err } // 4. extract query data - err = dataset.ExtractQuery(req) + err = dataset.GetQuery(req) if err != nil { return nil, err } // 5. extract form/json data - err = dataset.ExtractForm(req) + err = dataset.GetForm(req) if err != nil { return nil, err }