package api import ( "context" "errors" "log/slog" "net/http" "strings" "git.k6n.net/mats/fullstack-template/internal/browse" ) // browseHandler adapts HTTP requests to the browse service: read the url // query parameter, call Inspect, map errors to the wire. No fetching or // conversion logic lives here. type browseHandler struct { svc *browse.Service logger *slog.Logger } func (h *browseHandler) get(w http.ResponseWriter, r *http.Request) { raw := strings.TrimSpace(r.URL.Query().Get("url")) snap, err := h.svc.Inspect(r.Context(), raw) if err != nil { h.fail(w, err) return } writeJSON(w, http.StatusOK, snap) } // fail is the translation point from browse errors to the wire: // *ValidationError → 400, *FetchError → 502 (the site is the problem), // context deadline → 504, anything else → 500 (logged). func (h *browseHandler) fail(w http.ResponseWriter, err error) { var vErr *browse.ValidationError var fErr *browse.FetchError switch { case errors.As(err, &vErr): writeError(w, http.StatusBadRequest, "validation_failed", vErr.Error()) case errors.As(err, &fErr): h.logger.Info("browse fetch failed", "url", fErr.URL, "err", fErr.Err) writeError(w, http.StatusBadGateway, "unavailable", "could not fetch "+fErr.URL) case errors.Is(err, context.DeadlineExceeded): writeError(w, http.StatusGatewayTimeout, "timeout", "the site took too long to answer") default: h.logger.Error("browse failed", "err", err) writeError(w, http.StatusInternalServerError, "internal_error", "an internal error occurred") } }