107 lines
3.0 KiB
Go
107 lines
3.0 KiB
Go
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
|
|
}
|