package httputil import ( "encoding/json" "errors" "fmt" "net/http" "github.com/go-playground/validator/v10" ) func WriteJSON(w http.ResponseWriter, status int, data any) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) _ = json.NewEncoder(w).Encode(data) } func DecodeJSON[T any](w http.ResponseWriter, r *http.Request) (T, error) { var v T r.Body = http.MaxBytesReader(w, r.Body, 1<<20) dec := json.NewDecoder(r.Body) dec.DisallowUnknownFields() if err := dec.Decode(&v); err != nil { return v, err } return v, nil } func WriteValidationErrors(w http.ResponseWriter, err error) { var ve validator.ValidationErrors if !errors.As(err, &ve) { WriteJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid request"}) return } problems := make(map[string]string, len(ve)) for _, fe := range ve { problems[fe.Field()] = messageForTag(fe) } WriteJSON(w, http.StatusBadRequest, map[string]any{"errors": problems}) } func messageForTag(fe validator.FieldError) string { switch fe.Tag() { case "required": return "is required" case "email": return "must be a valid email address" case "min": return fmt.Sprintf("must be at least %s characters", fe.Param()) case "max": return fmt.Sprintf("must be at most %s characters", fe.Param()) case "gte": return fmt.Sprintf("must be %s or greater", fe.Param()) case "lte": return fmt.Sprintf("must be %s or less", fe.Param()) default: return "is invalid" } }