feat(ADM-018): completed feature
This commit is contained in:
54
legacy/specs-old/F-003-http-foundation/DESIGN.md
Normal file
54
legacy/specs-old/F-003-http-foundation/DESIGN.md
Normal file
@@ -0,0 +1,54 @@
|
||||
# DESIGN — F-003 HTTP foundation and request context
|
||||
|
||||
## Architecture
|
||||
All cross-cutting HTTP behavior lives in explicit, readable code:
|
||||
|
||||
- `src/infrastructure/logging/logger.ts` — pino factory. JSON only, level from
|
||||
`LOG_LEVEL` (default `info`), base field `service`. Accepts an optional writable
|
||||
destination so tests can capture output without transports (no magic).
|
||||
- `src/shared/errors.ts` — `AppError` class (statusCode, code, message) and the
|
||||
single `errorEnvelope(statusCode, code, message, requestId)` builder. Envelope v2:
|
||||
`{ error: { statusCode, code, message }, requestId }`.
|
||||
- `src/shared/http-input.ts` — `parseJson(schema, input)` using zod. On failure
|
||||
throws `AppError(400, VALIDATION_ERROR)` carrying field-level issues (field paths
|
||||
only, never internal state).
|
||||
- `src/app/build-app.ts` — composition root wires everything explicitly:
|
||||
- Fastify `genReqId`: trust incoming `x-request-id` only if it matches
|
||||
`^[A-Za-z0-9._-]{1,128}$`; otherwise generate `crypto.randomUUID()`.
|
||||
- `onRequest` hook: set `x-request-id` response header, start timer.
|
||||
- `onResponse` hook: one JSON log line per request
|
||||
`{ requestId, method, url, statusCode, durationMs }`.
|
||||
- `setErrorHandler`: map `AppError` and Fastify errors to the envelope. 4xx expose
|
||||
their message; >=500 always returns generic `Internal Server Error`. Errors are
|
||||
logged with stack server-side, tagged with requestId.
|
||||
- `setNotFoundHandler`: envelope with code `NOT_FOUND` + requestId.
|
||||
- `src/infrastructure/http/server.ts` — startup log lines through the logger.
|
||||
|
||||
## Request id policy
|
||||
- Propagation is opt-in and sanitized; anything suspicious is replaced by a fresh UUID.
|
||||
- Response header name: `x-request-id`.
|
||||
|
||||
## Validation policy
|
||||
- Validation is explicit per route: call `parseJson(schema, body)` inside the handler.
|
||||
- No schema decorators, no magic body binding. The F-003 deliverable is the hook
|
||||
(helper + envelope integration), exercised by composition tests with a test-only
|
||||
route registered on the built app.
|
||||
|
||||
## Backward compatibility
|
||||
- `GET /health` response body unchanged; envelope gains fields (additive).
|
||||
|
||||
## Files
|
||||
| File | Role |
|
||||
|---|---|
|
||||
| src/infrastructure/logging/logger.ts | pino factory |
|
||||
| src/shared/errors.ts | AppError + envelope v2 |
|
||||
| src/shared/http-input.ts | zod parse helper |
|
||||
| src/app/build-app.ts | hooks, genReqId, handlers |
|
||||
| src/infrastructure/http/server.ts | startup logging |
|
||||
| src/app/tests/http-foundation.test.ts | composition tests (request id, logs, envelope, no leak) |
|
||||
| src/shared/tests/http-input.test.ts | helper unit tests |
|
||||
|
||||
## Risks / mitigations
|
||||
- Double logging (fastify internal + hooks): keep `logger: false` on Fastify, log only via explicit hooks.
|
||||
- Header injection through propagated id: strict regex + length cap.
|
||||
- 5xx leaking internals: handler returns fixed generic message for anything >=500; tests assert absence of thrown message/stack.
|
||||
Reference in New Issue
Block a user