mirror of
https://github.com/SecurityBrewery/catalyst.git
synced 2025-12-06 23:32:47 +01:00
193 lines
4.3 KiB
Go
Executable File
193 lines
4.3 KiB
Go
Executable File
package api
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"strconv"
|
|
|
|
"github.com/go-chi/chi/v5"
|
|
"github.com/xeipuuv/gojsonschema"
|
|
)
|
|
|
|
type HTTPError struct {
|
|
Status int
|
|
Internal error
|
|
}
|
|
|
|
func (e *HTTPError) Error() string {
|
|
return fmt.Sprintf("HTTPError(%d): %s", e.Status, e.Internal)
|
|
}
|
|
|
|
func (e *HTTPError) Unwrap() error {
|
|
return e.Internal
|
|
}
|
|
|
|
func parseURLInt64(r *http.Request, s string) (int64, error) {
|
|
i, err := strconv.ParseInt(chi.URLParam(r, s), 10, 64)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("%w", &HTTPError{http.StatusUnprocessableEntity, err})
|
|
}
|
|
return i, nil
|
|
}
|
|
|
|
func parseURLInt(r *http.Request, s string) (int, error) {
|
|
i, err := strconv.Atoi(chi.URLParam(r, s))
|
|
if err != nil {
|
|
return 0, fmt.Errorf("%w", &HTTPError{http.StatusUnprocessableEntity, err})
|
|
}
|
|
return i, nil
|
|
}
|
|
|
|
func parseQueryInt(r *http.Request, s string) (int, error) {
|
|
i, err := strconv.Atoi(r.URL.Query().Get(s))
|
|
if err != nil {
|
|
return 0, fmt.Errorf("%w", &HTTPError{http.StatusUnprocessableEntity, err})
|
|
}
|
|
return i, nil
|
|
}
|
|
|
|
func parseQueryBool(r *http.Request, s string) (bool, error) {
|
|
b, err := strconv.ParseBool(r.URL.Query().Get(s))
|
|
if err != nil {
|
|
return false, fmt.Errorf("%w", &HTTPError{http.StatusUnprocessableEntity, err})
|
|
}
|
|
return b, nil
|
|
}
|
|
|
|
func parseQueryStringArray(r *http.Request, key string) ([]string, error) {
|
|
stringArray, ok := r.URL.Query()[key]
|
|
if !ok {
|
|
return nil, nil
|
|
}
|
|
return removeEmpty(stringArray), nil
|
|
}
|
|
|
|
func removeEmpty(l []string) []string {
|
|
var stringArray []string
|
|
for _, s := range l {
|
|
if s == "" {
|
|
continue
|
|
}
|
|
stringArray = append(stringArray, s)
|
|
}
|
|
|
|
return stringArray
|
|
}
|
|
|
|
func parseQueryBoolArray(r *http.Request, key string) ([]bool, error) {
|
|
stringArray, ok := r.URL.Query()[key]
|
|
if !ok {
|
|
return nil, nil
|
|
}
|
|
var boolArray []bool
|
|
for _, s := range stringArray {
|
|
if s == "" {
|
|
continue
|
|
}
|
|
b, err := strconv.ParseBool(s)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("%w", &HTTPError{http.StatusUnprocessableEntity, err})
|
|
}
|
|
boolArray = append(boolArray, b)
|
|
}
|
|
|
|
return boolArray, nil
|
|
}
|
|
|
|
func parseQueryOptionalInt(r *http.Request, key string) (*int, error) {
|
|
s := r.URL.Query().Get(key)
|
|
if s == "" {
|
|
return nil, nil
|
|
}
|
|
|
|
i, err := strconv.Atoi(s)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("%w", &HTTPError{http.StatusUnprocessableEntity, err})
|
|
}
|
|
return &i, nil
|
|
}
|
|
|
|
func parseQueryOptionalStringArray(r *http.Request, key string) ([]string, error) {
|
|
return parseQueryStringArray(r, key)
|
|
}
|
|
|
|
func parseQueryOptionalBoolArray(r *http.Request, key string) ([]bool, error) {
|
|
return parseQueryBoolArray(r, key)
|
|
}
|
|
|
|
func parseBody(b []byte, i any) error {
|
|
dec := json.NewDecoder(bytes.NewBuffer(b))
|
|
err := dec.Decode(i)
|
|
if err != nil {
|
|
return fmt.Errorf("%w", &HTTPError{http.StatusUnprocessableEntity, err})
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func JSONError(w http.ResponseWriter, err error) {
|
|
JSONErrorStatus(w, http.StatusInternalServerError, err)
|
|
}
|
|
|
|
func JSONErrorStatus(w http.ResponseWriter, status int, err error) {
|
|
w.WriteHeader(status)
|
|
b, _ := json.Marshal(map[string]string{"error": err.Error()})
|
|
w.Write(b)
|
|
}
|
|
|
|
func response(w http.ResponseWriter, v any, err error) {
|
|
if err != nil {
|
|
var httpError *HTTPError
|
|
if errors.As(err, &httpError) {
|
|
JSONErrorStatus(w, httpError.Status, httpError.Internal)
|
|
return
|
|
}
|
|
JSONError(w, err)
|
|
return
|
|
}
|
|
|
|
if v == nil {
|
|
w.WriteHeader(http.StatusNoContent)
|
|
return
|
|
}
|
|
w.WriteHeader(http.StatusOK)
|
|
b, _ := json.Marshal(v)
|
|
w.Write(b)
|
|
}
|
|
|
|
func validateSchema(body []byte, schema *gojsonschema.Schema, w http.ResponseWriter) bool {
|
|
jl := gojsonschema.NewBytesLoader(body)
|
|
validationResult, err := schema.Validate(jl)
|
|
if err != nil {
|
|
JSONError(w, err)
|
|
return true
|
|
}
|
|
if !validationResult.Valid() {
|
|
w.WriteHeader(http.StatusUnprocessableEntity)
|
|
|
|
var validationErrors []string
|
|
for _, valdiationError := range validationResult.Errors() {
|
|
validationErrors = append(validationErrors, valdiationError.String())
|
|
}
|
|
|
|
b, _ := json.Marshal(map[string]any{"error": "wrong input", "errors": validationErrors})
|
|
w.Write(b)
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func NilMiddleware() func(next http.Handler) http.Handler {
|
|
return func(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
next.ServeHTTP(w, r)
|
|
})
|
|
}
|
|
}
|
|
|
|
func IgnoreRoles(_ []string) func(next http.Handler) http.Handler {
|
|
return NilMiddleware()
|
|
}
|