|
1 | | -# AGENTS.md |
2 | | - |
3 | | -## Project Snapshot |
4 | | -- Module: `github.com/rusq/aklapi` |
5 | | -- Language: Go |
6 | | -- Purpose: unofficial Auckland Council API wrapper/service for address lookup and rubbish/recycling collection dates. |
7 | | - |
8 | | -## Repository Map |
9 | | -- `cmd/`: executable entrypoints. |
10 | | -- `addr.go`: address lookup logic. |
11 | | -- `rubbish.go`: rubbish/recycling API logic and response shaping. |
12 | | -- `caches.go`: cache helpers. |
13 | | -- `time.go`: date/time helpers. |
14 | | -- `*_test.go`: unit tests. |
15 | | -- `test_assets/`: fixtures used by tests. |
16 | | - |
17 | | -## Development Commands |
18 | | -- Run tests: `go test ./...` |
19 | | -- Run focused tests: `go test ./... -run <Name>` |
20 | | -- Tidy dependencies: `go mod tidy` |
21 | | -- Build all packages: `go build ./...` |
22 | | - |
23 | | -## Working Conventions |
24 | | -- Prefer small, targeted changes over broad refactors. |
25 | | -- Keep API behavior backward compatible unless explicitly requested. |
26 | | -- Add or update tests for behavioral changes. |
27 | | -- Keep exported identifiers and package-level docs concise. |
28 | | - |
29 | | -## Validation Checklist |
30 | | -Before finishing a code change, run: |
31 | | -1. `go test ./...` |
32 | | -2. `go build ./...` |
33 | | - |
34 | | -If a change only affects docs or comments, note that tests/build were not required. |
| 1 | +# AGENTS.md — Coding Agent Instructions for `aklapi` |
| 2 | + |
| 3 | +This document provides guidance for agentic coding assistants operating in this repository. |
| 4 | + |
| 5 | +--- |
| 6 | + |
| 7 | +## Project Overview |
| 8 | + |
| 9 | +`aklapi` is a Go library and HTTP server that exposes Auckland Council APIs |
| 10 | +(rubbish collection schedules, property address lookup) as a simple REST service. |
| 11 | + |
| 12 | +- **Module:** `github.com/rusq/aklapi` (`go 1.25`) |
| 13 | +- **Library package:** root (`aklapi`) |
| 14 | +- **Binary:** `cmd/aklapi/` — standard HTTP server on port 8080 |
| 15 | +- **Language:** Go only — no TypeScript, JavaScript, or Node tooling |
| 16 | + |
| 17 | +--- |
| 18 | + |
| 19 | +## Build, Run & Test Commands |
| 20 | + |
| 21 | +```sh |
| 22 | +# Build the server binary |
| 23 | +go build -o server ./cmd/aklapi |
| 24 | + |
| 25 | +# Run the server (port defaults to 8080) |
| 26 | +./server |
| 27 | + |
| 28 | +# Run all tests |
| 29 | +go test ./... |
| 30 | + |
| 31 | +# Run all tests with verbose output |
| 32 | +go test -v ./... |
| 33 | + |
| 34 | +# Run a single test by name (supports regex) |
| 35 | +go test -v -run TestFunctionName ./... |
| 36 | + |
| 37 | +# Run a single test in a specific package |
| 38 | +go test -v -run TestCollectionDayDetail ./cmd/aklapi/ |
| 39 | + |
| 40 | +# Run tests with race detector |
| 41 | +go test -race ./... |
| 42 | + |
| 43 | +# Build all packages (verify compilation) |
| 44 | +go build -v ./... |
| 45 | + |
| 46 | +# Format code (use goimports, not gofmt) |
| 47 | +goimports -w . |
| 48 | + |
| 49 | +# Lint (golangci-lint with default config) |
| 50 | +golangci-lint run ./... |
| 51 | + |
| 52 | +# Docker build |
| 53 | +docker build -t aklapi . |
| 54 | + |
| 55 | +# Make targets |
| 56 | +make server # go build -o server ./cmd/aklapi |
| 57 | +make test # go test ./... -race |
| 58 | +make docker # docker build -t aklapi . |
| 59 | +``` |
| 60 | + |
| 61 | +> **To run a single test:** use `go test -v -run <TestName> <./package/path>` |
| 62 | +> Example: `go test -v -run TestNextRubbish .` |
| 63 | +
|
| 64 | +--- |
| 65 | + |
| 66 | +## Code Style Guidelines |
| 67 | + |
| 68 | +### Formatting |
| 69 | + |
| 70 | +- Use **`goimports`** (not plain `gofmt`) — it manages imports automatically. |
| 71 | +- Indentation: **tabs** (Go standard). |
| 72 | +- VS Code devcontainer is configured with `"editor.formatOnSave": true` using `goimports`. |
| 73 | +- No trailing whitespace; no blank lines at end of file. |
| 74 | + |
| 75 | +### Imports |
| 76 | + |
| 77 | +Group imports in two blocks separated by a blank line: |
| 78 | +1. Standard library |
| 79 | +2. Third-party packages |
| 80 | + |
| 81 | +```go |
| 82 | +import ( |
| 83 | + "context" |
| 84 | + "encoding/json" |
| 85 | + "net/http" |
| 86 | + |
| 87 | + "github.com/PuerkitoBio/goquery" |
| 88 | +) |
| 89 | +``` |
| 90 | + |
| 91 | +- Use blank imports only where required: `_ "time/tzdata"`, `_ "embed"`. |
| 92 | +- Never use dot imports (`.`). |
| 93 | +- Alias imports only when disambiguation is genuinely needed. |
| 94 | + |
| 95 | +### Naming Conventions |
| 96 | + |
| 97 | +| Element | Convention | Example | |
| 98 | +|---|---|---| |
| 99 | +| Exported types | PascalCase | `AddrRequest`, `RubbishCollection` | |
| 100 | +| Unexported types | camelCase | `refuseParser`, `lruCache` | |
| 101 | +| Exported functions | PascalCase | `AddressLookup`, `CollectionDayDetail` | |
| 102 | +| Unexported functions | camelCase | `fetchandparse`, `oneAddress` | |
| 103 | +| Receiver names | Short (1–2 chars) | `(r *RubbishCollection)`, `(c *lruCache[K,V])` | |
| 104 | +| Package-level vars | camelCase | `addrCache`, `defaultLoc` | |
| 105 | +| Unexported constants | camelCase | `defCacheSz`, `dateLayout` | |
| 106 | +| Acronyms | Go convention | `addrURI` (not `addrUrl`), `ID` (not `Id`) | |
| 107 | + |
| 108 | +### Types & Structs |
| 109 | + |
| 110 | +- Add JSON struct tags to all exported response types: `json:"field,omitempty"`. |
| 111 | +- Prefer pointer receivers for types that may mutate state or are large. |
| 112 | +- Use **generics** for reusable containers (see `lruCache[K comparable, V any]`). |
| 113 | +- Use a stateful parser type (struct with fields for state, error, and results) when |
| 114 | + parsing multi-step data (see `refuseParser`). |
| 115 | + |
| 116 | +### Error Handling |
| 117 | + |
| 118 | +- Always check errors: `if err != nil { return nil, err }`. |
| 119 | +- No `panic` in production code. |
| 120 | +- Use `errors.New("...")` for static error messages. |
| 121 | +- Use string concatenation (not `fmt.Sprintf`) for simple dynamic error strings: |
| 122 | + ```go |
| 123 | + errors.New("address API returned status code: " + strconv.Itoa(resp.StatusCode)) |
| 124 | + ``` |
| 125 | +- Prefer `fmt.Errorf("context: %w", err)` for wrapping errors that need context. |
| 126 | +- Use package-level sentinel errors for flow control: |
| 127 | + ```go |
| 128 | + var errSkip = errors.New("skip this date") |
| 129 | + ``` |
| 130 | +- Use `errors.Is` for sentinel error comparisons. |
| 131 | +- HTTP handlers: use `http.Error(w, msg, code)` or a typed `respond(w, body, code)` helper. |
| 132 | + |
| 133 | +### HTTP & Networking |
| 134 | + |
| 135 | +- Use **standard library `net/http` only** — no external router (no Gin, Echo, Chi). |
| 136 | +- Register routes with `http.HandleFunc` on the default mux. |
| 137 | +- Always pass context to outgoing HTTP requests: |
| 138 | + ```go |
| 139 | + req, err := http.NewRequestWithContext(ctx, http.MethodGet, uri, nil) |
| 140 | + ``` |
| 141 | +- Always `defer resp.Body.Close()` immediately after a successful response. |
| 142 | +- Decode JSON responses with `json.NewDecoder(resp.Body).Decode(&v)`. |
| 143 | + |
| 144 | +### Logging |
| 145 | + |
| 146 | +- Use **`log/slog`** for all logging — not `log.Printf`, `fmt.Println`, etc. |
| 147 | +- Prefer context-aware variants: `slog.DebugContext(ctx, ...)`, `slog.InfoContext(ctx, ...)`. |
| 148 | +- Add structured key-value pairs for observability: |
| 149 | + ```go |
| 150 | + start := time.Now() |
| 151 | + // ... operation ... |
| 152 | + slog.DebugContext(ctx, "fetched addresses", "count", len(results), "duration", time.Since(start)) |
| 153 | + ``` |
| 154 | + |
| 155 | +### Dependency Injection & Testability |
| 156 | + |
| 157 | +- Declare external URLs as **package-level `var`** (not `const`) so tests can override them: |
| 158 | + ```go |
| 159 | + var addrURI = `https://example.com/api/addresses` |
| 160 | + ``` |
| 161 | +- Inject time via a replaceable variable: `var now = time.Now`. |
| 162 | +- Injectable function-type variables enable handler testing without real upstream calls: |
| 163 | + ```go |
| 164 | + var addressLookup = aklapi.AddressLookup |
| 165 | + ``` |
| 166 | +- Restore overridden vars with `defer`: |
| 167 | + ```go |
| 168 | + old := addrURI |
| 169 | + addrURI = ts.URL |
| 170 | + defer func() { addrURI = old }() |
| 171 | + ``` |
| 172 | + |
| 173 | +--- |
| 174 | + |
| 175 | +## Testing Guidelines |
| 176 | + |
| 177 | +### Style |
| 178 | + |
| 179 | +- Use **table-driven tests** for all non-trivial functions. |
| 180 | +- Table entry struct fields: `name string`, `args`, `want`, `wantErr bool`. |
| 181 | +- Field names may be omitted for the `name` field in composite literals. |
| 182 | +- Prefer `github.com/stretchr/testify/assert` for assertions in new tests (avoid raw |
| 183 | + `reflect.DeepEqual` + `t.Errorf` patterns from older tests). |
| 184 | +- Use `t.Context()` (Go 1.24+) for context in subtests. |
| 185 | +- Use `t.Cleanup(func() {...})` for teardown instead of `defer` in the test function body |
| 186 | + when working with subtests. |
| 187 | + |
| 188 | +### HTTP Testing |
| 189 | + |
| 190 | +- Use `net/http/httptest.NewServer` to mock upstream APIs. |
| 191 | +- Use `httptest.NewRequest` + `httptest.NewRecorder` for handler unit tests. |
| 192 | + |
| 193 | +### Test Fixtures |
| 194 | + |
| 195 | +- Embed HTML fixture files with `//go:embed`: |
| 196 | + ```go |
| 197 | + //go:embed test_assets/some-page.html |
| 198 | + var fixtureHTML []byte |
| 199 | + ``` |
| 200 | +- Fixtures are refreshed by `//go:generate` directives that `curl` the live page. |
| 201 | + |
| 202 | +### Subtests |
| 203 | + |
| 204 | +- Always run subtests with `t.Run(tt.name, func(t *testing.T) { ... })`. |
| 205 | +- Use `t.Helper()` in assertion helper functions. |
| 206 | + |
| 207 | +--- |
| 208 | + |
| 209 | +## Repository Conventions |
| 210 | + |
| 211 | +- **One concern per file:** `addr.go`, `rubbish.go`, `caches.go`, `time.go`. |
| 212 | +- **Library in root, binary in `cmd/`:** follows standard Go project layout. |
| 213 | +- CI runs on `push` and `pull_request` to `master` (see `.github/workflows/go.yml`): |
| 214 | + `go build -v ./...` then `go test -v ./...`. |
| 215 | +- Docker images are published to `ffffuuu/aklapi` on GitHub Release events. |
0 commit comments