diff --git a/docs/REQUEST_ID_MIDDLEWARE.md b/docs/REQUEST_ID_MIDDLEWARE.md new file mode 100644 index 00000000..eafe3810 --- /dev/null +++ b/docs/REQUEST_ID_MIDDLEWARE.md @@ -0,0 +1,35 @@ +# 🆔 Request ID & Correlation Tracing Middleware + +This document specifies the Request ID tracing middleware for NotifyChain's API services (Issue #686). + +--- + +## 1. Overview + +To make requests traceable across distributed logs and debugging sessions: +1. Every incoming HTTP request resolves a validated `X-Request-ID`. +2. Client-provided `X-Request-ID` and `X-Correlation-ID` headers are validated against strict alphanumeric/hyphen constraints (`^[a-zA-Z0-9_-]{1,64}$`). +3. If valid, the client-provided ID is preserved; if missing or invalid, a secure unique ID is minted. +4. Both IDs are attached to the request context for logging and echoed in HTTP response headers. + +--- + +## 2. Header Contract + +| Header Name | Required | Behavior | +|---|---|---| +| `X-Request-ID` | Optional on Request | Validated client ID or minted UUID, echoed on response | +| `X-Correlation-ID` | Optional on Request | Multi-service trace ID, echoed on response | + +--- + +## 3. Usage & Integration + +```typescript +import { applyRequestContext } from '../utils/request-id'; + +export function handleHttpRequest(req: IncomingMessage, res: ServerResponse) { + const { requestId, correlationId } = applyRequestContext(req, res); + logger.info(`Received request`, { requestId, correlationId, url: req.url }); +} +``` diff --git a/listener/src/middleware/request-id.test.ts b/listener/src/middleware/request-id.test.ts new file mode 100644 index 00000000..aa3f7b57 --- /dev/null +++ b/listener/src/middleware/request-id.test.ts @@ -0,0 +1,69 @@ +import { IncomingMessage, ServerResponse } from 'http'; +import { attachRequestId, isValidRequestId, resolveRequestId } from './request-id'; + +describe('Request ID Middleware (Issue #686)', () => { + describe('isValidRequestId', () => { + test('accepts valid UUIDs and alphanumeric tokens', () => { + expect(isValidRequestId('c9bf9e57-1685-4c89-bafb-ff5af830be8a')).toBe(true); + expect(isValidRequestId('req-123456_abc')).toBe(true); + expect(isValidRequestId('simpleid')).toBe(true); + }); + + test('rejects empty, invalid characters or oversized IDs', () => { + expect(isValidRequestId('')).toBe(false); + expect(isValidRequestId(' ')).toBe(false); + expect(isValidRequestId('req