Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
import com.palantir.logsafe.Preconditions;
import com.palantir.logsafe.SafeArg;
import com.palantir.logsafe.exceptions.SafeIllegalArgumentException;
import com.palantir.logsafe.exceptions.SafeIoException;
import java.io.IOException;

// TODO(rfink): Consider async Jackson, see
Expand Down Expand Up @@ -72,7 +71,15 @@ public final <T> Serializer<T> serializer(TypeMarker<T> type) {
ObjectWriter writer = mapper.writerFor(mapper.constructType(type.getType()));
return (value, output) -> {
Preconditions.checkNotNull(value, "cannot serialize null value");
writer.writeValue(output, value);
try {
writer.writeValue(output, value);
} catch (IOException e) {
throw FrameworkException.ioFailure(
"Failed to serialize and write response",
e,
SafeArg.of("contentType", getContentType()),
SafeArg.of("type", type));
}
};
}

Expand Down Expand Up @@ -109,7 +116,7 @@ public final <T> Deserializer<T> deserializer(TypeMarker<T> type) {
SafeArg.of("contentType", getContentType()),
SafeArg.of("type", type));
} catch (IOException e) {
throw new SafeIoException(
throw FrameworkException.ioFailure(
"Failed to deserialize request",
e,
SafeArg.of("contentType", getContentType()),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@
import com.google.common.collect.ImmutableList;
import com.google.errorprone.annotations.CompileTimeConstant;
import com.palantir.conjure.java.api.errors.ErrorType;
import com.palantir.conjure.java.api.errors.ErrorType.Code;
import com.palantir.logsafe.Arg;
import com.palantir.logsafe.SafeLoggable;
import io.undertow.util.StatusCodes;
import java.util.List;
import javax.annotation.Nullable;

/** Internal type to signal a conjure protocol-level failure with a specific response code. */
final class FrameworkException extends RuntimeException implements SafeLoggable {
Expand All @@ -31,6 +33,7 @@ final class FrameworkException extends RuntimeException implements SafeLoggable
ErrorType.create(ErrorType.Code.INVALID_ARGUMENT, "Conjure:UnprocessableEntity");
private static final ErrorType UNSUPPORTED_MEDIA_TYPE =
ErrorType.create(ErrorType.Code.INVALID_ARGUMENT, "Conjure:UnsupportedMediaType");
private static final ErrorType IO_ERROR = ErrorType.create(Code.CUSTOM_CLIENT, "Conjure:Io");

private final String logMessage;
private final List<Arg<?>> arguments;
Expand All @@ -54,6 +57,11 @@ static FrameworkException unsupportedMediaType(@CompileTimeConstant String messa
return new FrameworkException(message, UNSUPPORTED_MEDIA_TYPE, StatusCodes.UNSUPPORTED_MEDIA_TYPE, null, args);
}

static FrameworkException ioFailure(
@CompileTimeConstant String message, @Nullable Throwable cause, Arg<?>... args) {
return new FrameworkException(message, IO_ERROR, IO_ERROR.httpErrorCode(), cause, args);
}

@Override
public String getLogMessage() {
return logMessage;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,51 @@ void smile_supportsContentType() {
assertThat(smile.supportsContentType("application/unknown")).isFalse();
}

@Test
void deserializeIoExceptionIsClientError() {
assertThatThrownBy(() -> deserialize(new ThrowingInputStream(), new TypeMarker<String>() {}))
.isInstanceOf(FrameworkException.class)
.hasMessageContaining("Failed to deserialize")
.matches(exception -> ((FrameworkException) exception).getStatusCode() == 400, "Expected 400 status");
}

@Test
void serializeIoExceptionIsClientError() {
assertThatThrownBy(() -> serialize("value", new ThrowingOutputStream()))
.isInstanceOf(FrameworkException.class)
.hasMessageContaining("Failed to serialize")
.matches(exception -> ((FrameworkException) exception).getStatusCode() == 400, "Expected 400 status");
}

private static final class ThrowingInputStream extends InputStream {

private static IOException fail() {
return new IOException("expected");
}

@Override
public int read() throws IOException {
throw fail();
}

@Override
public int read(byte[] _buffer, int _off, int _len) throws IOException {
throw fail();
}
}

private static final class ThrowingOutputStream extends OutputStream {

private static IOException fail() {
return new IOException("expected");
}

@Override
public void write(int _value) throws IOException {
throw fail();
}
}

private static InputStream asStream(String data) {
return new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8));
}
Expand Down