package handler import ( "encoding/json" "fmt" "net/http" "strconv" "strings" "github.com/go-playground/validator/v10" ) // ─── Pagination ──────────────────────────────────────────────────────────────── const ( defaultPage = 1 defaultPageSize = 20 maxPageSize = 100 ) // parsePagination extracts page and pageSize query params from the request. func parsePagination(r *http.Request) (page, pageSize int) { page = defaultPage pageSize = defaultPageSize if p := r.URL.Query().Get("page"); p != "" { if n, err := strconv.Atoi(p); err == nil && n > 0 { page = n } } if ps := r.URL.Query().Get("pageSize"); ps != "" { if n, err := strconv.Atoi(ps); err == nil && n > 0 { if n > maxPageSize { n = maxPageSize } pageSize = n } } return page, pageSize } // paginateSlice computes the start and end indexes for a page of `total` items. // Bounds are clamped to [0, total]. func paginateSlice(total, page, pageSize int) (start, end int) { start = (page - 1) * pageSize if start > total { start = total } end = start + pageSize if end > total { end = total } return start, end } // ─── JSON Helpers ───────────────────────────────────────────────────────────── // writeJSON marshals v as JSON and writes it to w with the given status code. func writeJSON(w http.ResponseWriter, status int, v any) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) if v == nil { return } if err := json.NewEncoder(w).Encode(v); err != nil { http.Error(w, `{"error":"internal encoding error"}`, http.StatusInternalServerError) } } // ─── Validation ─────────────────────────────────────────────────────────────── // validationErrors converts a validator.ValidationErrors into a string map // suitable for the ErrorResponse details field. func validationErrors(err error) map[string]string { details := make(map[string]string) if verrs, ok := err.(validator.ValidationErrors); ok { for _, fe := range verrs { field := strings.ToLower(fe.Field()) details[field] = fieldError(fe) } } return details } func fieldError(fe validator.FieldError) string { switch fe.Tag() { case "required": return "this field is required" 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()) default: return fmt.Sprintf("failed validation: %s", fe.Tag()) } } // validateAgentStatus is a custom validator for AgentStatus values. func validateAgentStatus(fl validator.FieldLevel) bool { if status, ok := fl.Field().Interface().(interface { IsValid() bool }); ok { return status.IsValid() } return false }