Files
beacon-server/internal/api/handlers/responses.go
n30nex dab228e9bb feat(logging): configure levels and structured runtime output (#144)
* feat(logging): configure levels and structured runtime output

* fix(logging): bound request-derived log fields

* fix(logging): retain malformed IATA source attributes safely

* test(logging): capture structured REST rate-limit records
2026-09-19 10:36:26 -04:00

42 lines
1.5 KiB
Go

// Copyright 2026 Beacon Contributors
// SPDX-License-Identifier: AGPL-3.0-or-later
// Package handlers provides HTTP route handlers for the Beacon REST API.
package handlers
import (
"encoding/json"
"fmt"
"log/slog"
"net/http"
"strings"
)
// APIError is the standard error response shape for all Beacon API endpoints.
// Code is a stable snake_case identifier derived from the HTTP status text,
// safe to match against in client code. Message is a human-readable description.
type APIError struct {
Code string `json:"code"` // e.g. "not_found", "bad_request"
Message string `json:"message"` // e.g. "channel not found"
}
// respond writes a JSON response with the given status code and data.
// Logs if encoding fails since the header is already written at that point.
func respond(w http.ResponseWriter, status int, data any) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
if err := json.NewEncoder(w).Encode(data); err != nil {
slog.Error("api: failed to encode response", "component", "api", "error", err)
}
}
// respondError writes a standard JSON error response.
// The error code is derived automatically from the HTTP status text.
func respondError(w http.ResponseWriter, status int, message string) {
if status >= 500 {
slog.Error(fmt.Sprintf("api: error %d: %s", status, message), "component", "api")
}
code := strings.ToLower(strings.ReplaceAll(http.StatusText(status), " ", "_"))
respond(w, status, map[string]APIError{"error": {Code: code, Message: message}})
}