// Package httpapi exposes the Control API's HTTP surface — the only way // validator-agents, probers, and operators interact with the system. All // business logic lives in internal/orchestrator; handlers here do request // parsing/validation, call into the orchestrator or db package, and shape // the JSON response. package httpapi import ( "encoding/json" "errors" "log/slog" "net/http" "cloudipvalidator/internal/db" "cloudipvalidator/internal/orchestrator" ) type Server struct { DB *db.DB Orch *orchestrator.Orchestrator Log *slog.Logger Auth Authenticator } func New(d *db.DB, o *orchestrator.Orchestrator, log *slog.Logger) *Server { return &Server{DB: d, Orch: o, Log: log} } func (s *Server) Handler() http.Handler { s.Auth.Log = s.Log mux := http.NewServeMux() s.routes(mux) return loggingMiddleware(s.Log, mux) } func loggingMiddleware(log *slog.Logger, next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { next.ServeHTTP(w, r) log.Debug("request", "method", r.Method, "path", r.URL.Path, "remote", r.RemoteAddr) }) } func writeJSON(w http.ResponseWriter, status int, v interface{}) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) if v != nil { _ = json.NewEncoder(w).Encode(v) } } func writeError(w http.ResponseWriter, status int, msg string) { writeJSON(w, status, errorResponse{Error: msg}) } func readJSON(r *http.Request, v interface{}) error { if r.Body == nil || r.ContentLength == 0 { return nil } dec := json.NewDecoder(r.Body) return dec.Decode(v) } // writeDBError maps the typed sentinel errors returned by internal/db's // admin mutation methods to the appropriate HTTP status, instead of // defaulting everything to 500 like the older read-only admin handlers do. func writeDBError(w http.ResponseWriter, err error) { switch { case errors.Is(err, db.ErrNotFound): writeError(w, http.StatusNotFound, err.Error()) case errors.Is(err, db.ErrConflict), errors.Is(err, db.ErrBusy), errors.Is(err, db.ErrInUse), errors.Is(err, db.ErrInvalidState): writeError(w, http.StatusConflict, err.Error()) case errors.Is(err, db.ErrValidation): writeError(w, http.StatusBadRequest, err.Error()) default: writeError(w, http.StatusInternalServerError, err.Error()) } }