# Pathrule Pattern: Go API (Gin / Echo) (1.0.0)
# ::pathrule:package:go-api

### [RULE] Handle every error; wrap with context, never discard  (path: /internal)
<!-- scope: folder | priority: high | strict -->

In Go an error is a return value, not an exception. Ignoring it does not make the failure go away; it makes it invisible until it corrupts something downstream.

- Check every returned `error`. Do not assign it to `_` or leave it unchecked. If a function can fail, the caller decides what to do with the failure.
- Add context when you propagate: `fmt.Errorf("loading user %d: %w", id, err)`. The `%w` verb wraps the cause so callers can still `errors.Is` / `errors.As` it, while the message gains a breadcrumb trail.
- Handle an error once. Either log it or return it, not both at every layer; double-logging the same failure makes logs unreadable. Log at the boundary (the handler) and return wrapped below.
- Reserve `panic` for truly unrecoverable programmer errors, not for normal control flow. A web handler should turn an error into an HTTP status, not panic.

---

### [RULE] Propagate context.Context for cancellation and deadlines  (path: /internal)
<!-- scope: folder | priority: high | advisory -->

The request `context.Context` carries cancellation, deadlines, and request-scoped values. Dropping it means a client that disconnected still pays for work nobody will read.

- Take `ctx context.Context` as the first parameter of any function that does I/O (database, HTTP, RPC) and pass it down the call chain. Get it from `c.Request.Context()` (Gin) or `c.Request().Context()` (Echo) at the handler.
- Pass `ctx` into every database and outbound HTTP call (`db.QueryContext(ctx, ...)`, `http.NewRequestWithContext(ctx, ...)`) so a cancelled or timed-out request actually stops the downstream work.
- Do not store a `Context` in a struct field, and do not pass `context.Background()` deep in a request path; thread the real request context through.
- Use `context.WithTimeout` to bound slow downstream calls, and always `defer cancel()` to release the timer.

---

### [RULE] Bind and validate request input at the handler boundary  (path: /internal/handler)
<!-- scope: folder | priority: high | strict -->

A bound struct is not a validated struct. Binding fills the fields; validation decides whether the values are acceptable. Skipping validation ships untrusted input straight into your logic.

- Bind the request body to a typed request struct (`c.ShouldBindJSON(&req)` in Gin, `c.Bind(&req)` in Echo), and use a separate struct for input than for your domain/DB model.
- Validate with struct tags (`binding:"required,email"` via go-playground/validator, which both Gin and Echo integrate). Validate every externally-supplied field: required, format, length, range.
- Return `400 Bad Request` with a clear, structured error when binding or validation fails, before any business logic runs. Do not let a zero-value or malformed field reach a service or query.
- Keep validation rules on the request struct, not scattered through the handler body, so the contract is declared in one place and visible in the type.

---

### [MEMORY] Project layout: thin handlers over services over repositories  (path: /internal)

We keep a Go API layered so the HTTP framework stays at the edge and the core logic does not depend on Gin or Echo.

- Layout: `cmd/<app>/main.go` is the entrypoint (wiring, config, server start); business code lives under `internal/` so it cannot be imported by other modules. Group by `internal/handler` (HTTP), `internal/service` (business logic), `internal/repository` (data access), `internal/model` (domain types).
- Handlers are thin: parse and validate the request, call a service, map the result or error to an HTTP response. No SQL or business rules in the handler.
- Services hold business logic and depend on repository interfaces, not concrete database types. Define the interface in the consumer package so the service is testable with a fake.
- Wire dependencies explicitly in `main` (constructor injection); avoid global singletons for the DB handle or config. Order middleware deliberately (recovery, logger, CORS, auth, then routes).
- Pick the framework by team fit: Gin (most popular, large middleware ecosystem) or Echo (idiomatic error returns, built-in OpenAPI-friendly tooling). The layering above is identical either way.

See /internal for the error-handling and context rules and /internal/handler for the input-validation rule.

---

### [MEMORY] Concurrency and resource discipline  (path: /internal)

Go makes concurrency easy to start and easy to leak. A little discipline keeps it reliable.

- Do not spawn an unbounded number of goroutines per request. A goroutine started in a handler that outlives the request, with no context and no bound, is a leak; pass `ctx` to it and cap concurrency with a worker pool or `errgroup`.
- Use `errgroup.WithContext` to run parallel sub-tasks for a request: it propagates cancellation and collects the first error, instead of hand-rolling `WaitGroup` + channels.
- Guard shared mutable state with a mutex or confine it to one goroutine via channels; run tests and CI with `-race` to catch data races before production.
- Close every resource you open with `defer` (rows, response bodies, files) right after the error check, so an early return cannot leak it.

See /internal for the project-layout memory and the error/context rules.

---

### [SKILL] go-api-review  (path: /)

---
name: go-api-review
description: Review checklist for Go HTTP API changes on Gin or Echo. Run before merging any handler, service, or repository change.
---

# Go API review

- [ ] Every returned error is checked; propagated errors are wrapped with `%w` and context; nothing assigned to `_`.
- [ ] An error is handled once (logged at the boundary OR returned below), not double-logged at every layer.
- [ ] `context.Context` is the first arg on I/O functions and threaded into every DB/HTTP call; no `Context` stored in structs.
- [ ] Slow downstream calls are bounded with `context.WithTimeout` + `defer cancel()`.
- [ ] Request body bound to a typed input struct (separate from the domain model) and validated with struct tags before logic.
- [ ] Bind/validation failure returns `400` with a structured error before any business logic runs.
- [ ] Handlers are thin (parse, call service, map response); SQL and business rules live in service/repository under `internal/`.
- [ ] Services depend on repository interfaces, not concrete DB types; dependencies wired in `main`, no globals.
- [ ] No unbounded/leaking goroutines; parallel work uses `errgroup.WithContext`; shared state guarded; CI runs `-race`.
- [ ] Resources (rows, bodies, files) closed with `defer` right after the error check.
