50 lines
1.5 KiB
Go
50 lines
1.5 KiB
Go
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")
|
|
}
|
|
}
|