Skip to content

Commit d02cf2d

Browse files
committed
refactor: Address Sonar findings (status constants, renderer complexity)
1 parent 9f18236 commit d02cf2d

2 files changed

Lines changed: 62 additions & 46 deletions

File tree

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

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
package com.retailsvc.http;
22

3+
import static java.net.HttpURLConnection.HTTP_ACCEPTED;
4+
import static java.net.HttpURLConnection.HTTP_CREATED;
5+
import static java.net.HttpURLConnection.HTTP_NOT_FOUND;
6+
import static java.net.HttpURLConnection.HTTP_NOT_IMPLEMENTED;
7+
import static java.net.HttpURLConnection.HTTP_NO_CONTENT;
8+
import static java.net.HttpURLConnection.HTTP_OK;
9+
310
import com.retailsvc.http.internal.BodyWriter;
411
import java.io.IOException;
512
import java.io.OutputStream;
@@ -33,7 +40,7 @@ public record Response(int status, Object body, String contentType, Map<String,
3340

3441
/** {@code 204 No Content} with no body. */
3542
public static Response empty() {
36-
return new Response(204, null, null, Map.of());
43+
return new Response(HTTP_NO_CONTENT, null, null, Map.of());
3744
}
3845

3946
/** Given status, no body. Use for {@code 200 OK} no body, {@code 404}, {@code 405}, etc. */
@@ -45,45 +52,45 @@ public static Response status(int status) {
4552

4653
/** {@code 200 OK} with {@code body} serialised as JSON. */
4754
public static Response ok(Object body) {
48-
return new Response(200, body, null, Map.of());
55+
return new Response(HTTP_OK, body, null, Map.of());
4956
}
5057

5158
/** {@code 201 Created} with {@code body} serialised as JSON. */
5259
public static Response created(Object body) {
53-
return new Response(201, body, null, Map.of());
60+
return new Response(HTTP_CREATED, body, null, Map.of());
5461
}
5562

5663
/**
5764
* {@code 201 Created} with {@code body} as JSON and a {@code Location} header — the canonical
5865
* shape for a POST that creates a new resource.
5966
*/
6067
public static Response created(Object body, String location) {
61-
return new Response(201, body, null, Map.of("Location", location));
68+
return new Response(HTTP_CREATED, body, null, Map.of("Location", location));
6269
}
6370

6471
/** {@code 202 Accepted} with no body. Use for fire-and-forget async work. */
6572
public static Response accepted() {
66-
return new Response(202, null, null, Map.of());
73+
return new Response(HTTP_ACCEPTED, null, null, Map.of());
6774
}
6875

6976
/** {@code 202 Accepted} with {@code body} serialised as JSON (typically a job/poll URL). */
7077
public static Response accepted(Object body) {
71-
return new Response(202, body, null, Map.of());
78+
return new Response(HTTP_ACCEPTED, body, null, Map.of());
7279
}
7380

7481
/** {@code 404 Not Found} with no body. */
7582
public static Response notFound() {
76-
return new Response(404, null, null, Map.of());
83+
return new Response(HTTP_NOT_FOUND, null, null, Map.of());
7784
}
7885

7986
/** {@code 404 Not Found} with {@code body} serialised as JSON (e.g. a ProblemDetail). */
8087
public static Response notFound(Object body) {
81-
return new Response(404, body, null, Map.of());
88+
return new Response(HTTP_NOT_FOUND, body, null, Map.of());
8289
}
8390

8491
/** {@code 501 Not Implemented} with no body. */
8592
public static Response notImplemented() {
86-
return new Response(501, null, null, Map.of());
93+
return new Response(HTTP_NOT_IMPLEMENTED, null, null, Map.of());
8794
}
8895

8996
/** {@code status} with {@code body} serialised by the content-type's {@link TypeMapper}. */

src/main/java/com/retailsvc/http/internal/ResponseRenderer.java

Lines changed: 46 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ public final class ResponseRenderer {
1414

1515
private static final String CONTENT_TYPE = "Content-Type";
1616
private static final String DEFAULT_JSON = "application/json";
17+
private static final String OCTET_STREAM = "application/octet-stream";
1718

1819
private final Map<String, TypeMapper> mappers;
1920

@@ -31,47 +32,55 @@ public void render(HttpExchange exchange, Response response) throws IOException
3132

3233
if (body == null) {
3334
exchange.sendResponseHeaders(status, -1);
34-
return;
35+
} else if (body instanceof BodyWriter writer) {
36+
renderStream(exchange, headers, status, response.contentType(), writer);
37+
} else {
38+
renderBytes(exchange, headers, status, response.contentType(), body);
3539
}
40+
}
41+
}
3642

37-
if (body instanceof BodyWriter writer) {
38-
long length = writer instanceof BodyWriter.Sized sized ? sized.length() : 0;
39-
if (response.contentType() != null && !headers.containsKey(CONTENT_TYPE)) {
40-
headers.add(CONTENT_TYPE, response.contentType());
41-
}
42-
exchange.sendResponseHeaders(status, length);
43-
try (OutputStream out = exchange.getResponseBody()) {
44-
writer.writeTo(out);
45-
}
46-
return;
47-
}
43+
private static void renderStream(
44+
HttpExchange exchange, Headers headers, int status, String contentType, BodyWriter writer)
45+
throws IOException {
46+
if (contentType != null && !headers.containsKey(CONTENT_TYPE)) {
47+
headers.add(CONTENT_TYPE, contentType);
48+
}
49+
long length = writer instanceof BodyWriter.Sized sized ? sized.length() : 0;
50+
exchange.sendResponseHeaders(status, length);
51+
try (OutputStream out = exchange.getResponseBody()) {
52+
writer.writeTo(out);
53+
}
54+
}
4855

49-
byte[] bytes;
50-
String contentType = response.contentType();
51-
if (body instanceof byte[] raw) {
52-
bytes = raw;
53-
if (contentType == null) {
54-
contentType = "application/octet-stream";
55-
}
56-
} else {
57-
if (contentType == null) {
58-
contentType = DEFAULT_JSON;
59-
}
60-
TypeMapper mapper = mappers.get(contentType.toLowerCase(Locale.ROOT));
61-
if (mapper == null) {
62-
throw new IllegalStateException("No TypeMapper registered for " + contentType);
63-
}
64-
bytes = mapper.writeTo(body);
65-
}
66-
if (!headers.containsKey(CONTENT_TYPE)) {
67-
headers.add(CONTENT_TYPE, contentType);
68-
}
69-
exchange.sendResponseHeaders(status, bytes.length == 0 ? -1 : bytes.length);
70-
if (bytes.length > 0) {
71-
try (OutputStream out = exchange.getResponseBody()) {
72-
out.write(bytes);
73-
}
56+
private void renderBytes(
57+
HttpExchange exchange, Headers headers, int status, String contentType, Object body)
58+
throws IOException {
59+
byte[] bytes;
60+
String effectiveContentType;
61+
if (body instanceof byte[] raw) {
62+
bytes = raw;
63+
effectiveContentType = contentType != null ? contentType : OCTET_STREAM;
64+
} else {
65+
effectiveContentType = contentType != null ? contentType : DEFAULT_JSON;
66+
bytes = serialize(body, effectiveContentType);
67+
}
68+
if (!headers.containsKey(CONTENT_TYPE)) {
69+
headers.add(CONTENT_TYPE, effectiveContentType);
70+
}
71+
exchange.sendResponseHeaders(status, bytes.length == 0 ? -1 : bytes.length);
72+
if (bytes.length > 0) {
73+
try (OutputStream out = exchange.getResponseBody()) {
74+
out.write(bytes);
7475
}
7576
}
7677
}
78+
79+
private byte[] serialize(Object body, String contentType) {
80+
TypeMapper mapper = mappers.get(contentType.toLowerCase(Locale.ROOT));
81+
if (mapper == null) {
82+
throw new IllegalStateException("No TypeMapper registered for " + contentType);
83+
}
84+
return mapper.writeTo(body);
85+
}
7786
}

0 commit comments

Comments
 (0)