Something went wrong. Try again.
Lightweight and easily self-hostable Git forge.
Something went wrong. Try again.
4.2 kB · 166 lines
Go
at bugfixes
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167package middleware
import ( "context" "encoding/json" "errors" "farga/lib" "fmt" "net/http" "net/url" "reflect" "strconv" "strings"
"github.com/go-playground/validator/v10")
var validate *validator.Validate = validator.New()
type contextKey[T any] struct{} // allows using types as context keys
func decodeForm[T any](values url.Values) (T, error) { var dst T
v := reflect.ValueOf(&dst).Elem() t := v.Type()
if t.Kind() != reflect.Struct { return dst, fmt.Errorf("T must be a struct") }
for i := 0; i < t.NumField(); i++ { field := t.Field(i)
name := field.Tag.Get("form") if name == "" { name = strings.ToLower(field.Name) }
raw := values.Get(name) if raw == "" { continue }
f := v.Field(i)
switch f.Kind() { case reflect.String: f.SetString(raw)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: n, err := strconv.ParseInt(raw, 10, f.Type().Bits()) if err != nil { return dst, fmt.Errorf("field %q: %w", name, err) } f.SetInt(n)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: n, err := strconv.ParseUint(raw, 10, f.Type().Bits()) if err != nil { return dst, fmt.Errorf("field %q: %w", name, err) } f.SetUint(n)
case reflect.Float32, reflect.Float64: n, err := strconv.ParseFloat(raw, f.Type().Bits()) if err != nil { return dst, fmt.Errorf("field %q: %w", name, err) } f.SetFloat(n)
case reflect.Bool: b, err := strconv.ParseBool(raw) if err != nil { return dst, fmt.Errorf("field %q: %w", name, err) } f.SetBool(b) } }
return dst, nil}
type ValidationError struct { Field string Message string}type ValidationErrors []ValidationError
// replaces the ugly default error with a custom and better one.func fieldErrorMessage(fe validator.FieldError) string { switch fe.Tag() { case "required": return fmt.Sprintf("%s is required", fe.Field()) case "email": return fmt.Sprintf("%s must be a valid email address", fe.Field()) case "min": return fmt.Sprintf("%s must be at least %s characters", fe.Field(), fe.Param()) case "max": return fmt.Sprintf("%s must be at most %s characters", fe.Field(), fe.Param()) case "len": return fmt.Sprintf("%s must be exactly %s characters", fe.Field(), fe.Param()) case "gte": return fmt.Sprintf("%s must be greater than or equal to %s", fe.Field(), fe.Param()) case "lte": return fmt.Sprintf("%s must be less than or equal to %s", fe.Field(), fe.Param()) default: return fe.Error() }}
// validates a given form and makes it available like form, _ := md.GetForm[(form)](r).func ValidateForm[form_type any](next http.HandlerFunc) http.HandlerFunc { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { next.ServeHTTP(w, r) return }
err := r.ParseForm() if err != nil { http.Error(w, "Bad form", http.StatusBadRequest) return }
form, err := decodeForm[form_type](r.Form) if err != nil { http.Error(w, "Invalid form", http.StatusBadRequest) // validation options might be wrong if this happens. return }
err = validate.Struct(form) if err != nil { var errs ValidationErrors if _, ok := errors.AsType[validator.ValidationErrors](err); ok { for _, err := range err.(validator.ValidationErrors) { valError := ValidationError{ Field: err.Field(), Message: fieldErrorMessage(err), } errs = append(errs, valError) } // http.Error(w, "Invalid form params: "+errs.Error(), http.StatusBadRequest) }
// encode errors as base64 marshalled, err := json.Marshal(errs) if err != nil { http.Error(w, "Bad request", http.StatusBadRequest) return }
http.Redirect(w, r, r.URL.Path+"?err="+lib.ValidationErrorKey+lib.Base64Encoder.EncodeToString(marshalled), http.StatusSeeOther) return }
ctx := context.WithValue(r.Context(), contextKey[form_type]{}, form) next.ServeHTTP(w, r.WithContext(ctx)) })}
// returns requested item + true if found, false if not.func Form[T any](r *http.Request) (T, bool) { form, ok := r.Context().Value(contextKey[T]{}).(T) return form, ok}