Ctrl K
ComunidadGuías…

internal/handlers/errors.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package handlers

import (
	"encoding/json"
	"errors"
	"net/http"
	"strconv"
	"time"
	"xprem/internal/services"
)

type APIError struct {
	Title  string `json:"title"`
	Status int    `json:"status"`
	Detail string `json:"detail"`
}

// RenderError enforces the structured RFC 7807 error format
func RenderError(w http.ResponseWriter, status int, detail string) {
	w.Header().Set("Content-Type", "application/problem+json; charset=utf-8")
	w.WriteHeader(status)
	json.NewEncoder(w).Encode(APIError{
		Title:  http.StatusText(status),
		Status: status,
		Detail: detail,
	})
}

// RenderThrottled is the single response every rate-limited endpoint gives, and
// it says nothing beyond "too many, wait".
//
// The wording is fixed on purpose. It must not vary with which limit was
// reached, nor with whether the account named exists, because a caller able to
// tell those apart can enumerate accounts by watching how the refusal changes.
// One message for every case is what makes the counters unobservable from
// outside.
func RenderThrottled(w http.ResponseWriter, retryAfter time.Duration) {
	seconds := int(retryAfter.Seconds())
	if seconds < 1 {
		seconds = 1
	}
	w.Header().Set("Retry-After", strconv.Itoa(seconds))
	RenderError(w, http.StatusTooManyRequests, "Too many attempts. Try again later.")
}

// RenderJSON writes payload as a JSON body with the given status. The success
// counterpart to RenderError, shared so every handler renders the same way.
func RenderJSON(w http.ResponseWriter, status int, payload interface{}) {
	marshaledResponse, err := json.Marshal(payload)
	if err != nil {
		RenderError(w, http.StatusInternalServerError, "An internal error occurred.")
		return
	}
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(status)
	_, _ = w.Write(marshaledResponse)
}

// activeRolloutConflictMessage is the CLI-facing 409 body for publish, republish and
// rollback attempts against a branch and runtime version with an active per-update
// rollout. The republish and rollback commands print it verbatim.
const activeRolloutConflictMessage = "A progressive rollout is already active for this branch and runtime version. Finish or revert it from the dashboard first."

// RenderCliAuthError distinguishes three outcomes: a credential that failed to
// authenticate (401, generic message so nothing leaks about why), one that
// authenticated but is blocked by its access rules (403, with the reason so
// the CLI user knows what to fix), and one that could not be judged at all
// because the control plane was unreachable (500, because "your key is
// invalid" is the wrong thing to tell a CI job during a database blip).
func RenderCliAuthError(w http.ResponseWriter, err error) {
	if errors.Is(err, services.ErrCliAccessDenied) {
		http.Error(w, err.Error(), http.StatusForbidden)
		return
	}
	if errors.Is(err, services.ErrCliAuthUnavailable) {
		http.Error(w, "Could not verify this credential", http.StatusInternalServerError)
		return
	}
	http.Error(w, "Error validating auth", http.StatusUnauthorized)
}