Skip to main content

go

Use when writing, reviewing, testing, or shipping Go code and HTTP services: idioms, `%w` error wrapping, goroutine/context/errgroup concurrency, net/http 1.22 routing, log/slog, project layout, table-driven tests, Go hardening. NOT language-agnostic threat modeling (that is `secure-coding`), NOT Dockerfile/CI shipping (that is `deployment`).

Ir para a instalação

Informações da origem

Repositório
ericrisco/rsc-harness
Última atividade na origem
17 de agosto de 2026 às 20:04
Idioma detectado do SKILL.md
inglês
Estrelas
110
Forks
9

Opções de instalação

Por padrão, está selecionado o prompt que primeiro revisa a origem. Você pode mudar para um comando direto ou baixar uma cópia local.

Revise os arquivos de origem

Leia o SKILL.md e os arquivos complementares exibidos pelo SkillsMP antes de decidir se vai instalar.

Explorador de arquivos
7 arquivos

Exibindo SKILL.md

SKILL.md
Instruções da origem · Visualização somente leitura
name
go
description
Use when writing, reviewing, testing, or shipping Go code and HTTP services: idioms, `%w` error wrapping, goroutine/context/errgroup concurrency, net/http 1.22 routing, log/slog, project layout, table-driven tests, Go hardening. NOT language-agnostic threat modeling (that is `secure-coding`), NOT Dockerfile/CI shipping (that is `deployment`).
tags
["go","golang","http","backend","service"]
recommends
["postgresdb","secure-coding","deployment"]
origin
risco
# Idiomatic Go services Targets **Go 1.22+** (Go 1.26 is the current stable release): enhanced `net/http` routing (`mux.HandleFunc("GET /users/{id}", h)` + `r.PathValue`), `log/slog` structured logging, and fixed loop-variable semantics (no more `tt := tt`). > **⚠️ SDD new-feature gate — read this first.** If this skill fired on a **new, non-trivial feature or behaviour change** and there is **no approved spec + plan** under `02-DOCS/wiki/sdd/`, STOP — do **not** write feature code yet. Hand off to `../specify/SKILL.md` first: it runs brainstorm → spec → plan → tasks before any code, then routes back here once the plan is approved. Build here directly only for a genuinely one-line / low-risk change. Method: `../sdd/SKILL.md`. ## Boundary Go error handling and HTTP contract design (status-code taxonomy, REST resource naming) live **here** — this skill is the canonical authority for both in Go. Delegate outward: - Language-agnostic abuse/authz review, threat modeling, OWASP-class bugs -> [`secure-coding`](../secure-coding/SKILL.md). This skill keeps the Go-specific controls: SQL params, server timeouts, `govulncheck`, TLS defaults. - Containerfile / k8s / CI pipeline authoring -> [`deployment`](../deployment/SKILL.md). This skill ships only a Docker note + `ldflags`. - Recording per-project conventions in a workspace wiki -> [`harness`](../harness/SKILL.md) (see "Project grounding" below). Non-service Go (CLI tooling, codegen, ML): the patterns apply, but the HTTP/production half is irrelevant. ## Idioms **Useful zero value.** Design types so the zero value works before any constructor. ```go // Good: zero-value Counter is ready; the zero-value mutex is unlocked. var b bytes.Buffer too. type Counter struct { mu sync.Mutex n int } func (c *Counter) Inc() { c.mu.Lock(); c.n++; c.mu.Unlock() } // Bad: nil map field panics on first write (assignment to entry in nil map); hidden init step. type Registry struct{ items map[string]int } func (r *Registry) Add(k string) { r.items[k]++ } // panic if items was never make()'d ``` **Accept interfaces, return structs.** Return the concrete type; declare the interface where it is consumed. ```go type UserStore interface { // declared in package service - only what it needs GetUser(ctx context.Context, id string) (*User, error) } func NewService(s UserStore) *Service { return &Service{store: s} } // Good: return *Service // Bad: func NewService(s UserStore) UserStore - returning the interface hides the type. ``` **Functional options.** Defaults first, then apply options. ```go type Server struct { addr string timeout time.Duration logger *slog.Logger } type Option func(*Server) func WithTimeout(d time.Duration) Option { return func(s *Server) { s.timeout = d } } func WithLogger(l *slog.Logger) Option { return func(s *Server) { s.logger = l } } func NewServer(addr string, opts ...Option) *Server { s := &Server{addr: addr, timeout: 30 * time.Second, logger: slog.Default()} // defaults first for _, opt := range opts { opt(s) } return s } ``` Use a plain `Config` struct once options exceed ~5; options are for optional, composable tuning, not required fields. **Embedding for composition.** Embed to borrow a behavior, not to fake inheritance. ```go // Good: the service gets .Info/.Error for free from the embedded logger. type Service struct { *slog.Logger store UserStore } // Bad: deep type trees (Base -> Middle -> Leaf) modeling "is-a" inheritance - avoid. ``` **Early return.** Clear over clever: invert the error and `return`; keep the happy path flat (no arrow code). ```go // Good: each failure returns immediately; the success path is unindented. func save(ctx context.Context, u *User) error { if u == nil { return errors.New("nil user") } if err := validate(u); err != nil { return fmt.Errorf("validate: %w", err) } return store.Put(ctx, u) } // Bad: if u != nil { if err := validate(u); err == nil { ... } else { ... } } - arrow code. ``` **No package-level mutable state.** Inject via constructor (`func New(db *sql.DB) *Server`), never a global `var db *sql.DB` opened in `init()` - globals couple everything and kill testability. **Receivers.** Pick value or pointer per type and stay consistent across its method set; mutating / large / contains-`sync` -> pointer. **Go 1.22 loopvar.** Loop variables are per-iteration now. Stop emitting the workaround: inside `for _, tt := range tests` the line `// tt := tt` is obsolete - DELETE it. ## Errors **Sentinel vs typed.** Sentinels for identity; typed errors for data. ```go var ErrNotFound = errors.New("not found") // sentinel: identity type ValidationError struct{ Field, Msg string } // typed: carries data func (e *ValidationError) Error() string { return fmt.Sprintf("%s: %s", e.Field, e.Msg) } ``` **Wrap and classify.** Wrap every crossed boundary with `%w`; never compare message strings. ```go err := fmt.Errorf("find user %s: %w", id, ErrNotFound) if errors.Is(err, ErrNotFound) { /* sentinel match through the wrap chain */ } var verr *ValidationError if errors.As(err, &verr) { /* typed match: verr.Field, verr.Msg */ } joined := errors.Join(err1, err2) // 1.20+: aggregate; Is/As traverse both ``` **3-layer boundary (the canonical flow).** Repo wraps the driver sentinel into a domain sentinel; service passes it through; handler classifies once and maps to a status, logging only the unexpected. ```go // repository: translate sql.ErrNoRows into a domain sentinel, keep the chain. func (r *Repo) GetUser(ctx context.Context, id string) (*User, error) { var u User err := r.db.QueryRowContext(ctx, "SELECT id, name FROM users WHERE id = $1", id). Scan(&u.ID, &u.Name) if errors.Is(err, sql.ErrNoRows) { return nil, fmt.Errorf("user %s: %w", id, ErrNotFound) } if err != nil { return nil, fmt.Errorf("query user %s: %w", id, err) } return &u, nil } // handler: classify once, map to HTTP status. func (h *Handler) getUser(w http.ResponseWriter, r *http.Request) { u, err := h.svc.GetUser(r.Context(), r.PathValue("id")) switch { case err == nil: writeJSON(w, http.StatusOK, u) case errors.Is(err, ErrNotFound): http.Error(w, "not found", http.StatusNotFound) default: slog.Error("get user", "err", err) http.Error(w, "internal error", http.StatusInternalServerError) } } ``` Full handler adapter (error-returning `apiHandler`) -> `references/http-services.md`. **`defer` + named return** to capture `Close()` errors: ```go func read(name string) (err error) { f, e := os.Open(name) if e != nil { return e } defer func() { err = errors.Join(err, f.Close()) }() // capture Close() into the return return nil } ``` ## Concurrency (essentials) `context.Context` is the **first** param of every call, never stored in a struct, never `nil` (use `context.TODO()` while wiring). Bound work with a context deadline; bound concurrency with `errgroup` — the derived `ctx` cancels siblings on first error, and `g.SetLimit(n)` caps in-flight goroutines: ```go g, ctx := errgroup.WithContext(ctx) g.SetLimit(8) for _, id := range ids { g.Go(func() error { return process(ctx, id) }) // Go 1.22+: no id := id needed } err := g.Wait() ``` Three rules cover most service code: every goroutine needs a known exit path (a started goroutine you cannot stop is a leak); an unbuffered `ch <- v` with no receiver after a cancel blocks forever, so buffer it and `select` on `ctx.Done()`; **run `-race` in CI**. Low-level needs map to `sync.Once` (lazy init), `sync.RWMutex` (read-heavy state), `sync/atomic` (`atomic.Int64` counters). Full implementations — context plumbing, channel/select patterns, leak detection, worker pools, pipelines, fan-in/out, semaphores, `singleflight`, and a `withRetry` helper (backoff + full jitter, `ctx`-aware, **never retries 4xx**) -> `references/concurrency.md`. ## HTTP services (essentials) Go 1.22 routed mux — method and path live in the pattern; the `error`-returning adapter centralizes status mapping: ```go mux := http.NewServeMux() mux.HandleFunc("GET /users/{id}", getUser) // 405 on wrong method, 404 on no match id := r.PathValue("id") // inside the handler type apiHandler func(http.ResponseWriter, *http.Request) error func (h apiHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { if err := h(w, r); err != nil { /* classify via errors.Is/As -> status + slog */ } } ``` Set **all four** `http.Server` timeouts (`ReadHeaderTimeout`, `ReadTimeout`, `WriteTimeout`, `IdleTimeout`) — an unbounded read is a Slowloris DoS. Graceful shutdown on signal: `signal.NotifyContext(ctx, os.Interrupt, syscall.SIGTERM)`, then `srv.Shutdown(shutdownCtx)` on `<-ctx.Done()`. Routing patterns, chi vs stdlib, the full middleware chain (request-id, slog, panic-recovery, timeout), config, timeout values, functional-options server, and JSON helpers -> `references/http-services.md`. ## Project layout ```text cmd/api/main.go # entrypoint: wiring only internal/handler/ # HTTP adapters internal/service/ # business logic; defines the interfaces it needs internal/repository/ # data access (pgx); implements service interfaces internal/config/ # env parsing, validation pkg/ # ONLY genuinely reusable, stable public API testdata/ # fixtures, golden files go.mod go.sum ``` Wire the layers with **constructor injection**, outermost depends inward: `repo := repository.New(db); svc := service.New(repo); h := handler.New(svc)`. Package naming: short, lowercase, no underscores, no `util`/`common`, avoid stutter (`user.User`, not `user.UserStruct`). Interfaces live on the **consumer side**: the `service` package declares `UserStore`; the `repository` package implements it without importing the interface. ## Testing (essentials) Table-driven with subtests and parallelism: ```go func TestParse(t *testing.T) { tests := []struct { name string in string wantErr error }{ {"ok", "42", nil}, {"bad", "x", ErrInvalid}, } for _, tt := range tests { // Go 1.22+: no tt := tt needed t.Run(tt.name, func(t *testing.T) { t.Parallel() _, err := Parse(tt.in) if !errors.Is(err, tt.wantErr) { // classify, not just != nil t.Fatalf("got %v, want %v", err, tt.wantErr) } }) } } ``` HTTP handlers via `httptest`: `req := httptest.NewRequest("GET", "/users/1", nil)`; `w := httptest.NewRecorder()`; `h.ServeHTTP(w, req)`; then assert on `w.Code` / `w.Body`. Use `t.Helper()` in assertions, `t.TempDir()` for files, `t.Cleanup()` for teardown, `t.Setenv()` for env. Run `go test -race -cover ./...`, and treat `go vet` / `staticcheck` failures as build failures. Stdlib `testing` is the default; reach for `testify/require` only for deep-equality or large suites. Golden files, fuzzing, benchmarks, httptest matrices, interface fakes -> `references/testing.md`. ## Security (embedded) Validate at the boundary: parametrize SQL (PostgreSQL; prefer `pgx v5` over `database/sql`); cap request bodies and reject unknown fields; set a TLS floor and trust the `crypto/tls` defaults: ```go // Good // Bad: string interpolation = SQL injection. db.QueryContext(ctx, "... WHERE id = $1", id) // db.QueryContext(ctx, fmt.Sprintf("... '%s'", id)) r.Body = http.MaxBytesReader(w, r.Body, 1<<20) // 1 MiB cap dec := json.NewDecoder(r.Body) dec.DisallowUnknownFields() tlsCfg := &tls.Config{MinVersion: tls.VersionTLS12} // do not hand-pick cipher suites ``` **Server timeouts** are a DoS control - set all four (see HTTP services above). Run `govulncheck ./...` in CI; keep deps honest with `go mod tidy` + `go mod verify`. Read secrets from env / a secret manager, never log them; redact tokens with slog `ReplaceAttr`. Deeper authz/abuse review -> `secure-coding`. ## Production
Ver no GitHub
Este SKILL.md e muito grande, entao o SkillsMP mostra aqui apenas a primeira secao. Ver no GitHub