--- name: go-code description: Go style rules. Use before you write or review Go code. --- # Go code ## Packages and files - Put commands in `cmd/` and shared packages at the repository root beside `cmd/`. Do not add an `internal` package. - Give each package one clear responsibility and a small public API. Keep implementation types and helpers private. - Keep composition and dispatch in the main service type. Put distinct work in focused private types or standalone functions with explicit inputs. Do not spread the methods of the service type across files to make a file shorter. - Keep `main.go` limited to the entry point. Name other files for their function. Do not create `main_test.go`. - A file carries its own context. Keep each struct and all of its methods in the file that declares the struct. Give a separate responsibility its own type. - Name a file that holds a struct after that struct, in snake_case. Use the platform suffix for a platform-specific file. When platforms share the struct, put the platform details in standalone helpers. - Write all the letters of an initialism in one case. `revive` checks common initialisms. - Put entry points first, then constructors with a `new` or `New` prefix, then each struct's methods as a group, then standalone utility functions. Do not put a utility function between the methods of one type. - In each group, order functions by first use in the file. - Group related constants in one `const (...)` block and related variables in one `var (...)` block. Put an empty line between entries that have comments. ## Entry point and context - `main` creates the SIGINT/SIGTERM signal context and defers its cancellation. Pass that context through operations and let cleanup finish before `main` exits. Do not use `log.Fatal`. - Use `ctx` for a context and `cancel` for its cancellation function. Call `defer cancel()` immediately after you create the context. - Reuse `ctx` when a child context replaces its parent. Use a different name only when both contexts stay in use. Return `ctx.Err()` directly. - Derive each context from the context of the caller. When work must finish even after the caller is cancelled, such as a final write, use `context.WithoutCancel` of the caller's context. Use `context.Background()` only where no caller context exists: in `main`, and in methods that follow `io.Closer`. - Name a duration that limits one operation after that operation, and end the name with `Timeout`, so that the name says what it limits without its comment. Define it in the file that applies it, not in the file of another type. ## Errors - Handle every returned error. Do not discard an error with the blank identifier. `errcheck` checks this rule. - Reuse an existing `err` for later operations in the same scope. Use a different variable only to keep or join errors. - Use `errors.Is` and `errors.As` to examine errors. Do not compare an error directly with a sentinel, because errors can be wrapped. `errorlint` checks comparisons and type assertions on errors. - Handle a documented sentinel error explicitly when it does not show a failure. - When a function has more than one error path, wrap each returned error with the context of its operation. A function with one error path can return the error directly. `wrapcheck` checks package and interface calls, with the exceptions in `.golangci.yml`. Review local calls and the number of error paths by hand. - When deferred cleanup can fail, use a named `err` result and join the cleanup error with `errors.Join`. ## Logs and cancellation - Log with `log/slog`. Write the message as a short event in plain words, such as "receive failed". Put names, IDs, counts, and durations in attributes with short keys. Put the cause in `err`. Do not format values into the message. - Use info for normal events, warn for a failure that is retried or loses nothing, and error for a failure that loses data or stops a part. - Cancellation of a service or request context is an expected stop, not a failure. Do not log it as an error, and do not hide all errors only because the context is canceled. A joined error with an independent failure stays a failure. - Use the worker or request context for this check. A device request that reaches its own timeout while its worker is active stays a failure. - A client that cancels an HTTP request does not get a server-error response for that cancellation. - Never log secrets or tokens. ## Style - Keep function signatures on one line. Write function bodies on more than one line, also for anonymous functions, deferred cleanup, and test callbacks. - Do not use short `if` initializers. - Use a one-letter receiver name for each method. - Use field names in struct literals. Put each field on its own line when you set more than one field. This includes the cases of a table-driven test. - Put an empty line between logical blocks, such as two independent `if` statements. Put an empty line before and after loops, switches, selects, standalone blocks, and multiline declarations, assignments, literals, and calls, when there is an adjacent statement. These boundaries also apply when a loop immediately uses the declaration before it. - Keep an operation with its immediate error or result check. Keep a short declaration with its immediate use, except at the boundaries above. `wsl_v5` and `nlreturn` check part of these rules. Review the remaining logical blocks and immediate-use groups by hand. - Group imports in this order: standard library, external dependencies, project imports. `gci` checks the groups. - Use GNU-style command flags: two dashes for long flags, such as `--cwd`, and one dash only for one-letter flags, such as `-h`. - Prefer the Go standard library. Prefer Go over cgo. - Do not add compile-time interface assertions. Prove compatibility through actual assignments and calls. - Use `// nolint: name` for lint directives. ## Dependencies - After a change to `go.mod` or `go.sum`, update `vendorHash` in the Nix package. ## Tests - Add tests for behavior changes. Do not add tests that only repeat implementation details, and do not add a test file without behavior worth a test. - Put tests in a file that matches the tested source file. - Use `testify/require` for assertions. `testifylint` checks the assertion forms. - Name a table-driven case slice `testCases` and its range variable `tc`. A subtest name describes the behavior, not a generic outcome or input. - In a test file, put constants immediately after the imports, then the tests, then supporting types, methods, and helpers. Keep each supporting struct with its methods. - Keep a helper that only one test file uses in that file. Put a helper that more than one test file uses in a file without tests. Name that file for what it holds, and end the name with `_helper_test.go`. - Supply valid required dependencies in tests. Do not make a dependency optional or add a nil fallback only to make a test simpler.