Skip to content
Open
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
4 changes: 0 additions & 4 deletions problem-gson/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,6 @@
<compilerArgs combine.children="append">
<arg>--add-reads</arg>
<arg>org.zalando.problem.gson=com.google.gson</arg>
<arg>--add-exports</arg>
<arg>com.google.gson/com.google.gson.internal=org.zalando.problem.gson</arg>
<arg>--add-exports</arg>
<arg>com.google.gson/com.google.gson.internal.bind=org.zalando.problem.gson</arg>
</compilerArgs>
</configuration>
</plugin>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.TypeAdapter;
import com.google.gson.internal.Streams;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import lombok.AllArgsConstructor;
Expand Down Expand Up @@ -49,7 +48,7 @@ public void write(final JsonWriter out, final T value) throws IOException {
}
}

Streams.write(element, out);
gson.getAdapter(JsonElement.class).write(out, element);
}

private void flattenParameters(final JsonObject object) {
Expand All @@ -63,5 +62,4 @@ private void flattenParameters(final JsonObject object) {
public T read(final JsonReader in) throws IOException {
return delegate.read(in);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,9 @@
import org.zalando.problem.ThrowableProblem;

import java.io.IOException;
import java.net.URI;
import java.util.Map;

import static com.google.gson.internal.bind.TypeAdapters.STRING;
import static com.google.gson.internal.bind.TypeAdapters.URI;
import static java.util.Arrays.stream;
import static lombok.AccessLevel.PRIVATE;

Expand All @@ -26,7 +25,8 @@ final class DefaultProblemAdapter extends TypeAdapter<ThrowableProblem> {

private final Gson gson;
private final boolean stackTraces;
private final TypeAdapter<java.net.URI> type = URITypeAdapter.TYPE;
private final TypeAdapter<String> stringAdapter;
private final TypeAdapter<URI> uriAdapter;
private final TypeAdapter<Map<String, Object>> parameters;
private final TypeAdapter<StatusType> status;
private final TypeAdapter<ThrowableProblem> cause;
Expand All @@ -35,6 +35,8 @@ final class DefaultProblemAdapter extends TypeAdapter<ThrowableProblem> {
this(
gson,
stackTraces,
gson.getAdapter(String.class),
URITypeAdapter.TYPE,
gson.getAdapter(new TypeToken<Map<String, Object>>() {
// nothing to do here
}),
Expand All @@ -46,11 +48,11 @@ final class DefaultProblemAdapter extends TypeAdapter<ThrowableProblem> {
public void write(final JsonWriter out, final ThrowableProblem problem) throws IOException {
final JsonObject object = new JsonObject();

object.add("type", type.toJsonTree(problem.getType()));
object.add("title", STRING.toJsonTree(problem.getTitle()));
object.add("type", uriAdapter.toJsonTree(problem.getType()));
object.add("title", stringAdapter.toJsonTree(problem.getTitle()));
object.add("status", status.toJsonTree(problem.getStatus()));
object.add("detail", STRING.toJsonTree(problem.getDetail()));
object.add("instance", URI.toJsonTree(problem.getInstance()));
object.add("detail", stringAdapter.toJsonTree(problem.getDetail()));
object.add("instance", uriAdapter.toJsonTree(problem.getInstance()));
object.add("cause", cause.toJsonTree(problem.getCause()));

parameters.toJsonTree(problem.getParameters()).getAsJsonObject()
Expand All @@ -76,19 +78,19 @@ public ThrowableProblem read(final JsonReader in) throws IOException {
final String name = in.nextName();
switch (name) {
case "type":
builder.withType(URITypeAdapter.TYPE.read(in));
builder.withType(uriAdapter.read(in));
break;
case "title":
builder.withTitle(STRING.read(in));
builder.withTitle(stringAdapter.read(in));
break;
case "status":
builder.withStatus(status.read(in));
break;
case "detail":
builder.withDetail(STRING.read(in));
builder.withDetail(stringAdapter.read(in));
break;
case "instance":
builder.withInstance(URI.read(in));
builder.withInstance(uriAdapter.read(in));
break;
case "cause":
builder.withCause(cause.read(in));
Expand All @@ -102,5 +104,4 @@ public ThrowableProblem read(final JsonReader in) throws IOException {

return builder.build();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@

import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonNull;
import com.google.gson.JsonObject;
import com.google.gson.JsonSyntaxException;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.internal.Streams;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
Expand All @@ -18,6 +19,7 @@
import org.zalando.problem.StatusType;
import org.zalando.problem.ThrowableProblem;

import java.io.EOFException;
import java.io.IOException;
import java.net.URI;
import java.util.Collections;
Expand Down Expand Up @@ -134,9 +136,10 @@ private final class ProblemTypeAdapter<T> extends TypeAdapter<T> {
private final Gson gson;
private final TypeToken<T> type;
private final TypeAdapter<ThrowableProblem> defaultAdapter;
private final TypeAdapter<JsonElement> jsonElementAdapter;

ProblemTypeAdapter(final Gson gson, final TypeToken<T> type) {
this(gson, type, new DefaultProblemAdapter(gson, stackTraces));
this(gson, type, new DefaultProblemAdapter(gson, stackTraces), gson.getAdapter(JsonElement.class));
}

@Override
Expand All @@ -156,12 +159,26 @@ private TypeAdapter<T> selectAdapter(final T value) {
}

@Override
public T read(final JsonReader in) {
final JsonElement element = Streams.parse(in);
public T read(final JsonReader in) throws IOException {
final JsonElement element = parse(in);
final JsonObject problem = element.getAsJsonObject();
return selectAdapter(problem).fromJsonTree(element);
}

private JsonElement parse(final JsonReader reader) throws IOException {
boolean isEmpty = true;
try {
reader.peek();
isEmpty = false;
return jsonElementAdapter.read(reader);
} catch (final EOFException e) {
if (isEmpty) {
return JsonNull.INSTANCE;
}
throw new JsonSyntaxException(e);
}
}

@SuppressWarnings("unchecked")
private TypeAdapter<T> selectAdapter(final JsonObject problem) {
@Nullable final TypeToken<? extends Problem> subType =
Expand Down Expand Up @@ -194,5 +211,4 @@ private TypeAdapter<T> createCustomAdapter(
}

}

}
Original file line number Diff line number Diff line change
@@ -1,29 +1,5 @@
package org.zalando.problem.gson;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.stream.JsonReader;
import org.junit.jupiter.api.Test;
import org.zalando.problem.DefaultProblem;
import org.zalando.problem.Exceptional;
import org.zalando.problem.Problem;
import org.zalando.problem.ProblemBuilder;
import org.zalando.problem.Status;
import org.zalando.problem.StatusType;
import org.zalando.problem.ThrowableProblem;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.URI;
import java.net.URL;
import java.util.Collections;
import java.util.List;
import java.util.Objects;

import static com.jayway.jsonassert.JsonAssert.with;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
Expand All @@ -38,6 +14,27 @@
import static org.hobsoft.hamcrest.compose.ComposeMatchers.hasFeature;
import static org.zalando.problem.Status.BAD_REQUEST;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonSyntaxException;
import com.google.gson.stream.JsonReader;

import java.io.*;
import java.net.URI;
import java.net.URL;
import java.util.Collections;
import java.util.List;
import java.util.Objects;

import org.junit.jupiter.api.Test;
import org.zalando.problem.DefaultProblem;
import org.zalando.problem.Exceptional;
import org.zalando.problem.Problem;
import org.zalando.problem.ProblemBuilder;
import org.zalando.problem.Status;
import org.zalando.problem.StatusType;
import org.zalando.problem.ThrowableProblem;

class GsonProblemTest {

private final Gson gson = new GsonBuilder()
Expand All @@ -49,7 +46,7 @@ class GsonProblemTest {
private static JsonReader getReader(final String name) throws FileNotFoundException {
final ClassLoader loader = Thread.currentThread().getContextClassLoader();
final URL resource = Objects.requireNonNull(loader.getResource(name), () -> "resource " + name + " not found.");
return new JsonReader(new FileReader(new File(resource.getPath())));
return new JsonReader(new FileReader(resource.getPath()));
}

private String getStackTrace(final Throwable throwable) {
Expand Down Expand Up @@ -365,4 +362,28 @@ void shouldDeserializeWithProcessedStackTrace() throws IOException {
}
}

@Test
void shouldHandleEmptyJsonReader() throws IOException {
// Test the EOFException handling when JsonReader is at EOF immediately
// This tests the isEmpty=true path in the parse method
try (var reader = new JsonReader(new StringReader(""))) {
reader.setLenient(true);
final Problem problem = gson.fromJson(reader, Problem.class);

assertThat(problem, is(nullValue()));
}
}

@Test
void shouldThrowOnIncompleteJson() throws IOException {
// Test the EOFException handling when input is incomplete (non-empty but EOF encountered)
// This tests the isEmpty=false path in the parse method where JsonSyntaxException is thrown
try (var reader = new JsonReader(new StringReader("{\"type\":\"https://example.org/test\","))) {
gson.fromJson(reader, Problem.class);

assertThat("Expected JsonSyntaxException", false);
} catch (final JsonSyntaxException e) {
assertThat(e.getMessage(), notNullValue());
}
}
}
Loading