Personal Git forge.

Code git clone ssh://git.poy.ooo:6969/~poyo/farga ZIP
app/middleware/form-validation.go / 4KiB #6ef939b2
Raw
package middlewareimport (	"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 keysfunc 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}