| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111 |
- package requestutils
- import (
- "encoding/json"
- "errors"
- "fmt"
- "io"
- "net/http"
- "strings"
- "github.com/gorilla/schema"
- "github.com/porter-dev/porter/api/server/shared/apierrors"
- )
- // Decoder populates a request form from the request body and URL.
- type Decoder interface {
- // Decode accepts a target struct, a reader for the request body, and a URL
- // for the request endpoint
- Decode(s interface{}, r *http.Request) apierrors.RequestError
- }
- // DefaultDecoder decodes the request body with `json` and the URL query params with gorilla/schema,
- type DefaultDecoder struct {
- // we set the schema.Decoder on the global (shared across all endpoints) decoder
- // because it caches metadata about structs, but does not cache values
- schemaDecoder *schema.Decoder
- }
- // NewDefaultDecoder returns an implementation of Decoder that uses `json` and `gorilla/schema`.
- func NewDefaultDecoder() Decoder {
- decoder := schema.NewDecoder()
- return &DefaultDecoder{decoder}
- }
- // Decode reads the request and populates the target request object.
- func (d *DefaultDecoder) Decode(
- s interface{},
- r *http.Request,
- ) (reqErr apierrors.RequestError) {
- if r == nil || r.URL == nil {
- return apierrors.NewErrInternal(fmt.Errorf("decode: request or request.URL cannot be nil"))
- }
- // read query values from URL and decode using schema library
- vals := r.URL.Query()
- if err := d.schemaDecoder.Decode(s, vals); err != nil {
- return requestErrorFromSchemaErr(err)
- }
- // decode into the request object
- // a nil body is not a fatal error
- if r.Body != nil {
- if err := json.NewDecoder(r.Body).Decode(s); err != nil && !errors.Is(err, io.EOF) {
- return requestErrorFromJSONErr(err)
- }
- }
- return nil
- }
- func requestErrorFromJSONErr(err error) apierrors.RequestError {
- var syntaxErr *json.SyntaxError
- var typeErr *json.UnmarshalTypeError
- var clientErr error
- if errors.As(err, &syntaxErr) {
- clientErr = fmt.Errorf("JSON syntax error at character %d", syntaxErr.Offset)
- } else if errors.As(err, &typeErr) {
- clientErr = fmt.Errorf("Invalid type for body param %s: expected %s, got %s", typeErr.Field, typeErr.Type.Kind().String(), typeErr.Value)
- } else {
- clientErr = fmt.Errorf("Could not parse JSON request")
- }
- return apierrors.NewErrPassThroughToClient(clientErr, http.StatusBadRequest)
- }
- func requestErrorFromSchemaErr(err error) apierrors.RequestError {
- if multiErr := (schema.MultiError{}); errors.As(err, &multiErr) {
- errMap := map[string]error(multiErr)
- resStrArr := make([]string, 0)
- for _, err := range errMap {
- resStrArr = append(resStrArr, readableStringFromSchemaErr(err))
- }
- clientErr := fmt.Errorf(strings.Join(resStrArr, ","))
- return apierrors.NewErrPassThroughToClient(clientErr, http.StatusBadRequest)
- }
- // if not castable to multi-error, this is likely a server-side error, such as the
- // passed struct being nil; thus, we throw an internal server error
- return apierrors.NewErrInternal(err)
- }
- func readableStringFromSchemaErr(err error) string {
- var str string
- if typeErr := (schema.ConversionError{}); errors.As(err, &typeErr) {
- str = fmt.Sprintf("Invalid type for query param %s: expected %s", typeErr.Key, typeErr.Type.Kind().String())
- } else if emptyFieldErr := (schema.EmptyFieldError{}); errors.As(err, &emptyFieldErr) {
- str = fmt.Sprintf("Query param %s cannot be empty", emptyFieldErr.Key)
- } else if unknownKeyErr := (schema.UnknownKeyError{}); errors.As(err, &unknownKeyErr) {
- str = fmt.Sprintf("Unknown query param %s", unknownKeyErr.Key)
- }
- return str
- }
|