From 5f26718d22a88173efebee56e2855c13652744db Mon Sep 17 00:00:00 2001 From: Edgar Asatryan Date: Sat, 2 Apr 2022 20:05:30 +0400 Subject: [PATCH] feat: Disk cache supports Gson as well. --- CHANGELOG.md | 4 +- .../java/io/github/nstdio/http/ext/Cache.java | 14 +- .../io/github/nstdio/http/ext/DiskCache.java | 11 +- .../http/ext/GsonMetadataSerializer.java | 283 ++++++++++++++++++ .../nstdio/http/ext/HttpHeadersBuilder.java | 7 +- .../http/ext/JacksonMetadataSerializer.java | 42 ++- .../http/ext/MetadataSerializationFields.java | 34 +++ .../nstdio/http/ext/MetadataSerializer.java | 14 + .../http/ext/DiskCacheBuilderSpiTest.kt | 18 +- .../http/ext/GsonMetadataSerializerSpiTest.kt | 26 ++ .../ext/JacksonMetadataSerializerSpiTest.kt | 26 ++ .../http/ext/MetadataSerializerContract.kt | 87 ++++++ .../http/ext/JacksonMetadataSerializerTest.kt | 3 +- 13 files changed, 529 insertions(+), 40 deletions(-) create mode 100644 src/main/java/io/github/nstdio/http/ext/GsonMetadataSerializer.java create mode 100644 src/main/java/io/github/nstdio/http/ext/MetadataSerializationFields.java create mode 100644 src/spiTest/kotlin/io/github/nstdio/http/ext/GsonMetadataSerializerSpiTest.kt create mode 100644 src/spiTest/kotlin/io/github/nstdio/http/ext/JacksonMetadataSerializerSpiTest.kt create mode 100644 src/spiTest/kotlin/io/github/nstdio/http/ext/MetadataSerializerContract.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index fecc231..650eb82 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,8 +5,10 @@ All notable changes to this project will be documented in this file. --- +### ⭐ Features + - Disk cache supports Gson as well. ([997c185](https://github.com/nstdio/http-client-ext/commit/997c185615da53bd998ae2b7e270fb6b604b563e)) ### ♻️ Improvements - - **perf** Optimize header value splitting. ([16d8f72](https://github.com/nstdio/http-client-ext/commit/16d8f72de94f9bd8de8ada9ea779ce3ba5a6be8e)) + - **perf** Optimize header value splitting. ([e1873c7](https://github.com/nstdio/http-client-ext/commit/e1873c7ab7694e15e69bec4186b0c7ce006debe5)) - **doc** Add changelog. ([9ae5bcf](https://github.com/nstdio/http-client-ext/commit/9ae5bcf5ec4730d36e037401a8da168af76ed7b4)) - **doc** Polish Javadoc. ([fbf12cc](https://github.com/nstdio/http-client-ext/commit/fbf12cc75f69e13de9f02bcdae339eb01487e165)) - **test** Add test for asserting exception in JdkCompressionFactory. ([4498f0d](https://github.com/nstdio/http-client-ext/commit/4498f0d1fa43c10e1b84ca85e9a3221666a89861)) diff --git a/src/main/java/io/github/nstdio/http/ext/Cache.java b/src/main/java/io/github/nstdio/http/ext/Cache.java index 6858862..a368d3f 100644 --- a/src/main/java/io/github/nstdio/http/ext/Cache.java +++ b/src/main/java/io/github/nstdio/http/ext/Cache.java @@ -16,8 +16,6 @@ package io.github.nstdio.http.ext; -import io.github.nstdio.http.ext.spi.Classpath; - import java.net.http.HttpRequest; import java.net.http.HttpResponse.BodySubscriber; import java.nio.ByteBuffer; @@ -46,11 +44,7 @@ static InMemoryCacheBuilder newInMemoryCacheBuilder() { * @throws IllegalStateException When Jackson (a.k.a. ObjectMapper) is not in classpath. */ static DiskCacheBuilder newDiskCacheBuilder() { - if (!Classpath.isJacksonPresent()) { - throw new IllegalStateException("In order to use disk cache please add 'com.fasterxml.jackson.core:jackson-databind' to your dependencies"); - } - - return new DiskCacheBuilder(); + return new DiskCacheBuilder(MetadataSerializer.findAvailable()); } /** @@ -182,8 +176,10 @@ public Cache build() { */ class DiskCacheBuilder extends ConstrainedCacheBuilder { private Path dir; + private final MetadataSerializer serializer; - DiskCacheBuilder() { + DiskCacheBuilder(MetadataSerializer serializer) { + this.serializer = serializer; } /** @@ -204,7 +200,7 @@ public Cache build() { throw new IllegalStateException("dir cannot be null"); } - return build(new DiskCache(maxItems, size, dir)); + return build(new DiskCache(maxItems, size, serializer, dir)); } } } diff --git a/src/main/java/io/github/nstdio/http/ext/DiskCache.java b/src/main/java/io/github/nstdio/http/ext/DiskCache.java index fa509e7..fa76bba 100644 --- a/src/main/java/io/github/nstdio/http/ext/DiskCache.java +++ b/src/main/java/io/github/nstdio/http/ext/DiskCache.java @@ -36,21 +36,24 @@ import java.util.function.Consumer; import java.util.regex.Pattern; -import static io.github.nstdio.http.ext.IOUtils.*; +import static io.github.nstdio.http.ext.IOUtils.createFile; +import static io.github.nstdio.http.ext.IOUtils.delete; +import static io.github.nstdio.http.ext.IOUtils.size; class DiskCache extends SizeConstrainedCache { - private final MetadataSerializer metadataSerializer = new JacksonMetadataSerializer(); + private final MetadataSerializer metadataSerializer; private final Executor executor; private final Path dir; DiskCache(Path dir) { - this(1 << 13, -1, dir); + this(1 << 13, -1, new JacksonMetadataSerializer(), dir); } - DiskCache(int maxItems, long maxBytes, Path dir) { + DiskCache(int maxItems, long maxBytes, MetadataSerializer metadataSerializer, Path dir) { super(maxItems, maxBytes, null); addEvictionListener(this::deleteQuietly); + this.metadataSerializer = metadataSerializer; this.dir = dir; this.executor = Executors.newSingleThreadExecutor(r -> new Thread(r, "disk-cache-io")); diff --git a/src/main/java/io/github/nstdio/http/ext/GsonMetadataSerializer.java b/src/main/java/io/github/nstdio/http/ext/GsonMetadataSerializer.java new file mode 100644 index 0000000..4a493a0 --- /dev/null +++ b/src/main/java/io/github/nstdio/http/ext/GsonMetadataSerializer.java @@ -0,0 +1,283 @@ +/* + * Copyright (C) 2022 Edgar Asatryan + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.github.nstdio.http.ext; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import io.github.nstdio.http.ext.ImmutableResponseInfo.ResponseInfoBuilder; +import lombok.RequiredArgsConstructor; + +import java.io.IOException; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpHeaders; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse.ResponseInfo; +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.Clock; +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; + +import static io.github.nstdio.http.ext.MetadataSerializationFields.FIELD_NAME_CODE; +import static io.github.nstdio.http.ext.MetadataSerializationFields.FIELD_NAME_HEADERS; +import static io.github.nstdio.http.ext.MetadataSerializationFields.FIELD_NAME_REQUEST; +import static io.github.nstdio.http.ext.MetadataSerializationFields.FIELD_NAME_REQUEST_TIME; +import static io.github.nstdio.http.ext.MetadataSerializationFields.FIELD_NAME_RESPONSE; +import static io.github.nstdio.http.ext.MetadataSerializationFields.FIELD_NAME_RESPONSE_TIME; +import static io.github.nstdio.http.ext.MetadataSerializationFields.FIELD_NAME_VERSION; +import static io.github.nstdio.http.ext.MetadataSerializationFields.FILED_NAME_REQUEST_METHOD; +import static io.github.nstdio.http.ext.MetadataSerializationFields.FILED_NAME_REQUEST_TIMEOUT; +import static io.github.nstdio.http.ext.MetadataSerializationFields.FILED_NAME_REQUEST_URI; +import static java.net.http.HttpRequest.BodyPublishers.noBody; + +class GsonMetadataSerializer implements MetadataSerializer { + private final Gson gson; + + GsonMetadataSerializer() { + var headers = new HttpHeadersTypeAdapter(); + var request = new HttpRequestTypeAdapter(headers); + var response = new ResponseInfoTypeAdapter(headers); + + gson = new GsonBuilder() + .disableHtmlEscaping() + .registerTypeAdapter(CacheEntryMetadata.class, new CacheEntryMetadataTypeAdapter(request, response)) + .create(); + } + + @Override + public void write(CacheEntryMetadata metadata, Path path) { + try (var out = Files.newBufferedWriter(path)) { + gson.toJson(metadata, out); + } catch (IOException ignored) { + } + } + + @Override + public CacheEntryMetadata read(Path path) { + try (var out = Files.newBufferedReader(path)) { + return gson.fromJson(out, CacheEntryMetadata.class); + } catch (IOException ignored) { + return null; + } + } + + private static class HttpHeadersTypeAdapter extends TypeAdapter { + + @Override + public void write(JsonWriter out, HttpHeaders value) throws IOException { + out.beginObject(); + + for (var entry : value.map().entrySet()) { + out.name(entry.getKey()).beginArray(); + + for (String headerValue : entry.getValue()) { + out.value(headerValue); + } + + out.endArray(); + } + + out.endObject(); + } + + @Override + public HttpHeaders read(JsonReader in) throws IOException { + in.beginObject(); + var builder = new HttpHeadersBuilder(); + + while (in.hasNext()) { + String name = in.nextName(); + List values = new ArrayList<>(1); + + in.beginArray(); + while (in.hasNext()) { + String value = in.nextString(); + values.add(value); + } + in.endArray(); + + builder.setTrusted(name, values); + } + + in.endObject(); + return builder.build(); + } + } + + @RequiredArgsConstructor + private static class HttpRequestTypeAdapter extends TypeAdapter { + private final TypeAdapter headersTypeAdapter; + + @Override + public void write(JsonWriter out, HttpRequest value) throws IOException { + out.beginObject(); + + out.name(FILED_NAME_REQUEST_URI).value(value.uri().toASCIIString()); + out.name(FILED_NAME_REQUEST_METHOD).value(value.method()); + + String timeoutString = value.timeout().map(Duration::toString).orElse(null); + if (timeoutString != null) { + out.name(FILED_NAME_REQUEST_TIMEOUT).value(timeoutString); + } + Integer versionOrd = value.version().map(Enum::ordinal).orElse(null); + if (versionOrd != null) { + out.name(FIELD_NAME_VERSION).value(versionOrd); + } + + out.name(FIELD_NAME_HEADERS); + headersTypeAdapter.write(out, value.headers()); + + out.endObject(); + } + + @Override + public HttpRequest read(JsonReader in) throws IOException { + in.beginObject(); + HttpRequest.Builder builder = HttpRequest.newBuilder(); + + while (in.hasNext()) { + switch (in.nextName()) { + case FILED_NAME_REQUEST_METHOD: + builder.method(in.nextString(), noBody()); + break; + case FILED_NAME_REQUEST_TIMEOUT: + builder.timeout(Duration.parse(in.nextString())); + break; + case FIELD_NAME_VERSION: + int version = in.nextInt(); + builder.version(HttpClient.Version.values()[version]); + break; + case FILED_NAME_REQUEST_URI: + builder.uri(URI.create(in.nextString())); + break; + case FIELD_NAME_HEADERS: + HttpHeaders headers = headersTypeAdapter.read(in); + headers.map().forEach((name, values) -> values.forEach(value -> builder.header(name, value))); + break; + } + } + + in.endObject(); + + return builder.build(); + } + } + + @RequiredArgsConstructor + private static class ResponseInfoTypeAdapter extends TypeAdapter { + private final TypeAdapter headersTypeAdapter; + + @Override + public void write(JsonWriter out, ResponseInfo value) throws IOException { + out.beginObject(); + + out.name(FIELD_NAME_CODE).value(value.statusCode()); + out.name(FIELD_NAME_VERSION).value(value.version().ordinal()); + + out.name(FIELD_NAME_HEADERS); + headersTypeAdapter.write(out, value.headers()); + + out.endObject(); + } + + @Override + public ResponseInfo read(JsonReader in) throws IOException { + in.beginObject(); + ResponseInfoBuilder builder = ImmutableResponseInfo.builder(); + String fieldName; + + while (in.hasNext()) { + fieldName = in.nextName(); + + switch (fieldName) { + case FIELD_NAME_CODE: + builder.statusCode(in.nextInt()); + break; + case FIELD_NAME_VERSION: + int version = in.nextInt(); + builder.version(HttpClient.Version.values()[version]); + break; + case FIELD_NAME_HEADERS: + HttpHeaders headers = headersTypeAdapter.read(in); + builder.headers(headers); + break; + } + } + + in.endObject(); + + return builder.build(); + } + } + + @RequiredArgsConstructor + private static class CacheEntryMetadataTypeAdapter extends TypeAdapter { + private final TypeAdapter requestTypeAdapter; + private final TypeAdapter responseTypeAdapter; + + @Override + public void write(JsonWriter out, CacheEntryMetadata value) throws IOException { + out.beginObject(); + + out.name(FIELD_NAME_REQUEST_TIME).value(value.requestTime()); + out.name(FIELD_NAME_RESPONSE_TIME).value(value.responseTime()); + + out.name(FIELD_NAME_REQUEST); + requestTypeAdapter.write(out, value.request()); + + out.name(FIELD_NAME_RESPONSE); + responseTypeAdapter.write(out, value.response()); + + out.endObject(); + } + + @Override + public CacheEntryMetadata read(JsonReader in) throws IOException { + in.beginObject(); + + long requestTime = -1; + long responseTime = -1; + HttpRequest request = null; + ResponseInfo response = null; + + while (in.hasNext()) { + switch (in.nextName()) { + case FIELD_NAME_REQUEST_TIME: + requestTime = in.nextLong(); + break; + case FIELD_NAME_RESPONSE_TIME: + responseTime = in.nextLong(); + break; + case FIELD_NAME_REQUEST: + request = requestTypeAdapter.read(in); + break; + case FIELD_NAME_RESPONSE: + response = responseTypeAdapter.read(in); + break; + } + } + + in.endObject(); + return CacheEntryMetadata.of(requestTime, responseTime, response, request, Clock.systemUTC()); + } + } +} diff --git a/src/main/java/io/github/nstdio/http/ext/HttpHeadersBuilder.java b/src/main/java/io/github/nstdio/http/ext/HttpHeadersBuilder.java index f18cca9..b80f213 100644 --- a/src/main/java/io/github/nstdio/http/ext/HttpHeadersBuilder.java +++ b/src/main/java/io/github/nstdio/http/ext/HttpHeadersBuilder.java @@ -66,7 +66,12 @@ HttpHeadersBuilder set(String name, String value) { HttpHeadersBuilder set(String name, List value) { List values = new ArrayList<>(value); - headersMap.put(name, values); + + return setTrusted(name, values); + } + + HttpHeadersBuilder setTrusted(String name, List value) { + headersMap.put(name, value); return this; } diff --git a/src/main/java/io/github/nstdio/http/ext/JacksonMetadataSerializer.java b/src/main/java/io/github/nstdio/http/ext/JacksonMetadataSerializer.java index da4e7f3..4fcf62e 100644 --- a/src/main/java/io/github/nstdio/http/ext/JacksonMetadataSerializer.java +++ b/src/main/java/io/github/nstdio/http/ext/JacksonMetadataSerializer.java @@ -18,7 +18,14 @@ import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.*; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectReader; +import com.fasterxml.jackson.databind.ObjectWriter; +import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.deser.std.StdDeserializer; import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.databind.ser.std.StdSerializer; @@ -45,18 +52,23 @@ import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; +import static io.github.nstdio.http.ext.MetadataSerializationFields.FIELD_NAME_CODE; +import static io.github.nstdio.http.ext.MetadataSerializationFields.FIELD_NAME_HEADERS; +import static io.github.nstdio.http.ext.MetadataSerializationFields.FIELD_NAME_REQUEST; +import static io.github.nstdio.http.ext.MetadataSerializationFields.FIELD_NAME_REQUEST_TIME; +import static io.github.nstdio.http.ext.MetadataSerializationFields.FIELD_NAME_RESPONSE; +import static io.github.nstdio.http.ext.MetadataSerializationFields.FIELD_NAME_RESPONSE_TIME; +import static io.github.nstdio.http.ext.MetadataSerializationFields.FIELD_NAME_VERSION; +import static io.github.nstdio.http.ext.MetadataSerializationFields.FILED_NAME_REQUEST_METHOD; +import static io.github.nstdio.http.ext.MetadataSerializationFields.FILED_NAME_REQUEST_TIMEOUT; +import static io.github.nstdio.http.ext.MetadataSerializationFields.FILED_NAME_REQUEST_URI; import static java.net.http.HttpRequest.BodyPublishers.noBody; -import static java.nio.file.StandardOpenOption.*; +import static java.nio.file.StandardOpenOption.CREATE; +import static java.nio.file.StandardOpenOption.READ; +import static java.nio.file.StandardOpenOption.WRITE; class JacksonMetadataSerializer implements MetadataSerializer { - private static final String FIELD_NAME_VERSION = "version"; - private static final String FIELD_NAME_REQUEST_TIME = "requestTime"; - private static final String FIELD_NAME_RESPONSE_TIME = "responseTime"; - private static final String FIELD_NAME_REQUEST = "request"; - private static final String FIELD_NAME_RESPONSE = "response"; - private static final String FIELD_NAME_HEADERS = "headers"; - private static final String FIELD_NAME_CODE = "code"; private final ObjectWriter writer; private final ObjectReader reader; @@ -180,14 +192,14 @@ static class HttpRequestSerializer extends StdSerializer { @Override public void serialize(HttpRequest value, JsonGenerator gen, SerializerProvider serializers) throws IOException { gen.writeStartObject(); - gen.writeStringField("method", value.method()); + gen.writeStringField(FILED_NAME_REQUEST_METHOD, value.method()); String timeoutString = value.timeout().map(Duration::toString).orElse(null); if (timeoutString != null) { - gen.writeStringField("timeout", timeoutString); + gen.writeStringField(FILED_NAME_REQUEST_TIMEOUT, timeoutString); } - gen.writeStringField("uri", value.uri().toASCIIString()); + gen.writeStringField(FILED_NAME_REQUEST_URI, value.uri().toASCIIString()); Integer versionOrd = value.version().map(Enum::ordinal).orElse(null); if (versionOrd != null) { gen.writeNumberField(FIELD_NAME_VERSION, versionOrd); @@ -214,10 +226,10 @@ public HttpRequest deserialize(JsonParser p, DeserializationContext ctxt) throws while ((fieldName = p.nextFieldName()) != null) { switch (fieldName) { - case "method": + case FILED_NAME_REQUEST_METHOD: builder.method(p.nextTextValue(), noBody()); break; - case "timeout": + case FILED_NAME_REQUEST_TIMEOUT: String timeout = p.nextTextValue(); builder.timeout(Duration.parse(timeout)); break; @@ -225,7 +237,7 @@ public HttpRequest deserialize(JsonParser p, DeserializationContext ctxt) throws int version = p.nextIntValue(-1); builder.version(HttpClient.Version.values()[version]); break; - case "uri": + case FILED_NAME_REQUEST_URI: builder.uri(URI.create(p.nextTextValue())); break; case FIELD_NAME_HEADERS: diff --git a/src/main/java/io/github/nstdio/http/ext/MetadataSerializationFields.java b/src/main/java/io/github/nstdio/http/ext/MetadataSerializationFields.java new file mode 100644 index 0000000..94b1dba --- /dev/null +++ b/src/main/java/io/github/nstdio/http/ext/MetadataSerializationFields.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2022 Edgar Asatryan + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.github.nstdio.http.ext; + +class MetadataSerializationFields { + static final String FIELD_NAME_VERSION = "version"; + static final String FIELD_NAME_REQUEST_TIME = "requestTime"; + static final String FIELD_NAME_RESPONSE_TIME = "responseTime"; + static final String FIELD_NAME_REQUEST = "request"; + static final String FIELD_NAME_RESPONSE = "response"; + static final String FIELD_NAME_HEADERS = "headers"; + static final String FIELD_NAME_CODE = "code"; + + static final String FILED_NAME_REQUEST_METHOD = "method"; + static final String FILED_NAME_REQUEST_TIMEOUT = "timeout"; + static final String FILED_NAME_REQUEST_URI = "uri"; + + private MetadataSerializationFields() { + } +} diff --git a/src/main/java/io/github/nstdio/http/ext/MetadataSerializer.java b/src/main/java/io/github/nstdio/http/ext/MetadataSerializer.java index 7bfdc0a..98696c8 100644 --- a/src/main/java/io/github/nstdio/http/ext/MetadataSerializer.java +++ b/src/main/java/io/github/nstdio/http/ext/MetadataSerializer.java @@ -16,9 +16,23 @@ package io.github.nstdio.http.ext; +import io.github.nstdio.http.ext.spi.Classpath; + import java.nio.file.Path; interface MetadataSerializer { + static MetadataSerializer findAvailable() { + if (Classpath.isJacksonPresent()) { + return new JacksonMetadataSerializer(); + } + + if (Classpath.isGsonPresent()) { + return new GsonMetadataSerializer(); + } + + throw new IllegalStateException("In order to use disk cache please add either Jackson or Gson to your dependencies"); + } + void write(CacheEntryMetadata metadata, Path path); CacheEntryMetadata read(Path path); diff --git a/src/spiTest/kotlin/io/github/nstdio/http/ext/DiskCacheBuilderSpiTest.kt b/src/spiTest/kotlin/io/github/nstdio/http/ext/DiskCacheBuilderSpiTest.kt index 252890d..658c069 100644 --- a/src/spiTest/kotlin/io/github/nstdio/http/ext/DiskCacheBuilderSpiTest.kt +++ b/src/spiTest/kotlin/io/github/nstdio/http/ext/DiskCacheBuilderSpiTest.kt @@ -21,14 +21,14 @@ import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test internal class DiskCacheBuilderSpiTest { - @Nested - @DisabledIfOnClasspath(JACKSON) - internal inner class WithoutJacksonTest { - @Test - fun shouldDescriptiveException() { - Assertions.assertThatIllegalStateException() - .isThrownBy { Cache.newDiskCacheBuilder() } - .withMessage("In order to use disk cache please add 'com.fasterxml.jackson.core:jackson-databind' to your dependencies") - } + @Nested + @DisabledIfOnClasspath(JACKSON, GSON) + internal inner class WithoutJacksonTest { + @Test + fun shouldDescriptiveException() { + Assertions.assertThatIllegalStateException() + .isThrownBy { Cache.newDiskCacheBuilder() } + .withMessage("In order to use disk cache please add either Jackson or Gson to your dependencies") } + } } \ No newline at end of file diff --git a/src/spiTest/kotlin/io/github/nstdio/http/ext/GsonMetadataSerializerSpiTest.kt b/src/spiTest/kotlin/io/github/nstdio/http/ext/GsonMetadataSerializerSpiTest.kt new file mode 100644 index 0000000..8c52706 --- /dev/null +++ b/src/spiTest/kotlin/io/github/nstdio/http/ext/GsonMetadataSerializerSpiTest.kt @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2022 Edgar Asatryan + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.github.nstdio.http.ext + +import io.github.nstdio.http.ext.jupiter.EnabledIfOnClasspath + +@EnabledIfOnClasspath(GSON) +internal class GsonMetadataSerializerSpiTest : MetadataSerializerContract { + override fun serializer(): MetadataSerializer { + return GsonMetadataSerializer() + } +} \ No newline at end of file diff --git a/src/spiTest/kotlin/io/github/nstdio/http/ext/JacksonMetadataSerializerSpiTest.kt b/src/spiTest/kotlin/io/github/nstdio/http/ext/JacksonMetadataSerializerSpiTest.kt new file mode 100644 index 0000000..e01303f --- /dev/null +++ b/src/spiTest/kotlin/io/github/nstdio/http/ext/JacksonMetadataSerializerSpiTest.kt @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2022 Edgar Asatryan + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.github.nstdio.http.ext + +import io.github.nstdio.http.ext.jupiter.EnabledIfOnClasspath + +@EnabledIfOnClasspath(JACKSON) +internal class JacksonMetadataSerializerSpiTest : MetadataSerializerContract { + override fun serializer(): MetadataSerializer { + return JacksonMetadataSerializer() + } +} \ No newline at end of file diff --git a/src/spiTest/kotlin/io/github/nstdio/http/ext/MetadataSerializerContract.kt b/src/spiTest/kotlin/io/github/nstdio/http/ext/MetadataSerializerContract.kt new file mode 100644 index 0000000..ed416c0 --- /dev/null +++ b/src/spiTest/kotlin/io/github/nstdio/http/ext/MetadataSerializerContract.kt @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2022 Edgar Asatryan + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.github.nstdio.http.ext + +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Assertions.assertNull +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.io.TempDir +import java.net.URI +import java.net.http.HttpClient +import java.net.http.HttpRequest +import java.nio.file.Path +import java.time.Clock +import java.time.Duration + +internal interface MetadataSerializerContract { + + /** + * The metadata serializer under the test. + */ + fun serializer(): MetadataSerializer + + @Test + fun `Should return null when cannot read`(@TempDir dir: Path) { + //given + val file = dir.resolve("abc") + val ser = serializer() + + //when + val metadata = ser.read(file) + + //then + assertNull(metadata) + } + + @Test + fun `Should write and read`(@TempDir dir: Path) { + //given + val file = dir.resolve("abc") + val responseInfo = ImmutableResponseInfo.builder() + .headers( + HttpHeadersBuilder() + .add("test", "1") + .add("test", "2") + .build() + ) + .statusCode(200) + .version(HttpClient.Version.HTTP_1_1) + .build() + val request = HttpRequest.newBuilder(URI.create("https://example.com")) + .header("abc", "1") + .header("abc", "2") + .header("abcd", "11") + .header("abcd", "22") + .version(HttpClient.Version.HTTP_2) + .timeout(Duration.ofSeconds(30)) + .build() + val metadata = CacheEntryMetadata(10, 15, responseInfo, request, Clock.systemUTC()) + val ser = serializer() + + //when + ser.write(metadata, file) + val actual = ser.read(file) + + //then + assertThat(actual.requestTime()).isEqualTo(metadata.requestTime()) + assertThat(actual.responseTime()).isEqualTo(metadata.responseTime()) + assertThat(actual.request()).isEqualTo(metadata.request()) + assertThat(actual.response().statusCode()).isEqualTo(metadata.response().statusCode()) + assertThat(actual.response().version()).isEqualTo(metadata.response().version()) + assertThat(actual.response().headers()).isEqualTo(metadata.response().headers()) + } +} \ No newline at end of file diff --git a/src/test/kotlin/io/github/nstdio/http/ext/JacksonMetadataSerializerTest.kt b/src/test/kotlin/io/github/nstdio/http/ext/JacksonMetadataSerializerTest.kt index e2adc54..d0a8716 100644 --- a/src/test/kotlin/io/github/nstdio/http/ext/JacksonMetadataSerializerTest.kt +++ b/src/test/kotlin/io/github/nstdio/http/ext/JacksonMetadataSerializerTest.kt @@ -17,6 +17,7 @@ package io.github.nstdio.http.ext import io.github.nstdio.http.ext.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Assertions.assertNull import org.junit.jupiter.api.Test import org.junit.jupiter.api.io.TempDir import java.net.URI @@ -40,7 +41,7 @@ internal class JacksonMetadataSerializerTest { val metadata = ser.read(file) //then - org.junit.jupiter.api.Assertions.assertNull(metadata) + assertNull(metadata) } @Test