- task-discipline: require explicit target platform/language before implementation - go-api: bounded context deadline on every outbound network/DB call - controls-selection: visible busy state for any control triggering such a call - import-export: enumerate real format variants before writing a parser - app-binary: embedded assets do not hot-reload; verify rebuild before diagnosing Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
97 lines
3.3 KiB
Markdown
97 lines
3.3 KiB
Markdown
# Contract: REST API Conventions (Go Web Applications)
|
|
|
|
Version: 1.2
|
|
|
|
## URL Naming
|
|
|
|
- Resources are plural nouns: `/api/assets`, `/api/components`, `/api/pricelists`.
|
|
- Nested resources use parent path: `/api/assets/:id/components`.
|
|
- Actions that are not CRUD use a verb suffix: `/api/pricelists/:id/export-csv`,
|
|
`/api/tasks/:id/cancel`, `/api/sync/push`.
|
|
- No verbs in resource paths: use `/api/assets` + DELETE, not `/api/delete-asset`.
|
|
|
|
## HTTP Methods and Status Codes
|
|
|
|
| Operation | Method | Success | Notes |
|
|
|-----------|--------|---------|-------|
|
|
| List | GET | 200 | |
|
|
| Get one | GET | 200 / 404 | |
|
|
| Create | POST | 201 | Return created resource |
|
|
| Update | PUT / PATCH | 200 | Return updated resource |
|
|
| Delete / Archive | DELETE | 200 or 204 | |
|
|
| Async action | POST | 202 | Return `{task_id}` |
|
|
| Validation error | POST/PUT | 422 | Return field errors |
|
|
| Server error | any | 500 | Log full error server-side |
|
|
| Not found | any | 404 | |
|
|
| Unauthorized | any | 401 | |
|
|
|
|
- Never return `200 OK` for validation errors — use `422 Unprocessable Entity`.
|
|
- Never return `200 OK` with `{"error": "..."}` in the body — use the correct status code.
|
|
|
|
## Error Response Format
|
|
|
|
All non-2xx responses return a consistent JSON body:
|
|
|
|
```json
|
|
{
|
|
"error": "human-readable message",
|
|
"fields": {
|
|
"serial_number": "Serial number is required",
|
|
"price": "Must be greater than 0"
|
|
}
|
|
}
|
|
```
|
|
|
|
- `error` — always present, describes what went wrong.
|
|
- `fields` — optional, present only for validation errors (422). Keys match form field names.
|
|
- Never expose raw Go error strings, stack traces, or SQL errors to the client.
|
|
|
|
## List Response Format
|
|
|
|
```json
|
|
{
|
|
"items": [...],
|
|
"total_count": 342,
|
|
"page": 2,
|
|
"per_page": 50,
|
|
"total_pages": 7
|
|
}
|
|
```
|
|
|
|
- Always include pagination metadata even if the client did not request it.
|
|
- `items` is always an array — never `null`, use `[]` for empty.
|
|
|
|
## Request Conventions
|
|
|
|
- Accept `application/json` for API endpoints.
|
|
- HTML form submissions (htmx) use `application/x-www-form-urlencoded` or `multipart/form-data`.
|
|
- File uploads use `multipart/form-data`.
|
|
- Query parameters for filtering and pagination: `?page=1&per_page=50&search=abc&status=active`.
|
|
|
|
## Timeouts
|
|
|
|
- Every outbound network or database call must run under a bounded `context.Context` deadline.
|
|
No call reachable from a user action may block indefinitely.
|
|
- The deadline is set by the handler or service that owns the request, not by the caller of the app.
|
|
- On timeout, return a real error status (`504` for an upstream timeout, `500` for an internal one)
|
|
with a human-readable message. Never let a timed-out call fall through as an empty success.
|
|
- The UI side of the same rule — visible busy state for the duration of the call — is owned by
|
|
`controls-selection`.
|
|
|
|
## Health and Utility Endpoints
|
|
|
|
Every application must expose:
|
|
|
|
```
|
|
GET /health → 200 {"status": "ok"}
|
|
GET /api/db-status → 200 {"ok": true} or 500 {"ok": false, "error": "..."}
|
|
```
|
|
|
|
- `/health` must respond even if DB is down (for load balancer probes).
|
|
- `/api/db-status` checks the actual DB connection and returns its state.
|
|
|
|
## Async Actions
|
|
|
|
Long-running operations return `202 {"task_id": "..."}` immediately and the client polls
|
|
`GET /api/tasks/:id`. Full flow, statuses, and task struct: `go-background-tasks` contract.
|