From bcbf495e90650afb39c38634e50501a417211890 Mon Sep 17 00:00:00 2001 From: Pritham Marupaka Date: Fri, 10 Jan 2025 11:23:53 -0500 Subject: [PATCH] add test to ensure supplied JSON deserializer is used when available --- .../serde/EndpointErrorTestUtils.java | 45 +++++++++++++++- .../EndpointErrorsConjureBodySerDeTest.java | 52 +++++++++++++++++-- 2 files changed, 93 insertions(+), 4 deletions(-) diff --git a/dialogue-serde/src/test/java/com/palantir/conjure/java/dialogue/serde/EndpointErrorTestUtils.java b/dialogue-serde/src/test/java/com/palantir/conjure/java/dialogue/serde/EndpointErrorTestUtils.java index 6f45ffbc4..b53de896e 100644 --- a/dialogue-serde/src/test/java/com/palantir/conjure/java/dialogue/serde/EndpointErrorTestUtils.java +++ b/dialogue-serde/src/test/java/com/palantir/conjure/java/dialogue/serde/EndpointErrorTestUtils.java @@ -21,12 +21,19 @@ import com.palantir.dialogue.TypeMarker; import com.palantir.logsafe.Arg; import com.palantir.logsafe.Safe; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Optional; import java.util.OptionalDouble; import java.util.OptionalInt; import java.util.OptionalLong; +import java.util.function.Function; final class EndpointErrorTestUtils { private EndpointErrorTestUtils() {} @@ -84,9 +91,17 @@ private static boolean shouldIncludeArgInParameters(Arg arg) { public static final class TypeReturningStubEncoding implements Encoding { private final String contentType; + private final Function, Encoding.Deserializer> deserializerFactory; + private final Map, Encoding.Deserializer> deserializers = new HashMap<>(); TypeReturningStubEncoding(String contentType) { + this(contentType, typeMarker -> Encodings.json().deserializer(typeMarker)); + } + + TypeReturningStubEncoding( + String contentType, Function, Encoding.Deserializer> deserializerFactory) { this.contentType = contentType; + this.deserializerFactory = deserializerFactory; } @Override @@ -97,9 +112,12 @@ public Encoding.Serializer serializer(TypeMarker _type) { } @Override + @SuppressWarnings("unchecked") public Encoding.Deserializer deserializer(TypeMarker type) { return input -> { - return (T) Encodings.json().deserializer(type).deserialize(input); + Deserializer deserializer = + (Deserializer) deserializers.computeIfAbsent(type, deserializerFactory); + return deserializer.deserialize(input); }; } @@ -117,5 +135,30 @@ public boolean supportsContentType(String input) { public String toString() { return "TypeReturningStubEncoding{" + contentType + '}'; } + + @SuppressWarnings("unchecked") + public Encoding.Deserializer getDeserializer(TypeMarker type) { + return (Deserializer) deserializers.get(type); + } + } + + public static final class ContentRecordingJsonDeserializer implements Encoding.Deserializer { + private final List deserializedContent = new ArrayList<>(); + private final Encoding.Deserializer delegate; + + ContentRecordingJsonDeserializer(TypeMarker type) { + this.delegate = Encodings.json().deserializer(type); + } + + public List getDeserializedContent() { + return deserializedContent; + } + + @Override + public T deserialize(InputStream input) throws IOException { + String inputString = new String(input.readAllBytes(), StandardCharsets.UTF_8); + deserializedContent.add(inputString); + return delegate.deserialize(new ByteArrayInputStream(inputString.getBytes(StandardCharsets.UTF_8))); + } } } diff --git a/dialogue-serde/src/test/java/com/palantir/conjure/java/dialogue/serde/EndpointErrorsConjureBodySerDeTest.java b/dialogue-serde/src/test/java/com/palantir/conjure/java/dialogue/serde/EndpointErrorsConjureBodySerDeTest.java index cde06f5fa..858e9df20 100644 --- a/dialogue-serde/src/test/java/com/palantir/conjure/java/dialogue/serde/EndpointErrorsConjureBodySerDeTest.java +++ b/dialogue-serde/src/test/java/com/palantir/conjure/java/dialogue/serde/EndpointErrorsConjureBodySerDeTest.java @@ -21,6 +21,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.collect.ImmutableList; import com.palantir.conjure.java.api.errors.CheckedServiceException; @@ -28,6 +29,7 @@ import com.palantir.conjure.java.api.errors.RemoteException; import com.palantir.conjure.java.api.errors.SerializableError; import com.palantir.conjure.java.dialogue.serde.EndpointErrorTestUtils.ConjureError; +import com.palantir.conjure.java.dialogue.serde.EndpointErrorTestUtils.ContentRecordingJsonDeserializer; import com.palantir.conjure.java.dialogue.serde.EndpointErrorTestUtils.EndpointError; import com.palantir.conjure.java.dialogue.serde.EndpointErrorTestUtils.TypeReturningStubEncoding; import com.palantir.conjure.java.serialization.ObjectMappers; @@ -42,11 +44,15 @@ import com.palantir.logsafe.UnsafeArg; import java.io.IOException; import java.util.Arrays; +import java.util.List; import java.util.Optional; import javax.annotation.Nullable; import javax.annotation.processing.Generated; +import org.assertj.core.api.InstanceOfAssertFactories; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import org.mockito.junit.jupiter.MockitoExtension; @ExtendWith(MockitoExtension.class) @@ -103,8 +109,10 @@ private TestEndpointError( } } - @Test - public void testDeserializeCustomError() throws IOException { + // The error should be deserialized using Encodings.json(), when a JSON encoding is not provided. + @ParameterizedTest + @ValueSource(strings = {"application/json", "text/plain"}) + public void testDeserializeCustomError(String supportedContentType) throws IOException { // Given TestEndpointError errorThrownByEndpoint = new TestEndpointError("value", "unsafeValue", new ComplexArg(1, "bar"), Optional.of(2), null); @@ -114,7 +122,7 @@ public void testDeserializeCustomError() throws IOException { TestResponse response = TestResponse.withBody(responseBody) .contentType("application/json") .code(500); - BodySerDe serializers = conjureBodySerDe("application/json", "text/plain"); + BodySerDe serializers = conjureBodySerDe(supportedContentType); DeserializerArgs deserializerArgs = DeserializerArgs.builder() .baseType(new TypeMarker<>() {}) .success(new TypeMarker() {}) @@ -201,6 +209,44 @@ public void testDeserializeExpectedValue() { assertThat(value).isEqualTo(new ExpectedReturnValue(expectedString)); } + // Ensure that the supplied JSON encoding is used when available. + @Test + public void testDeserializeWithCustomEncoding() throws JsonProcessingException { + // Given + TestEndpointError errorThrownByEndpoint = + new TestEndpointError("value", "unsafeValue", new ComplexArg(1, "bar"), Optional.of(2), null); + String responseBody = + MAPPER.writeValueAsString(ConjureError.fromCheckedServiceException(errorThrownByEndpoint)); + + TypeReturningStubEncoding stubbingEncoding = + new TypeReturningStubEncoding("application/json", ContentRecordingJsonDeserializer::new); + BodySerDe serializers = new ConjureBodySerDe( + List.of(WeightedEncoding.of(stubbingEncoding)), + Encodings.emptyContainerDeserializer(), + DefaultConjureRuntime.DEFAULT_SERDE_CACHE_SPEC); + TestResponse response = TestResponse.withBody(responseBody) + .contentType("application/json") + .code(500); + + TypeMarker errorTypeMarker = new TypeMarker<>() {}; + DeserializerArgs deserializerArgs = DeserializerArgs.builder() + .baseType(new TypeMarker<>() {}) + .success(new TypeMarker() {}) + .error("Default:FailedPrecondition", errorTypeMarker) + .build(); + + // When + serializers.deserializer(deserializerArgs).deserialize(response); + + // Then + assertThat(stubbingEncoding.getDeserializer(errorTypeMarker)) + .isInstanceOfSatisfying(ContentRecordingJsonDeserializer.class, deserializer -> { + assertThat(deserializer.getDeserializedContent()) + .asInstanceOf(InstanceOfAssertFactories.LIST) + .containsExactly(responseBody); + }); + } + private ConjureBodySerDe conjureBodySerDe(String... contentTypes) { return new ConjureBodySerDe( Arrays.stream(contentTypes)