Skip to content

Commit 8b29ab0

Browse files
committed
refactor: Extract helpers in OpenApiServer to reduce cognitive complexity
1 parent fd83072 commit 8b29ab0

1 file changed

Lines changed: 131 additions & 77 deletions

File tree

src/main/java/com/retailsvc/http/OpenApiServer.java

Lines changed: 131 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -81,71 +81,113 @@ record HandlerConfig(
8181
throw new IllegalStateException("at least one spec binding is required");
8282
}
8383
requireNonNull(bodyMappers, "bodyMappers must not be null");
84-
ExceptionHandler exceptionHandler = handlerConfig.exceptionHandler();
8584

8685
long t0 = System.currentTimeMillis();
86+
ExceptionHandler exceptionHandler = handlerConfig.exceptionHandler();
8787

8888
InetSocketAddress socketAddress =
8989
(bindAddress == null)
9090
? new InetSocketAddress(port)
9191
: new InetSocketAddress(bindAddress, port);
92-
if (sslContext != null) {
93-
HttpsServer https = HttpsServer.create(socketAddress, 0);
94-
https.setHttpsConfigurator(new TlsHttpsConfigurator(sslContext));
95-
this.httpServer = https;
96-
} else {
97-
this.httpServer = HttpServer.create(socketAddress, 0);
98-
}
92+
this.httpServer = createHttpServer(socketAddress, sslContext);
9993
httpServer.setExecutor(newThreadPerTaskExecutor(ofVirtual().name("http-", 0).factory()));
10094

10195
ResponseRenderer renderer = new ResponseRenderer(bodyMappers);
96+
boolean anyBindingAtRoot =
97+
wireBindings(httpServer, bindings, bodyMappers, handlerConfig, exceptionHandler, renderer);
98+
wireExtras(httpServer, anyBindingAtRoot, handlerConfig.extras(), exceptionHandler, renderer);
99+
100+
httpServer.start();
101+
this.shutdownTimeoutSeconds = shutdownTimeoutSeconds;
102+
logStartup(t0);
103+
}
104+
105+
private static HttpServer createHttpServer(InetSocketAddress addr, SSLContext sslContext)
106+
throws IOException {
107+
if (sslContext != null) {
108+
HttpsServer https = HttpsServer.create(addr, 0);
109+
https.setHttpsConfigurator(new TlsHttpsConfigurator(sslContext));
110+
return https;
111+
}
112+
return HttpServer.create(addr, 0);
113+
}
102114

115+
@SuppressWarnings("java:S107")
116+
private static boolean wireBindings(
117+
HttpServer httpServer,
118+
List<SpecBinding> bindings,
119+
Map<String, TypeMapper> bodyMappers,
120+
HandlerConfig handlerConfig,
121+
ExceptionHandler exceptionHandler,
122+
ResponseRenderer renderer) {
103123
boolean anyBindingAtRoot = false;
104124
for (SpecBinding binding : bindings) {
105125
String basePath = Optional.ofNullable(binding.spec().basePath()).orElse("/");
106126
anyBindingAtRoot |= "/".equals(basePath);
107-
Map<String, Operation> operationsById =
108-
binding.spec().operations().stream()
109-
.collect(Collectors.toUnmodifiableMap(Operation::operationId, op -> op));
110-
HttpContext ctx = httpServer.createContext(basePath);
111-
ctx.getFilters()
112-
.add(
113-
new RequestPreparationFilter(
114-
binding.spec(),
115-
binding.router(),
116-
binding.validator(),
117-
bodyMappers,
118-
exceptionHandler,
119-
renderer,
120-
handlerConfig.afterHooks()));
121-
ctx.getFilters()
122-
.add(
123-
new SecurityFilter(
124-
operationsById,
125-
binding.spec().securitySchemes(),
126-
binding.spec().security(),
127-
binding.securityValidators(),
128-
handlerConfig.externalAuth()));
129-
ctx.setHandler(
130-
new DispatchHandler(
131-
binding.handlers(),
132-
handlerConfig.interceptors(),
133-
handlerConfig.decorators(),
134-
renderer));
135-
}
136-
137-
if (!anyBindingAtRoot) {
138-
ExtrasRouter extrasRouter = new ExtrasRouter(handlerConfig.extras(), renderer);
139-
HttpContext extrasCtx = httpServer.createContext("/", extrasRouter);
140-
extrasCtx.getFilters().add(new ExceptionFilter(exceptionHandler, renderer));
141-
} else if (!handlerConfig.extras().isEmpty()) {
142-
throw new IllegalStateException(
143-
"extras cannot be registered when a binding owns basePath '/'");
127+
wireBinding(
128+
httpServer, basePath, binding, bodyMappers, handlerConfig, exceptionHandler, renderer);
144129
}
145-
httpServer.start();
130+
return anyBindingAtRoot;
131+
}
146132

147-
this.shutdownTimeoutSeconds = shutdownTimeoutSeconds;
133+
@SuppressWarnings("java:S107")
134+
private static void wireBinding(
135+
HttpServer httpServer,
136+
String basePath,
137+
SpecBinding binding,
138+
Map<String, TypeMapper> bodyMappers,
139+
HandlerConfig handlerConfig,
140+
ExceptionHandler exceptionHandler,
141+
ResponseRenderer renderer) {
142+
Map<String, Operation> operationsById =
143+
binding.spec().operations().stream()
144+
.collect(Collectors.toUnmodifiableMap(Operation::operationId, op -> op));
145+
HttpContext ctx = httpServer.createContext(basePath);
146+
ctx.getFilters()
147+
.add(
148+
new RequestPreparationFilter(
149+
binding.spec(),
150+
binding.router(),
151+
binding.validator(),
152+
bodyMappers,
153+
exceptionHandler,
154+
renderer,
155+
handlerConfig.afterHooks()));
156+
ctx.getFilters()
157+
.add(
158+
new SecurityFilter(
159+
operationsById,
160+
binding.spec().securitySchemes(),
161+
binding.spec().security(),
162+
binding.securityValidators(),
163+
handlerConfig.externalAuth()));
164+
ctx.setHandler(
165+
new DispatchHandler(
166+
binding.handlers(),
167+
handlerConfig.interceptors(),
168+
handlerConfig.decorators(),
169+
renderer));
170+
}
171+
172+
private static void wireExtras(
173+
HttpServer httpServer,
174+
boolean anyBindingAtRoot,
175+
Map<String, RequestHandler> extras,
176+
ExceptionHandler exceptionHandler,
177+
ResponseRenderer renderer) {
178+
if (anyBindingAtRoot) {
179+
if (!extras.isEmpty()) {
180+
throw new IllegalStateException(
181+
"extras cannot be registered when a binding owns basePath '/'");
182+
}
183+
return;
184+
}
185+
ExtrasRouter extrasRouter = new ExtrasRouter(extras, renderer);
186+
HttpContext extrasCtx = httpServer.createContext("/", extrasRouter);
187+
extrasCtx.getFilters().add(new ExceptionFilter(exceptionHandler, renderer));
188+
}
148189

190+
private void logStartup(long t0) {
149191
String host = httpServer.getAddress().getHostString();
150192
String displayHost = host.contains(":") ? "[" + host + "]" : host;
151193
LOG.info(
@@ -384,30 +426,62 @@ public Builder extraRoute(String path, RequestHandler handler) {
384426
}
385427

386428
public OpenApiServer build() throws IOException {
429+
List<SpecBinding> effectiveBindings = resolveBindings();
430+
validateBindings(effectiveBindings);
431+
Map<String, String> seenBasePaths = rejectDuplicateBasePaths(effectiveBindings);
432+
rejectExtrasOnBasePath(seenBasePaths);
433+
434+
Map<String, TypeMapper> resolved = resolveBodyMappers(bodyMappers);
435+
ExceptionHandler effectiveExceptionHandler =
436+
exceptionHandler != null ? exceptionHandler : Handlers.defaultExceptionHandler();
437+
HandlerConfig handlerConfig =
438+
new HandlerConfig(
439+
interceptors,
440+
decorators,
441+
effectiveExceptionHandler,
442+
extras,
443+
externalAuth,
444+
List.copyOf(afterHooks));
445+
int resolvedPort = resolvePort();
446+
SSLContext sslContext =
447+
httpsCertChain != null ? PemSslContext.load(httpsCertChain, httpsPrivateKey) : null;
448+
return new OpenApiServer(
449+
effectiveBindings,
450+
resolved,
451+
handlerConfig,
452+
resolvedPort,
453+
bindAddress,
454+
shutdownTimeoutSeconds,
455+
sslContext);
456+
}
457+
458+
private List<SpecBinding> resolveBindings() {
387459
boolean usedLegacy = spec != null || handlers != null || !securityValidators.isEmpty();
388460
boolean usedAddSpec = !bindings.isEmpty();
389461
if (usedLegacy && usedAddSpec) {
390462
throw new IllegalStateException(
391463
"use either spec()/handler()/securityValidator() or addSpec(), not both");
392464
}
393-
List<SpecBinding> effectiveBindings;
394465
if (usedAddSpec) {
395-
effectiveBindings = List.copyOf(bindings);
396-
} else {
397-
requireNonNull(spec, "Spec must not be null");
398-
requireNonNull(handlers, "handlers must not be null");
399-
effectiveBindings = List.of(SpecBinding.of(spec, handlers, securityValidators));
466+
return List.copyOf(bindings);
400467
}
468+
requireNonNull(spec, "Spec must not be null");
469+
requireNonNull(handlers, "handlers must not be null");
470+
return List.of(SpecBinding.of(spec, handlers, securityValidators));
471+
}
401472

473+
private void validateBindings(List<SpecBinding> effectiveBindings) {
402474
for (SpecBinding b : effectiveBindings) {
403475
if (!externalAuth) {
404476
validateSecurityWiring(b.spec(), b.securityValidators());
405477
}
406478
validateHandlerWiring(b.spec(), b.handlers());
407479
}
480+
}
408481

482+
private static Map<String, String> rejectDuplicateBasePaths(List<SpecBinding> bindings) {
409483
Map<String, String> seenBasePaths = new LinkedHashMap<>();
410-
for (SpecBinding b : effectiveBindings) {
484+
for (SpecBinding b : bindings) {
411485
String bp = Optional.ofNullable(b.spec().basePath()).orElse("/");
412486
String existingTitle = seenBasePaths.putIfAbsent(bp, b.spec().info().title());
413487
if (existingTitle != null) {
@@ -421,7 +495,10 @@ public OpenApiServer build() throws IOException {
421495
+ "'");
422496
}
423497
}
498+
return seenBasePaths;
499+
}
424500

501+
private void rejectExtrasOnBasePath(Map<String, String> seenBasePaths) {
425502
for (String path : extras.keySet()) {
426503
if (seenBasePaths.containsKey(path)) {
427504
throw new IllegalStateException(
@@ -432,29 +509,6 @@ public OpenApiServer build() throws IOException {
432509
+ "'");
433510
}
434511
}
435-
436-
Map<String, TypeMapper> resolved = resolveBodyMappers(bodyMappers);
437-
ExceptionHandler effectiveExceptionHandler =
438-
exceptionHandler != null ? exceptionHandler : Handlers.defaultExceptionHandler();
439-
HandlerConfig handlerConfig =
440-
new HandlerConfig(
441-
interceptors,
442-
decorators,
443-
effectiveExceptionHandler,
444-
extras,
445-
externalAuth,
446-
List.copyOf(afterHooks));
447-
int resolvedPort = resolvePort();
448-
SSLContext sslContext =
449-
httpsCertChain != null ? PemSslContext.load(httpsCertChain, httpsPrivateKey) : null;
450-
return new OpenApiServer(
451-
effectiveBindings,
452-
resolved,
453-
handlerConfig,
454-
resolvedPort,
455-
bindAddress,
456-
shutdownTimeoutSeconds,
457-
sslContext);
458512
}
459513

460514
private int resolvePort() {

0 commit comments

Comments
 (0)