285 lines
6.3 KiB
Go
285 lines
6.3 KiB
Go
package reqdata
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"reflect"
|
|
|
|
"github.com/xdrm-io/aicra/internal/config"
|
|
"github.com/xdrm-io/aicra/internal/multipart"
|
|
|
|
"net/http"
|
|
"strings"
|
|
)
|
|
|
|
// T represents all data that can be caught from an http request for a specific
|
|
// configuration Service; it features:
|
|
// - URI (from the URI)
|
|
// - 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 T struct {
|
|
service *config.Service
|
|
Data map[string]interface{}
|
|
}
|
|
|
|
// New creates a new empty store.
|
|
func New(service *config.Service) *T {
|
|
return &T{
|
|
service: service,
|
|
Data: map[string]interface{}{},
|
|
}
|
|
}
|
|
|
|
// GetURI parameters
|
|
func (i *T) GetURI(req http.Request) error {
|
|
uriparts := config.SplitURL(req.URL.RequestURI())
|
|
|
|
for _, capture := range i.service.Captures {
|
|
// out of range
|
|
if capture.Index > len(uriparts)-1 {
|
|
return fmt.Errorf("%s: %w", capture.Name, ErrMissingURIParameter)
|
|
}
|
|
value := uriparts[capture.Index]
|
|
|
|
// should not happen
|
|
if capture.Ref == nil {
|
|
return fmt.Errorf("%s: %w", capture.Name, ErrUnknownType)
|
|
}
|
|
|
|
parsed := parseParameter(value)
|
|
cast, valid := capture.Ref.Validator(parsed)
|
|
if !valid {
|
|
return fmt.Errorf("%s: %w", capture.Name, ErrInvalidType)
|
|
}
|
|
i.Data[capture.Ref.Rename] = cast
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// 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 {
|
|
values, exist := query[name]
|
|
|
|
if !exist {
|
|
if !param.Optional {
|
|
return fmt.Errorf("%s: %w", name, ErrMissingRequiredParam)
|
|
}
|
|
continue
|
|
}
|
|
|
|
var parsed interface{}
|
|
|
|
// consider element instead of slice or elements when only 1
|
|
if len(values) == 1 {
|
|
parsed = parseParameter(values[0])
|
|
} else { // consider slice
|
|
parsed = parseParameter(values)
|
|
}
|
|
|
|
cast, valid := param.Validator(parsed)
|
|
if !valid {
|
|
return fmt.Errorf("%s: %w", name, ErrInvalidType)
|
|
}
|
|
i.Data[param.Rename] = cast
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// 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 *T) GetForm(req http.Request) error {
|
|
if req.Method == http.MethodGet {
|
|
return nil
|
|
}
|
|
|
|
ct := req.Header.Get("Content-Type")
|
|
switch {
|
|
case strings.HasPrefix(ct, "application/json"):
|
|
return i.parseJSON(req)
|
|
|
|
case strings.HasPrefix(ct, "application/x-www-form-urlencoded"):
|
|
return i.parseUrlencoded(req)
|
|
|
|
case strings.HasPrefix(ct, "multipart/form-data; boundary="):
|
|
return i.parseMultipart(req)
|
|
|
|
default:
|
|
|
|
// fail on at least 1 mandatory form param when there is no body
|
|
for name, param := range i.service.Form {
|
|
if !param.Optional {
|
|
return fmt.Errorf("%s: %w", name, ErrMissingRequiredParam)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// parseJSON parses JSON from the request body inside 'Form'
|
|
// and 'Set'
|
|
func (i *T) parseJSON(req http.Request) error {
|
|
var parsed map[string]interface{}
|
|
|
|
decoder := json.NewDecoder(req.Body)
|
|
err := decoder.Decode(&parsed)
|
|
if err != io.EOF {
|
|
if err != nil {
|
|
return fmt.Errorf("%s: %w", err, ErrInvalidJSON)
|
|
}
|
|
}
|
|
|
|
for name, param := range i.service.Form {
|
|
value, exist := parsed[name]
|
|
|
|
if !exist {
|
|
if !param.Optional {
|
|
return fmt.Errorf("%s: %w", name, ErrMissingRequiredParam)
|
|
}
|
|
continue
|
|
}
|
|
|
|
cast, valid := param.Validator(value)
|
|
if !valid {
|
|
return fmt.Errorf("%s: %w", name, ErrInvalidType)
|
|
}
|
|
i.Data[param.Rename] = cast
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// parseUrlencoded parses urlencoded from the request body inside 'Form'
|
|
// and 'Set'
|
|
func (i *T) parseUrlencoded(req http.Request) error {
|
|
if err := req.ParseForm(); err != nil {
|
|
return err
|
|
}
|
|
|
|
for name, param := range i.service.Form {
|
|
values, exist := req.PostForm[name]
|
|
|
|
if !exist {
|
|
if !param.Optional {
|
|
return fmt.Errorf("%s: %w", name, ErrMissingRequiredParam)
|
|
}
|
|
continue
|
|
}
|
|
|
|
var parsed interface{}
|
|
|
|
// consider element instead of slice or elements when only 1
|
|
if len(values) == 1 {
|
|
parsed = parseParameter(values[0])
|
|
} else { // consider slice
|
|
parsed = parseParameter(values)
|
|
}
|
|
|
|
cast, valid := param.Validator(parsed)
|
|
if !valid {
|
|
return fmt.Errorf("%s: %w", name, ErrInvalidType)
|
|
}
|
|
i.Data[param.Rename] = cast
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// parseMultipart parses multi-part from the request body inside 'Form'
|
|
// and 'Set'
|
|
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 {
|
|
return err
|
|
}
|
|
|
|
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]
|
|
|
|
if !exist {
|
|
if !param.Optional {
|
|
return fmt.Errorf("%s: %w", name, ErrMissingRequiredParam)
|
|
}
|
|
continue
|
|
}
|
|
|
|
parsed := parseParameter(string(component.Data))
|
|
cast, valid := param.Validator(parsed)
|
|
if !valid {
|
|
return fmt.Errorf("%s: %w", name, ErrInvalidType)
|
|
}
|
|
i.Data[param.Rename] = cast
|
|
}
|
|
|
|
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{} {
|
|
rt := reflect.TypeOf(data)
|
|
rv := reflect.ValueOf(data)
|
|
|
|
switch rt.Kind() {
|
|
|
|
// []string -> recursive
|
|
case reflect.Slice:
|
|
if rv.Len() == 0 {
|
|
return data
|
|
}
|
|
|
|
slice := make([]interface{}, rv.Len())
|
|
for i, l := 0, rv.Len(); i < l; i++ {
|
|
element := rv.Index(i)
|
|
slice[i] = parseParameter(element.Interface())
|
|
}
|
|
return slice
|
|
|
|
// string -> parse as json
|
|
// keep as string if invalid json
|
|
case reflect.String:
|
|
var cast interface{}
|
|
wrapper := fmt.Sprintf("{\"wrapped\":%s}", rv.String())
|
|
err := json.Unmarshal([]byte(wrapper), &cast)
|
|
if err != nil {
|
|
return rv.String()
|
|
}
|
|
|
|
mapval, ok := cast.(map[string]interface{})
|
|
if !ok {
|
|
return rv.String()
|
|
}
|
|
|
|
wrapped, ok := mapval["wrapped"]
|
|
if !ok {
|
|
return rv.String()
|
|
}
|
|
return wrapped
|
|
|
|
// any type -> unchanged
|
|
default:
|
|
return rv.Interface()
|
|
}
|
|
}
|