From eec22a8d46bcb89a5eb65af6d994947ee5ded98d Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Fri, 19 Apr 2024 14:25:12 -0400 Subject: [PATCH 1/3] adds moshi adapter --- avaje-http-client-moshi/pom.xml | 37 +++++ .../http/client/moshi/MoshiBodyAdapter.java | 139 ++++++++++++++++++ .../src/main/java/module-info.java | 7 + .../java/io/avaje/http/client/moshi/Foo.java | 9 ++ .../client/moshi/MoshiBodyAdapterTest.java | 58 ++++++++ .../src/main/java/module-info.java | 4 +- pom.xml | 1 + 7 files changed, 253 insertions(+), 2 deletions(-) create mode 100644 avaje-http-client-moshi/pom.xml create mode 100644 avaje-http-client-moshi/src/main/java/io/avaje/http/client/moshi/MoshiBodyAdapter.java create mode 100644 avaje-http-client-moshi/src/main/java/module-info.java create mode 100644 avaje-http-client-moshi/src/test/java/io/avaje/http/client/moshi/Foo.java create mode 100644 avaje-http-client-moshi/src/test/java/io/avaje/http/client/moshi/MoshiBodyAdapterTest.java diff --git a/avaje-http-client-moshi/pom.xml b/avaje-http-client-moshi/pom.xml new file mode 100644 index 00000000..152e5dca --- /dev/null +++ b/avaje-http-client-moshi/pom.xml @@ -0,0 +1,37 @@ + + 4.0.0 + + io.avaje + avaje-http-parent + 2.4 + + avaje-http-client-moshi + + + + + com.squareup.moshi + moshi + 1.15.1 + true + + + + io.avaje + avaje-http-client + ${project.version} + provided + + + + + + io.avaje + junit + 1.4 + test + + + + + \ No newline at end of file diff --git a/avaje-http-client-moshi/src/main/java/io/avaje/http/client/moshi/MoshiBodyAdapter.java b/avaje-http-client-moshi/src/main/java/io/avaje/http/client/moshi/MoshiBodyAdapter.java new file mode 100644 index 00000000..ddd4ea58 --- /dev/null +++ b/avaje-http-client-moshi/src/main/java/io/avaje/http/client/moshi/MoshiBodyAdapter.java @@ -0,0 +1,139 @@ +package io.avaje.http.client.moshi; + +import java.io.IOException; +import java.lang.reflect.Type; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; + +import com.squareup.moshi.JsonAdapter; +import com.squareup.moshi.Moshi; +import com.squareup.moshi.Types; + +import io.avaje.http.client.BodyAdapter; +import io.avaje.http.client.BodyContent; +import io.avaje.http.client.BodyReader; +import io.avaje.http.client.BodyWriter; + +/** + * Moshi BodyAdapter to read and write beans as JSON. + * + *
{@code
+ * HttpClient.builder()
+ *     .baseUrl(baseUrl)
+ *     .bodyAdapter(new MoshiBodyAdapter())
+ *     .build();
+ *
+ * }
+ */ +public final class MoshiBodyAdapter implements BodyAdapter { + + private final Moshi moshi; + private final ConcurrentHashMap> beanWriterCache = new ConcurrentHashMap<>(); + private final ConcurrentHashMap> beanReaderCache = new ConcurrentHashMap<>(); + private final ConcurrentHashMap> listReaderCache = new ConcurrentHashMap<>(); + + /** Create passing the Moshi to use. */ + public MoshiBodyAdapter(Moshi moshi) { + this.moshi = moshi; + } + + /** Create with a default Moshi that allows unknown properties. */ + public MoshiBodyAdapter() { + this(new Moshi.Builder().build()); + } + + @SuppressWarnings("unchecked") + @Override + public BodyWriter beanWriter(Class cls) { + return (BodyWriter) + beanWriterCache.computeIfAbsent(cls, aClass -> new JWriter<>(moshi.adapter(cls))); + } + + @SuppressWarnings("unchecked") + @Override + public BodyWriter beanWriter(Type type) { + return (BodyWriter) + beanWriterCache.computeIfAbsent(type, aClass -> new JWriter<>(moshi.adapter(type))); + } + + @SuppressWarnings("unchecked") + @Override + public BodyReader beanReader(Class cls) { + return (BodyReader) + beanReaderCache.computeIfAbsent(cls, aClass -> new JReader<>(moshi.adapter(cls))); + } + + @SuppressWarnings("unchecked") + @Override + public BodyReader beanReader(Type type) { + return (BodyReader) + beanReaderCache.computeIfAbsent(type, aClass -> new JReader<>(moshi.adapter(type))); + } + + @SuppressWarnings("unchecked") + @Override + public BodyReader> listReader(Type type) { + + return (BodyReader>) + listReaderCache.computeIfAbsent( + type, + aClass -> new JReader<>(moshi.adapter(Types.newParameterizedType(List.class, type)))); + } + + @SuppressWarnings("unchecked") + @Override + public BodyReader> listReader(Class cls) { + return (BodyReader>) + listReaderCache.computeIfAbsent( + cls, + aClass -> new JReader<>(moshi.adapter(Types.newParameterizedType(List.class, cls)))); + } + + private static class JReader implements BodyReader { + + private final JsonAdapter reader; + + JReader(JsonAdapter reader) { + this.reader = reader; + } + + @Override + public T readBody(String content) { + try { + return reader.fromJson(content); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public T read(BodyContent bodyContent) { + try { + return reader.fromJson(bodyContent.contentAsUtf8()); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + + private static class JWriter implements BodyWriter { + + private final JsonAdapter writer; + + public JWriter(JsonAdapter writer) { + this.writer = writer; + } + + @Override + public BodyContent write(T bean, String contentType) { + // ignoring the requested contentType and always + // writing the body as json content + return write(bean); + } + + @Override + public BodyContent write(T bean) { + return BodyContent.of(writer.toJson(bean)); + } + } +} diff --git a/avaje-http-client-moshi/src/main/java/module-info.java b/avaje-http-client-moshi/src/main/java/module-info.java new file mode 100644 index 00000000..4c68c474 --- /dev/null +++ b/avaje-http-client-moshi/src/main/java/module-info.java @@ -0,0 +1,7 @@ +module io.avaje.http.client.gson { + + exports io.avaje.http.client.moshi; + + requires transitive io.avaje.http.client; + requires transitive com.squareup.moshi; +} diff --git a/avaje-http-client-moshi/src/test/java/io/avaje/http/client/moshi/Foo.java b/avaje-http-client-moshi/src/test/java/io/avaje/http/client/moshi/Foo.java new file mode 100644 index 00000000..ec78ac26 --- /dev/null +++ b/avaje-http-client-moshi/src/test/java/io/avaje/http/client/moshi/Foo.java @@ -0,0 +1,9 @@ +package io.avaje.http.client.moshi; + +public class Foo { + + public long id; + + public String name; + +} diff --git a/avaje-http-client-moshi/src/test/java/io/avaje/http/client/moshi/MoshiBodyAdapterTest.java b/avaje-http-client-moshi/src/test/java/io/avaje/http/client/moshi/MoshiBodyAdapterTest.java new file mode 100644 index 00000000..1dcc4649 --- /dev/null +++ b/avaje-http-client-moshi/src/test/java/io/avaje/http/client/moshi/MoshiBodyAdapterTest.java @@ -0,0 +1,58 @@ +package io.avaje.http.client.moshi; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.nio.charset.StandardCharsets; +import java.util.List; + +import org.junit.jupiter.api.Test; + +import io.avaje.http.client.BodyContent; +import io.avaje.http.client.BodyReader; +import io.avaje.http.client.BodyWriter; + +class MoshiBodyAdapterTest { + + private final MoshiBodyAdapter adapter = new MoshiBodyAdapter(); + + @Test + void beanWriter() { + + final Foo foo = new Foo(); + foo.id = 42; + foo.name = "bar"; + + final BodyWriter writer = adapter.beanWriter(Foo.class); + final BodyContent content = writer.write(foo); + + final String json = new String(content.content(), StandardCharsets.UTF_8); + assertEquals("{\"id\":42,\"name\":\"bar\"}", json); + } + + @Test + void beanReader() { + + final BodyReader reader = adapter.beanReader(Foo.class); + + final Foo read = reader.read(content("{\"id\":42, \"name\":\"bar\"}")); + assertEquals(42, read.id); + assertEquals("bar", read.name); + } + + @Test + void listReader() { + + final BodyReader> reader = adapter.listReader(Foo.class); + + final List read = + reader.read(content("[{\"id\":42, \"name\":\"bar\"},{\"id\":43, \"name\":\"baz\"}]")); + + assertEquals(2, read.size()); + assertEquals(42, read.get(0).id); + assertEquals(43, read.get(1).id); + } + + BodyContent content(String raw) { + return BodyContent.of(raw.getBytes()); + } +} diff --git a/http-client-gson-adapter/src/main/java/module-info.java b/http-client-gson-adapter/src/main/java/module-info.java index 9933af68..0a9b1f4e 100644 --- a/http-client-gson-adapter/src/main/java/module-info.java +++ b/http-client-gson-adapter/src/main/java/module-info.java @@ -2,6 +2,6 @@ exports io.avaje.http.client.gson; - requires io.avaje.http.client; - requires com.google.gson; + requires transitive io.avaje.http.client; + requires transitive com.google.gson; } diff --git a/pom.xml b/pom.xml index 93d149d0..9c58a913 100644 --- a/pom.xml +++ b/pom.xml @@ -44,6 +44,7 @@ http-generator-javalin http-generator-jex http-generator-client + avaje-http-client-moshi From dc1f638e7a258a5083d73dbb22325be475ac8d61 Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Mon, 22 Apr 2024 08:45:40 +1200 Subject: [PATCH 2/3] Move moshi module to be consistent with gson module --- {avaje-http-client-moshi => http-client-moshi-adapter}/pom.xml | 0 .../main/java/io/avaje/http/client/moshi/MoshiBodyAdapter.java | 0 .../src/main/java/module-info.java | 0 .../src/test/java/io/avaje/http/client/moshi/Foo.java | 0 .../java/io/avaje/http/client/moshi/MoshiBodyAdapterTest.java | 0 pom.xml | 2 +- 6 files changed, 1 insertion(+), 1 deletion(-) rename {avaje-http-client-moshi => http-client-moshi-adapter}/pom.xml (100%) rename {avaje-http-client-moshi => http-client-moshi-adapter}/src/main/java/io/avaje/http/client/moshi/MoshiBodyAdapter.java (100%) rename {avaje-http-client-moshi => http-client-moshi-adapter}/src/main/java/module-info.java (100%) rename {avaje-http-client-moshi => http-client-moshi-adapter}/src/test/java/io/avaje/http/client/moshi/Foo.java (100%) rename {avaje-http-client-moshi => http-client-moshi-adapter}/src/test/java/io/avaje/http/client/moshi/MoshiBodyAdapterTest.java (100%) diff --git a/avaje-http-client-moshi/pom.xml b/http-client-moshi-adapter/pom.xml similarity index 100% rename from avaje-http-client-moshi/pom.xml rename to http-client-moshi-adapter/pom.xml diff --git a/avaje-http-client-moshi/src/main/java/io/avaje/http/client/moshi/MoshiBodyAdapter.java b/http-client-moshi-adapter/src/main/java/io/avaje/http/client/moshi/MoshiBodyAdapter.java similarity index 100% rename from avaje-http-client-moshi/src/main/java/io/avaje/http/client/moshi/MoshiBodyAdapter.java rename to http-client-moshi-adapter/src/main/java/io/avaje/http/client/moshi/MoshiBodyAdapter.java diff --git a/avaje-http-client-moshi/src/main/java/module-info.java b/http-client-moshi-adapter/src/main/java/module-info.java similarity index 100% rename from avaje-http-client-moshi/src/main/java/module-info.java rename to http-client-moshi-adapter/src/main/java/module-info.java diff --git a/avaje-http-client-moshi/src/test/java/io/avaje/http/client/moshi/Foo.java b/http-client-moshi-adapter/src/test/java/io/avaje/http/client/moshi/Foo.java similarity index 100% rename from avaje-http-client-moshi/src/test/java/io/avaje/http/client/moshi/Foo.java rename to http-client-moshi-adapter/src/test/java/io/avaje/http/client/moshi/Foo.java diff --git a/avaje-http-client-moshi/src/test/java/io/avaje/http/client/moshi/MoshiBodyAdapterTest.java b/http-client-moshi-adapter/src/test/java/io/avaje/http/client/moshi/MoshiBodyAdapterTest.java similarity index 100% rename from avaje-http-client-moshi/src/test/java/io/avaje/http/client/moshi/MoshiBodyAdapterTest.java rename to http-client-moshi-adapter/src/test/java/io/avaje/http/client/moshi/MoshiBodyAdapterTest.java diff --git a/pom.xml b/pom.xml index 9c58a913..2f73594d 100644 --- a/pom.xml +++ b/pom.xml @@ -39,12 +39,12 @@ http-api-javalin http-client http-client-gson-adapter + http-client-moshi-adapter http-inject-plugin http-generator-core http-generator-javalin http-generator-jex http-generator-client - avaje-http-client-moshi From 1ca9ff6b246c4946fe42c9f54e21d3e2404492de Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Mon, 22 Apr 2024 08:48:25 +1200 Subject: [PATCH 3/3] Use UncheckedIOException and format --- .../http/client/moshi/MoshiBodyAdapter.java | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/http-client-moshi-adapter/src/main/java/io/avaje/http/client/moshi/MoshiBodyAdapter.java b/http-client-moshi-adapter/src/main/java/io/avaje/http/client/moshi/MoshiBodyAdapter.java index ddd4ea58..80e754bc 100644 --- a/http-client-moshi-adapter/src/main/java/io/avaje/http/client/moshi/MoshiBodyAdapter.java +++ b/http-client-moshi-adapter/src/main/java/io/avaje/http/client/moshi/MoshiBodyAdapter.java @@ -1,6 +1,7 @@ package io.avaje.http.client.moshi; import java.io.IOException; +import java.io.UncheckedIOException; import java.lang.reflect.Type; import java.util.List; import java.util.concurrent.ConcurrentHashMap; @@ -32,12 +33,16 @@ public final class MoshiBodyAdapter implements BodyAdapter { private final ConcurrentHashMap> beanReaderCache = new ConcurrentHashMap<>(); private final ConcurrentHashMap> listReaderCache = new ConcurrentHashMap<>(); - /** Create passing the Moshi to use. */ + /** + * Create passing the Moshi to use. + */ public MoshiBodyAdapter(Moshi moshi) { this.moshi = moshi; } - /** Create with a default Moshi that allows unknown properties. */ + /** + * Create with a default Moshi that allows unknown properties. + */ public MoshiBodyAdapter() { this(new Moshi.Builder().build()); } @@ -45,48 +50,43 @@ public MoshiBodyAdapter() { @SuppressWarnings("unchecked") @Override public BodyWriter beanWriter(Class cls) { - return (BodyWriter) - beanWriterCache.computeIfAbsent(cls, aClass -> new JWriter<>(moshi.adapter(cls))); + return (BodyWriter) beanWriterCache.computeIfAbsent(cls, aClass -> new JWriter<>(moshi.adapter(cls))); } @SuppressWarnings("unchecked") @Override public BodyWriter beanWriter(Type type) { - return (BodyWriter) - beanWriterCache.computeIfAbsent(type, aClass -> new JWriter<>(moshi.adapter(type))); + return (BodyWriter) beanWriterCache.computeIfAbsent(type, aClass -> new JWriter<>(moshi.adapter(type))); } @SuppressWarnings("unchecked") @Override public BodyReader beanReader(Class cls) { - return (BodyReader) - beanReaderCache.computeIfAbsent(cls, aClass -> new JReader<>(moshi.adapter(cls))); + return (BodyReader) beanReaderCache.computeIfAbsent(cls, aClass -> new JReader<>(moshi.adapter(cls))); } @SuppressWarnings("unchecked") @Override public BodyReader beanReader(Type type) { - return (BodyReader) - beanReaderCache.computeIfAbsent(type, aClass -> new JReader<>(moshi.adapter(type))); + return (BodyReader) beanReaderCache.computeIfAbsent(type, aClass -> new JReader<>(moshi.adapter(type))); } @SuppressWarnings("unchecked") @Override public BodyReader> listReader(Type type) { - return (BodyReader>) - listReaderCache.computeIfAbsent( - type, - aClass -> new JReader<>(moshi.adapter(Types.newParameterizedType(List.class, type)))); + listReaderCache.computeIfAbsent( + type, + aClass -> new JReader<>(moshi.adapter(Types.newParameterizedType(List.class, type)))); } @SuppressWarnings("unchecked") @Override public BodyReader> listReader(Class cls) { return (BodyReader>) - listReaderCache.computeIfAbsent( - cls, - aClass -> new JReader<>(moshi.adapter(Types.newParameterizedType(List.class, cls)))); + listReaderCache.computeIfAbsent( + cls, + aClass -> new JReader<>(moshi.adapter(Types.newParameterizedType(List.class, cls)))); } private static class JReader implements BodyReader { @@ -102,7 +102,7 @@ public T readBody(String content) { try { return reader.fromJson(content); } catch (IOException e) { - throw new RuntimeException(e); + throw new UncheckedIOException(e); } } @@ -111,7 +111,7 @@ public T read(BodyContent bodyContent) { try { return reader.fromJson(bodyContent.contentAsUtf8()); } catch (IOException e) { - throw new RuntimeException(e); + throw new UncheckedIOException(e); } } }