hub/internal/handlers/helpers/helpers.go

126 lines
3.6 KiB
Go

package helpers
import (
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"strconv"
"time"
"github.com/artifacthub/hub/internal/hub"
)
const (
// DefaultAPICacheMaxAge represents the default cache duration used by some
// API endpoints.
DefaultAPICacheMaxAge = 5 * time.Minute
// PaginationDefaultLimit represents the default limit used for pagination.
PaginationDefaultLimit = 20
// PaginationMaxLimit represents the default maximum limit used for
// pagination.
PaginationMaxLimit = 60
// PaginationTotalCount represents a header used to indicate the number of
// entries available for pagination purposes.
PaginationTotalCount = "Pagination-Total-Count"
)
// BuildCacheControlHeader builds an http cache header using the max age
// duration provided.
func BuildCacheControlHeader(cacheMaxAge time.Duration) string {
return fmt.Sprintf("max-age=%d", int64(cacheMaxAge.Seconds()))
}
// GetPagination is a helper that extracts the pagination information from the
// query string values provided.
func GetPagination(qs url.Values, defaultLimit, maxLimit int) (*hub.Pagination, error) {
// Limit
var limit int
if qs.Get("limit") != "" {
var err error
limit, err = strconv.Atoi(qs.Get("limit"))
if err != nil {
return nil, fmt.Errorf("invalid limit: %s", qs.Get("limit"))
}
if limit > maxLimit {
return nil, fmt.Errorf("invalid limit: %s (max: %d)", qs.Get("limit"), maxLimit)
}
} else {
limit = defaultLimit
}
// Offset
var offset int
if qs.Get("offset") != "" {
var err error
offset, err = strconv.Atoi(qs.Get("offset"))
if err != nil {
return nil, fmt.Errorf("invalid offset: %s", qs.Get("offset"))
}
}
return &hub.Pagination{
Limit: limit,
Offset: offset,
}, nil
}
// RenderJSON is a helper to write the json data provided to the given http
// response writer, setting the appropriate content type, cache and status code.
func RenderJSON(w http.ResponseWriter, dataJSON []byte, cacheMaxAge time.Duration, code int) {
w.Header().Set("Cache-Control", BuildCacheControlHeader(cacheMaxAge))
w.Header().Set("Content-Length", strconv.Itoa(len(dataJSON)))
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(code)
_, _ = w.Write(dataJSON)
}
// RenderErrorJSON is a helper to write the error provided to the given http
// response writer as json setting the appropriate content type.
func RenderErrorJSON(w http.ResponseWriter, err error) {
w.Header().Set("Content-Type", "application/json")
var errMsg string
switch {
case errors.Is(err, hub.ErrInvalidInput):
w.WriteHeader(http.StatusBadRequest)
if err != nil {
errMsg = err.Error()
}
case errors.Is(err, hub.ErrInsufficientPrivilege):
w.WriteHeader(http.StatusForbidden)
case errors.Is(err, hub.ErrNotFound):
w.WriteHeader(http.StatusNotFound)
default:
w.WriteHeader(http.StatusInternalServerError)
}
writeErrorJSON(w, errMsg)
}
// RenderErrorWithCodeJSON is a helper to write the error provided to the given
// http response writer as json setting the appropriate content type. Unlike
// RenderErrorJSON, which decides what status code to use based on the type of
// the error provided, this methods expects the status code and the error msg
// will be always sent to the requester.
func RenderErrorWithCodeJSON(w http.ResponseWriter, err error, code int) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(code)
var errMsg string
if err != nil {
errMsg = err.Error()
}
writeErrorJSON(w, errMsg)
}
// writeErrorJSON buids the error payload and writes it to the writer provided.
func writeErrorJSON(w io.Writer, msg string) {
data := map[string]interface{}{
"message": msg,
}
_ = json.NewEncoder(w).Encode(data)
}