Skip to content

Commit

Permalink
handle empty body
Browse files Browse the repository at this point in the history
  • Loading branch information
Pritham Marupaka committed Jan 22, 2025
1 parent ae9dfff commit 9cf53e9
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -249,14 +249,16 @@ private static final class EncodingDeserializerForEndpointRegistry<T> implements
private final ImmutableList<EncodingDeserializerContainer<? extends T>> encodings;
private final EndpointErrorDecoder<T> endpointErrorDecoder;
private final Optional<String> acceptValue;
private final Supplier<Optional<T>> emptyInstance;
private final Supplier<Optional<? extends T>> emptyInstance;
private final TypeMarker<T> token;
private final TypeMarker<? extends T> successTypeMarker;

EncodingDeserializerForEndpointRegistry(
List<Encoding> encodingsSortedByWeight,
EmptyContainerDeserializer empty,
TypeMarker<T> token,
DeserializerArgs<T> deserializersForEndpoint) {
this.successTypeMarker = deserializersForEndpoint.successType();
this.encodings = encodingsSortedByWeight.stream()
.map(encoding ->
new EncodingDeserializerContainer<>(encoding, deserializersForEndpoint.successType()))
Expand All @@ -267,7 +269,7 @@ private static final class EncodingDeserializerForEndpointRegistry<T> implements
.filter(encoding -> encoding.supportsContentType("application/json"))
.findFirst());
this.token = token;
this.emptyInstance = Suppliers.memoize(() -> empty.tryGetEmptyInstance(token));
this.emptyInstance = Suppliers.memoize(() -> empty.tryGetEmptyInstance(successTypeMarker));
// Encodings are applied to the accept header in the order of preference based on the provided list.
this.acceptValue = Optional.of(encodingsSortedByWeight.stream()
.map(Encoding::getContentType)
Expand All @@ -284,7 +286,7 @@ public T deserialize(Response response) {
// TODO(dfox): what if we get a 204 for a non-optional type???
// TODO(dfox): support http200 & body=null
// TODO(dfox): what if we were expecting an empty list but got {}?
Optional<T> maybeEmptyInstance = emptyInstance.get();
Optional<? extends T> maybeEmptyInstance = emptyInstance.get();
if (maybeEmptyInstance.isPresent()) {
return maybeEmptyInstance.get();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,13 @@

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.google.common.collect.ImmutableList;
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.api.errors.ServiceException;
import com.palantir.conjure.java.dialogue.serde.EndpointErrorTestUtils.CustomNullDeserializer;
import com.palantir.conjure.java.serialization.ObjectMappers;
import com.palantir.dialogue.BinaryRequestBody;
import com.palantir.dialogue.BodySerDe;
Expand Down Expand Up @@ -69,6 +71,24 @@ public void testRequestOptionalEmpty() {
assertThat(value).isEmpty();
}

@Test
public void testRequestCustomEmpty() {
@JsonDeserialize(using = EmptyRecord.EmptyRecordDeserializer.class)
record EmptyRecord() {
private static final class EmptyRecordDeserializer extends CustomNullDeserializer<EmptyRecord> {
@Override
public EmptyRecord create() {
return new EmptyRecord();
}
}
}
TestResponse response = new TestResponse().code(204);
BodySerDe serializers = conjureBodySerDe("application/json");
EmptyRecord value =
serializers.deserializer(new TypeMarker<EmptyRecord>() {}).deserialize(response);
assertThat(value).isNotNull();
}

private ConjureBodySerDe conjureBodySerDe(String... contentTypes) {
return new ConjureBodySerDe(
Arrays.stream(contentTypes)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
package com.palantir.conjure.java.dialogue.serde;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.palantir.conjure.java.api.errors.CheckedServiceException;
import com.palantir.dialogue.TypeMarker;
import com.palantir.logsafe.Arg;
Expand Down Expand Up @@ -58,6 +61,20 @@ abstract static class EndpointError<T> {
}
}

abstract static class CustomNullDeserializer<T> extends JsonDeserializer<T> {
public abstract T create();

@Override
public T deserialize(JsonParser _parser, DeserializationContext _ctxt) {
return create();
}

@Override
public T getNullValue(DeserializationContext _ctxt) {
return create();
}
}

record ConjureError(
@JsonProperty("errorCode") String errorCode,
@JsonProperty("errorName") String errorName,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,15 @@
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
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.CustomNullDeserializer;
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 Down Expand Up @@ -59,6 +61,20 @@
public class EndpointErrorsConjureBodySerDeTest {
private static final ObjectMapper MAPPER = ObjectMappers.newServerObjectMapper();

@Generated("by conjure-java")
private sealed interface EmptyBodyEndpointReturnBaseType permits EmptyReturnValue, ErrorReturnValue {}

@Generated("by conjure-java")
@JsonDeserialize(using = EmptyReturnValue.EmptyReturnValueDeserializer.class)
record EmptyReturnValue() implements EmptyBodyEndpointReturnBaseType {
private static final class EmptyReturnValueDeserializer extends CustomNullDeserializer<EmptyReturnValue> {
@Override
public EmptyReturnValue create() {
return new EmptyReturnValue();
}
}
}

@Generated("by conjure-java")
private sealed interface EndpointReturnBaseType permits ExpectedReturnValue, ErrorReturnValue {}

Expand All @@ -80,7 +96,8 @@ record ErrorForEndpointArgs(
@JsonProperty("complexArg") @Safe ComplexArg complexArg,
@JsonProperty("optionalArg") @Safe Optional<Integer> optionalArg) {}

static final class ErrorReturnValue extends EndpointError<ErrorForEndpointArgs> implements EndpointReturnBaseType {
static final class ErrorReturnValue extends EndpointError<ErrorForEndpointArgs>
implements EndpointReturnBaseType, EmptyBodyEndpointReturnBaseType {
@JsonCreator
ErrorReturnValue(
@JsonProperty("errorCode") String errorCode,
Expand Down Expand Up @@ -209,6 +226,24 @@ public void testDeserializeExpectedValue() {
assertThat(value).isEqualTo(new ExpectedReturnValue(expectedString));
}

@Test
public void testDeserializeEmptyBody() {
// Given
TestResponse response = new TestResponse().code(204);
BodySerDe serializers = conjureBodySerDe("application/json", "text/plain");
DeserializerArgs<EmptyBodyEndpointReturnBaseType> deserializerArgs =
DeserializerArgs.<EmptyBodyEndpointReturnBaseType>builder()
.baseType(new TypeMarker<>() {})
.success(new TypeMarker<EmptyReturnValue>() {})
.error("Default:FailedPrecondition", new TypeMarker<ErrorReturnValue>() {})
.build();
// When
EmptyBodyEndpointReturnBaseType value =
serializers.deserializer(deserializerArgs).deserialize(response);
// Then
assertThat(value).isEqualTo(new EmptyReturnValue());
}

// Ensure that the supplied JSON encoding is used when available.
@Test
public void testDeserializeWithCustomEncoding() throws JsonProcessingException {
Expand Down

0 comments on commit 9cf53e9

Please sign in to comment.