Skip to content

Commit 0761986

Browse files
committed
docs: Document combining interceptors and decorators in README
1 parent 48f7f0d commit 0761986

1 file changed

Lines changed: 55 additions & 0 deletions

File tree

README.md

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,61 @@ OpenApiServer.builder()
173173

174174
Each interceptor must call `next.proceed()` to continue the chain. Exceptions propagate to the library's standard `ExceptionFilter` and `ExceptionHandler` pipeline.
175175

176+
### Combining interceptors and decorators
177+
178+
The two collaborate naturally: the interceptor binds per-request context once, and the decorator reads that context when stamping response headers. Handlers stay free of cross-cutting code.
179+
180+
``` java
181+
// Per-request context populated by the interceptor, read by the decorator and handlers.
182+
ScopedValue<String> CORRELATION_ID = ScopedValue.newInstance();
183+
ScopedValue<String> TENANT_ID = ScopedValue.newInstance();
184+
185+
OpenApiServer.builder()
186+
.spec(spec)
187+
.handlers(handlers)
188+
// 1. Resolve once per request and bind to ScopedValues.
189+
.interceptor(
190+
(request, next) -> {
191+
String correlationId =
192+
Optional.ofNullable(request.header("X-Correlation-Id"))
193+
.orElseGet(() -> UUID.randomUUID().toString());
194+
String tenantId = resolveTenant(request);
195+
ScopedValue.where(CORRELATION_ID, correlationId)
196+
.where(TENANT_ID, tenantId)
197+
.call(
198+
() -> {
199+
next.proceed();
200+
return null;
201+
});
202+
})
203+
// 2. Stamp those values on every response.
204+
.responseDecorator(
205+
(request, response) -> {
206+
response.header("X-Correlation-Id", CORRELATION_ID.get());
207+
response.header("X-Tenant-Id", TENANT_ID.get());
208+
})
209+
.build();
210+
```
211+
212+
Inside any handler, `CORRELATION_ID.get()` / `TENANT_ID.get()` return the resolved values — no parameter threading, no static accessors. Because the decorator runs *inside* the interceptor's `ScopedValue` binding (decorators fire on `request.respond(...)`, which the handler calls while the interceptor's `proceed()` is still on the stack), the `get()` calls always see the bound value.
213+
214+
A handler in this setup is just business logic:
215+
216+
``` java
217+
public class GetPromotionHandler implements RequestHandler {
218+
@Override
219+
public void handle(Request request) throws IOException {
220+
String id = request.pathParams().get("id");
221+
String tenant = TENANT_ID.get();
222+
promotionService
223+
.find(tenant, id)
224+
.ifPresentOrElse(
225+
promotion -> request.respond(HTTP_OK).json(promotion),
226+
() -> request.respond(HTTP_NOT_FOUND).empty());
227+
}
228+
}
229+
```
230+
176231
### Request body content types
177232

178233
The server reads `requestBody.content` from the spec and selects a mapper by the request's media type (the bare `type/subtype` from `Content-Type`, e.g. `application/json`; lookup is case-insensitive):

0 commit comments

Comments
 (0)