Skip to content

Restructure the library: opt-in dependencies, one middleware, and new outbound + problemjson packages - #6

Merged
andresbott merged 16 commits into
mainfrom
refactor/middleware-remove-standalone-wrappers
Sep 11, 2026
Merged

andresbott merged 16 commits into
mainfrom
refactor/middleware-remove-standalone-wrappers

Conversation

@andresbott

Copy link
Copy Markdown
Member

Summary

The middleware package hard-depended on Prometheus and a concrete *slog.Logger — so every consumer compiled Prometheus whether they used it or not — and it shipped standalone wrappers that duplicated the combined middleware while baking an ad-hoc JSON error shape into the request path. This reshapes the library around interface seams so a package's third-party dependencies are pulled in only when you import it, collapses the several middlewares into one handler that never rewrites response bodies, and broadens the scope from inbound-only to inbound + outbound.

What

  • Third-party dependencies are now opt-in. middleware depends on new Observer (metrics) and Logger interfaces instead of concrete types; Prometheus moves to a new middleware/metrics package (NewObserver/NopObserver) and golang.org/x/time/rate lives only in outbound. Importing middleware alone links no third-party code — and *slog.Logger still satisfies Logger, so existing callers pass it unchanged.
  • One middleware, no standalone wrappers. Removed the Logging, Metrics, JSONErrors, GenericErrors, and PanicRecover standalone functions; the combined Middleware (method renamed Middleware() → Wrap()) is the sole entry point and only logs, records metrics, and recovers panics. It no longer modifies the body — error bodies are teed to the client and captured for logging. ReqDelay stays as the one dev-only helper.
  • New outbound package for a service's own calls to third-party APIs: a throttled, retrying client (rate limiting, bounded exponential backoff for 5xx/429/timeouts, Retry-After compliance) with a typed *Error carrying a failure Kind, HTTP status, and user-facing message.
  • New problemjson package writing RFC 9457 application/problem+json, replacing the removed {"error":...} shape. ModeMasked strips detail in production; WriteUpstream renders an outbound failure through a structural interface, so neither package imports the other.
  • Single Cfg-struct constructors across middleware.New, metrics.NewObserver, spa.New, outbound.New, and problemjson.New. spa moves from handlers/spa/ to top-level spa/ and gains dotfile blocking (except .well-known/) plus PathPrefix normalisation; the old lib/limitio package is inlined into middleware as an unexported capped buffer.
  • Correctness and hardening. Metric label cardinality is bounded (matched route pattern or "unmatched", normalised method — the raw request path is never a label); panic recovery no longer synthesises a 500 over a response the handler already started (new Started() guard, matching net/http); the log correlation id is configurable via Cfg.RequestID; ReqDelay honours the request context.

Notes

  • These are breaking API changes throughout, which is fine pre-1.0 (the ! commits) — no compatibility shims were kept; callers move to the new Cfg constructors and Wrap.
  • Error-body rewriting was removed from the middleware on purpose, not lost: it now belongs to the handler-layer problemjson package. Running a body-rewriting middleware alongside problemjson would double-encode, so a route group should use one or the other.
  • CLAUDE.md becomes AGENTS.md (a compat symlink is kept), which documents the target architecture; go.mod moves to Go 1.25.

@andresbott
andresbott merged commit 2b2ac35 into main Sep 11, 2026
3 checks passed
@andresbott
andresbott deleted the refactor/middleware-remove-standalone-wrappers branch September 11, 2026 22:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant