package api import ( "context" "encoding/json" "fmt" "io" "log/slog" "net/http" "regexp" "runtime" "strconv" "strings" "time" ) type contextKey int const requestIDCtxKey contextKey = iota func requestIDFromCtx(ctx context.Context) string { if id, ok := ctx.Value(requestIDCtxKey).(string); ok { return id } return "" } var entityIDPattern = regexp.MustCompile(`^[A-Z]{2,3}-[0-9]{7}$`) func parseID(path, prefix string) (string, bool) { if !strings.HasPrefix(path, prefix) { return "", false } trimmed := strings.TrimPrefix(path, prefix) if trimmed == "" || strings.Contains(trimmed, "/") { return "", false } if !entityIDPattern.MatchString(trimmed) { return "", false } return trimmed, true } func parseSubresourceID(path, prefix, suffix string) (string, bool) { if !strings.HasPrefix(path, prefix) || !strings.HasSuffix(path, suffix) { return "", false } trimmed := strings.TrimPrefix(path, prefix) trimmed = strings.TrimSuffix(trimmed, suffix) if trimmed == "" || strings.Contains(trimmed, "/") { return "", false } if !entityIDPattern.MatchString(trimmed) { return "", false } return trimmed, true } func decodeJSON(r *http.Request, dest any) error { decoder := json.NewDecoder(r.Body) decoder.DisallowUnknownFields() if err := decoder.Decode(dest); err != nil { return err } if err := decoder.Decode(&struct{}{}); err != io.EOF { return err } return nil } func writeJSON(w http.ResponseWriter, status int, payload any) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) if err := json.NewEncoder(w).Encode(payload); err != nil { slog.Error("write json response failed", "status", status, "err", err) } } func writeError(w http.ResponseWriter, status int, message string, cause ...error) { if pc, file, line, ok := runtime.Caller(1); ok { fn := runtime.FuncForPC(pc) fnName := "" if fn != nil { fnName = fn.Name() } if status >= http.StatusInternalServerError { root := "" if len(cause) > 0 && cause[0] != nil { root = cause[0].Error() } slog.Error("api error", "status", status, "message", message, "cause", root, "caller", fmt.Sprintf("%s:%d", file, line), "func", fnName) } else { slog.Warn("api error", "status", status, "message", message, "caller", fmt.Sprintf("%s:%d", file, line), "func", fnName) } } else { if status >= http.StatusInternalServerError { root := "" if len(cause) > 0 && cause[0] != nil { root = cause[0].Error() } slog.Error("api error", "status", status, "message", message, "cause", root) } else { slog.Warn("api error", "status", status, "message", message) } } writeJSON(w, status, map[string]string{"error": message}) } func parseTimeParam(value string) (time.Time, error) { return time.Parse(time.RFC3339, value) } func parseIntParam(value string) (int, error) { parsed, err := strconv.Atoi(value) if err != nil { return 0, err } return parsed, nil } func parseFloatParam(value string) (float64, error) { return strconv.ParseFloat(value, 64) }