From 5eb3e9bb4488c8ae92f064898eb101dadfab8809 Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Tue, 7 May 2024 21:12:20 +1200 Subject: [PATCH 1/2] httpclient: #427 Fix support for 204 no response body for HttpClient reading content --- .../io/avaje/http/client/BodyAdapter.java | 1 - .../io/avaje/http/client/BodyContent.java | 5 +++ .../io/avaje/http/client/DBodyContent.java | 5 +++ .../io/avaje/http/client/DBodyContentS.java | 5 +++ .../avaje/http/client/DHttpClientContext.java | 13 +++++++ .../main/java/org/example/TestController.java | 15 +++++--- .../java/org/example/TestControllerTest.java | 35 +++++++++++++++++++ 7 files changed, 74 insertions(+), 5 deletions(-) diff --git a/http-client/src/main/java/io/avaje/http/client/BodyAdapter.java b/http-client/src/main/java/io/avaje/http/client/BodyAdapter.java index 5cb9f2ee3..ef6ee6de3 100644 --- a/http-client/src/main/java/io/avaje/http/client/BodyAdapter.java +++ b/http-client/src/main/java/io/avaje/http/client/BodyAdapter.java @@ -23,7 +23,6 @@ public interface BodyAdapter { * @param type The type of the bean this writer is for */ default BodyWriter beanWriter(Type type) { - throw new UnsupportedOperationException("java.lang.reflect.Type is not supported for this adapter"); } diff --git a/http-client/src/main/java/io/avaje/http/client/BodyContent.java b/http-client/src/main/java/io/avaje/http/client/BodyContent.java index 2f95d166a..6e6f0e42d 100644 --- a/http-client/src/main/java/io/avaje/http/client/BodyContent.java +++ b/http-client/src/main/java/io/avaje/http/client/BodyContent.java @@ -56,4 +56,9 @@ static BodyContent asJson(byte[] content) { * Return the content as UTF8 string. */ String contentAsUtf8(); + + /** + * Return true if the content is empty. + */ + boolean isEmpty(); } diff --git a/http-client/src/main/java/io/avaje/http/client/DBodyContent.java b/http-client/src/main/java/io/avaje/http/client/DBodyContent.java index eec7affaa..5770c05dd 100644 --- a/http-client/src/main/java/io/avaje/http/client/DBodyContent.java +++ b/http-client/src/main/java/io/avaje/http/client/DBodyContent.java @@ -36,6 +36,11 @@ public byte[] content() { return content; } + @Override + public boolean isEmpty() { + return content.length == 0; + } + @Override public String contentAsUtf8() { return new String(content, StandardCharsets.UTF_8); diff --git a/http-client/src/main/java/io/avaje/http/client/DBodyContentS.java b/http-client/src/main/java/io/avaje/http/client/DBodyContentS.java index c553a3721..664adeff9 100644 --- a/http-client/src/main/java/io/avaje/http/client/DBodyContentS.java +++ b/http-client/src/main/java/io/avaje/http/client/DBodyContentS.java @@ -25,6 +25,11 @@ public byte[] content() { return content.getBytes(StandardCharsets.UTF_8); } + @Override + public boolean isEmpty() { + return content == null || content.isEmpty(); + } + @Override public String contentAsUtf8() { return content; diff --git a/http-client/src/main/java/io/avaje/http/client/DHttpClientContext.java b/http-client/src/main/java/io/avaje/http/client/DHttpClientContext.java index f7a240be3..1d2934285 100644 --- a/http-client/src/main/java/io/avaje/http/client/DHttpClientContext.java +++ b/http-client/src/main/java/io/avaje/http/client/DHttpClientContext.java @@ -5,6 +5,7 @@ import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.time.Duration; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; @@ -274,20 +275,32 @@ BodyReader beanReader(Type type) { } T readBean(Class type, BodyContent content) { + if (content.isEmpty()) { + return null; + } return bodyAdapter.beanReader(type).read(content); } List readList(Class type, BodyContent content) { + if (content.isEmpty()) { + return Collections.emptyList(); + } return bodyAdapter.listReader(type).read(content); } @SuppressWarnings("unchecked") T readBean(Type type, BodyContent content) { + if (content.isEmpty()) { + return null; + } return (T) bodyAdapter.beanReader(type).read(content); } @SuppressWarnings("unchecked") List readList(Type type, BodyContent content) { + if (content.isEmpty()) { + return Collections.emptyList(); + } return (List) bodyAdapter.listReader(type).read(content); } diff --git a/tests/test-nima-jsonb/src/main/java/org/example/TestController.java b/tests/test-nima-jsonb/src/main/java/org/example/TestController.java index 6f8593d67..d3f0ceef6 100644 --- a/tests/test-nima-jsonb/src/main/java/org/example/TestController.java +++ b/tests/test-nima-jsonb/src/main/java/org/example/TestController.java @@ -1,10 +1,7 @@ package org.example; import java.io.InputStream; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; import io.avaje.http.api.BodyString; import io.avaje.http.api.Controller; @@ -155,4 +152,14 @@ String pattern(String patty) { String patternPlus(String plus) { return plus.toString(); } + + @Get("/maybe/{maybe}") + Person maybePerson(boolean maybe) { + return maybe ? new Person(9, "hi") : null; + } + + @Get("/maybeList/{maybe}") + List maybePersonList(boolean maybe) { + return maybe ? List.of(new Person(9, "hi")) : null; // Collections.emptyList(); + } } diff --git a/tests/test-nima-jsonb/src/test/java/org/example/TestControllerTest.java b/tests/test-nima-jsonb/src/test/java/org/example/TestControllerTest.java index 6c43551c3..1389a006c 100644 --- a/tests/test-nima-jsonb/src/test/java/org/example/TestControllerTest.java +++ b/tests/test-nima-jsonb/src/test/java/org/example/TestControllerTest.java @@ -3,6 +3,7 @@ import static org.assertj.core.api.Assertions.assertThat; import java.net.http.HttpResponse; +import java.util.List; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; @@ -121,4 +122,38 @@ void ithrowIllegalStateException() { assertThat(res.statusCode()).isEqualTo(503); } + + @Test + void testNoBodyResponse() { + HttpResponse res = client.request() + .path("test/maybe/true") + .GET().as(Person.class); + + assertThat(res.statusCode()).isEqualTo(200); + assertThat(res.body().name()).isEqualTo("hi"); + + HttpResponse resNoBody = client.request() + .path("test/maybe/false") + .GET().as(Person.class); + + assertThat(resNoBody.statusCode()).isEqualTo(204); + assertThat(resNoBody.body()).isNull(); + } + + @Test + void testNoBodyListResponse() { + HttpResponse> res = client.request() + .path("test/maybeList/true") + .GET().asList(Person.class); + + assertThat(res.statusCode()).isEqualTo(200); + assertThat(res.body().getFirst().name()).isEqualTo("hi"); + + HttpResponse> resNoBody = client.request() + .path("test/maybeList/false") + .GET().asList(Person.class); + + assertThat(resNoBody.statusCode()).isEqualTo(204); + assertThat(resNoBody.body()).isEmpty(); + } } From 37b1c4100b0ed0a084cc74939eba7d0b38828c66 Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Tue, 7 May 2024 21:22:20 +1200 Subject: [PATCH 2/2] Update test NimaProcessorTest to use release 21 --- .../test/java/io/avaje/http/generator/NimaProcessorTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test-nima-jsonb/src/test/java/io/avaje/http/generator/NimaProcessorTest.java b/tests/test-nima-jsonb/src/test/java/io/avaje/http/generator/NimaProcessorTest.java index 963e3b91f..8f22b8177 100644 --- a/tests/test-nima-jsonb/src/test/java/io/avaje/http/generator/NimaProcessorTest.java +++ b/tests/test-nima-jsonb/src/test/java/io/avaje/http/generator/NimaProcessorTest.java @@ -45,7 +45,7 @@ void runAnnotationProcessor() throws Exception { final var task = compiler.getTask( - new PrintWriter(System.out), null, null, List.of("--release=20", "-AdisableDirectWrites=true"), null, files); + new PrintWriter(System.out), null, null, List.of("--release=21", "-AdisableDirectWrites=true"), null, files); task.setProcessors(List.of(new HelidonProcessor())); assertThat(task.call()).isTrue(); @@ -61,7 +61,7 @@ void runAnnotationProcessorWithJsonB() throws Exception { final var task = compiler.getTask( - new PrintWriter(System.out), null, null, List.of("--release=19"), null, files); + new PrintWriter(System.out), null, null, List.of("--release=21"), null, files); task.setProcessors(List.of(new HelidonProcessor())); assertThat(task.call()).isTrue();