mirror of
https://github.com/hatchet-dev/hatchet.git
synced 2025-12-30 13:19:44 -06:00
150 lines
4.0 KiB
Go
150 lines
4.0 KiB
Go
package webhookutils
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/gorilla/schema"
|
|
|
|
hatcheterrors "github.com/hatchet-dev/hatchet/pkg/errors"
|
|
)
|
|
|
|
// 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) error
|
|
|
|
// DecodeQueryOnly is Decode but only looks at the query parameters
|
|
DecodeQueryOnly(s interface{}, r *http.Request) error
|
|
}
|
|
|
|
// 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,
|
|
) error {
|
|
if r == nil || r.URL == nil {
|
|
return hatcheterrors.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
|
|
}
|
|
|
|
// Decode reads the request and populates the target request object.
|
|
func (d *DefaultDecoder) DecodeQueryOnly(
|
|
s interface{},
|
|
r *http.Request,
|
|
) error {
|
|
if r == nil || r.URL == nil {
|
|
return hatcheterrors.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)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func requestErrorFromJSONErr(err error) error {
|
|
var syntaxErr *json.SyntaxError
|
|
var typeErr *json.UnmarshalTypeError
|
|
var clientErr string
|
|
|
|
if errors.As(err, &syntaxErr) {
|
|
clientErr = fmt.Sprintf("JSON syntax error at character %d", syntaxErr.Offset)
|
|
} else if errors.As(err, &typeErr) {
|
|
clientErr = fmt.Sprintf("Invalid type for body param %s: expected %s, got %s", typeErr.Field, typeErr.Type.Kind().String(), typeErr.Value)
|
|
} else {
|
|
return hatcheterrors.NewError(
|
|
400,
|
|
"Bad Request",
|
|
"Could not parse JSON request",
|
|
"",
|
|
)
|
|
}
|
|
|
|
return hatcheterrors.NewError(
|
|
400,
|
|
"Bad Request",
|
|
clientErr,
|
|
"",
|
|
)
|
|
}
|
|
|
|
func requestErrorFromSchemaErr(err error) error {
|
|
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.Sprintf(strings.Join(resStrArr, ","))
|
|
|
|
return hatcheterrors.NewError(
|
|
400,
|
|
"Bad Request",
|
|
clientErr,
|
|
"",
|
|
)
|
|
}
|
|
|
|
// 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 hatcheterrors.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
|
|
}
|