package api import ( "context" "encoding/json" "errors" "io" "log/slog" "net/http" "atlas9.dev/c/core" "atlas9.dev/c/core/iam" "atlas9.dev/c/demo/lib" ) // preparer is implemented by request types to apply defaults and validate themselves. type preparer interface { Prepare() error } func read[T preparer](w http.ResponseWriter, r *http.Request, out T) bool { // Read the request if err := json.NewDecoder(r.Body).Decode(out); err != nil && err != io.EOF { w.WriteHeader(http.StatusBadRequest) w.Header().Set("Content-Type", "application/json") enc := json.NewEncoder(w) enc.Encode(ErrorResponse{Message: InvalidJsonError{Err: err}.Error()}) return true } if err := out.Prepare(); err != nil { w.WriteHeader(http.StatusBadRequest) w.Header().Set("Content-Type", "application/json") enc := json.NewEncoder(w) enc.Encode(ErrorResponse{Message: err.Error()}) return true } return false } func check(w http.ResponseWriter, r *http.Request, guard lib.Guard, cap iam.Cap, tenant core.ID, path core.Path) bool { return writeErr(r.Context(), w, guard.Check(r.Context(), cap, tenant, path)) } func checkSystem(w http.ResponseWriter, r *http.Request, guard lib.Guard, cap iam.Cap) bool { return writeErr(r.Context(), w, guard.System(r.Context(), cap)) } func writeErr(ctx context.Context, w http.ResponseWriter, err error) bool { if err == nil { return false } slog.ErrorContext(ctx, err.Error()) status := http.StatusInternalServerError switch { case errors.Is(err, core.ErrNotFound): status = http.StatusNotFound case errors.Is(err, iam.ErrForbidden): status = http.StatusForbidden } w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) enc := json.NewEncoder(w) enc.Encode(ErrorResponse{Message: err.Error()}) return true } func write(ctx context.Context, w http.ResponseWriter, err error, data any) bool { if writeErr(ctx, w, err) { return true } if data == nil { return false } w.Header().Set("Content-Type", "application/json") enc := json.NewEncoder(w) encerr := enc.Encode(data) if encerr != nil { // TODO write error response code? slog.ErrorContext(ctx, "failed to encode response", "error", encerr) return true } return false }