Skip to content

Commit

Permalink
add test to ensure supplied JSON deserializer is used when available
Browse files Browse the repository at this point in the history
  • Loading branch information
Pritham Marupaka committed Jan 10, 2025
1 parent 7dac6ee commit bcbf495
Show file tree
Hide file tree
Showing 2 changed files with 93 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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() {}
Expand Down Expand Up @@ -84,9 +91,17 @@ private static boolean shouldIncludeArgInParameters(Arg<?> arg) {
public static final class TypeReturningStubEncoding implements Encoding {

private final String contentType;
private final Function<TypeMarker<?>, Encoding.Deserializer<?>> deserializerFactory;
private final Map<TypeMarker<?>, Encoding.Deserializer<?>> deserializers = new HashMap<>();

TypeReturningStubEncoding(String contentType) {
this(contentType, typeMarker -> Encodings.json().deserializer(typeMarker));
}

TypeReturningStubEncoding(
String contentType, Function<TypeMarker<?>, Encoding.Deserializer<?>> deserializerFactory) {
this.contentType = contentType;
this.deserializerFactory = deserializerFactory;
}

@Override
Expand All @@ -97,9 +112,12 @@ public <T> Encoding.Serializer<T> serializer(TypeMarker<T> _type) {
}

@Override
@SuppressWarnings("unchecked")
public <T> Encoding.Deserializer<T> deserializer(TypeMarker<T> type) {
return input -> {
return (T) Encodings.json().deserializer(type).deserialize(input);
Deserializer<T> deserializer =
(Deserializer<T>) deserializers.computeIfAbsent(type, deserializerFactory);
return deserializer.deserialize(input);
};
}

Expand All @@ -117,5 +135,30 @@ public boolean supportsContentType(String input) {
public String toString() {
return "TypeReturningStubEncoding{" + contentType + '}';
}

@SuppressWarnings("unchecked")
public <T> Encoding.Deserializer<T> getDeserializer(TypeMarker<T> type) {
return (Deserializer<T>) deserializers.get(type);
}
}

public static final class ContentRecordingJsonDeserializer<T> implements Encoding.Deserializer<T> {
private final List<String> deserializedContent = new ArrayList<>();
private final Encoding.Deserializer<T> delegate;

ContentRecordingJsonDeserializer(TypeMarker<T> type) {
this.delegate = Encodings.json().deserializer(type);
}

public List<String> 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)));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,15 @@

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;
import com.palantir.conjure.java.api.errors.ErrorType;
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;
Expand All @@ -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)
Expand Down Expand Up @@ -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);
Expand All @@ -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<EndpointReturnBaseType> deserializerArgs = DeserializerArgs.<EndpointReturnBaseType>builder()
.baseType(new TypeMarker<>() {})
.success(new TypeMarker<ExpectedReturnValue>() {})
Expand Down Expand Up @@ -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<ErrorReturnValue> errorTypeMarker = new TypeMarker<>() {};
DeserializerArgs<EndpointReturnBaseType> deserializerArgs = DeserializerArgs.<EndpointReturnBaseType>builder()
.baseType(new TypeMarker<>() {})
.success(new TypeMarker<ExpectedReturnValue>() {})
.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)
Expand Down

0 comments on commit bcbf495

Please sign in to comment.