Skip to content

Commit

Permalink
Link to troubleshooting guide from exception messages (#2357)
Browse files Browse the repository at this point in the history
* Link to troubleshooting guide from exception messages

* Add examples to troubleshooting guide

* Use proper anchor names for troubleshooting guide
  • Loading branch information
Marcono1234 authored and eamonnmcmanus committed May 31, 2023
1 parent dd0e122 commit 48a1bd4
Show file tree
Hide file tree
Showing 26 changed files with 518 additions and 248 deletions.
115 changes: 93 additions & 22 deletions Troubleshooting.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion gson/src/main/java/com/google/gson/Gson.java
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@
* <p>See the <a href="https://github.com/google/gson/blob/master/UserGuide.md">Gson User Guide</a>
* for a more complete set of examples.</p>
*
* <h2>JSON Strictness handling</h2>
* <h2 id="default-lenient">JSON Strictness handling</h2>
* For legacy reasons most of the {@code Gson} methods allow JSON data which does not
* comply with the JSON specification when the strictness is set to {@code null} (the default value).
* To specify the {@linkplain Strictness strictness} of a {@code Gson} instance, you should set it through
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.google.gson.internal;

public class TroubleshootingGuide {
private TroubleshootingGuide() {}

/**
* Creates a URL referring to the specified troubleshooting section.
*/
public static String createUrl(String id) {
return "https://github.com/google/gson/blob/master/Troubleshooting.md#" + id;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import com.google.gson.internal.ObjectConstructor;
import com.google.gson.internal.Primitives;
import com.google.gson.internal.ReflectionAccessFilterHelper;
import com.google.gson.internal.TroubleshootingGuide;
import com.google.gson.internal.reflect.ReflectionHelper;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
Expand Down Expand Up @@ -114,7 +115,7 @@ public <T> TypeAdapter<T> create(Gson gson, final TypeToken<T> type) {
if (filterResult == FilterResult.BLOCK_ALL) {
throw new JsonIOException(
"ReflectionAccessFilter does not permit using reflection for " + raw
+ ". Register a TypeAdapter for this type or adjust the access filter.");
+ ". Register a TypeAdapter for this type or adjust the access filter.");
}
boolean blockInaccessible = filterResult == FilterResult.BLOCK_INACCESSIBLE;

Expand Down Expand Up @@ -306,7 +307,8 @@ private Map<String, BoundField> getBoundFields(Gson context, TypeToken<?> type,
if (previous != null) {
throw new IllegalArgumentException("Class " + originalRaw.getName()
+ " declares multiple JSON fields named '" + previous.name + "'; conflict is caused"
+ " by fields " + ReflectionHelper.fieldToString(previous.field) + " and " + ReflectionHelper.fieldToString(field));
+ " by fields " + ReflectionHelper.fieldToString(previous.field) + " and " + ReflectionHelper.fieldToString(field)
+ "\nSee " + TroubleshootingGuide.createUrl("duplicate-fields"));
}
}
type = TypeToken.get($Gson$Types.resolve(type.getType(), raw, raw.getGenericSuperclass()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import com.google.gson.TypeAdapterFactory;
import com.google.gson.annotations.SerializedName;
import com.google.gson.internal.LazilyParsedNumber;
import com.google.gson.internal.TroubleshootingGuide;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
Expand Down Expand Up @@ -73,12 +74,14 @@ private TypeAdapters() {
@Override
public void write(JsonWriter out, Class value) throws IOException {
throw new UnsupportedOperationException("Attempted to serialize java.lang.Class: "
+ value.getName() + ". Forgot to register a type adapter?");
+ value.getName() + ". Forgot to register a type adapter?"
+ "\nSee " + TroubleshootingGuide.createUrl("java-lang-class-unsupported"));
}
@Override
public Class read(JsonReader in) throws IOException {
throw new UnsupportedOperationException(
"Attempted to deserialize a java.lang.Class. Forgot to register a type adapter?");
"Attempted to deserialize a java.lang.Class. Forgot to register a type adapter?"
+ "\nSee " + TroubleshootingGuide.createUrl("java-lang-class-unsupported"));
}
}.nullSafe();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import com.google.gson.JsonIOException;
import com.google.gson.internal.GsonBuildConfig;
import com.google.gson.internal.TroubleshootingGuide;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
Expand All @@ -40,6 +41,17 @@ public class ReflectionHelper {

private ReflectionHelper() {}

private static String getInaccessibleTroubleshootingSuffix(Exception e) {
// Class was added in Java 9, therefore cannot use instanceof
if (e.getClass().getName().equals("java.lang.reflect.InaccessibleObjectException")) {
String message = e.getMessage();
String troubleshootingId = message != null && message.contains("to module com.google.gson")
? "reflection-inaccessible-to-module-gson" : "reflection-inaccessible";
return "\nSee " + TroubleshootingGuide.createUrl(troubleshootingId);
}
return "";
}

/**
* Internal implementation of making an {@link AccessibleObject} accessible.
*
Expand All @@ -52,7 +64,8 @@ public static void makeAccessible(AccessibleObject object) throws JsonIOExceptio
} catch (Exception exception) {
String description = getAccessibleObjectDescription(object, false);
throw new JsonIOException("Failed making " + description + " accessible; either increase its visibility"
+ " or write a custom TypeAdapter for its declaring type.", exception);
+ " or write a custom TypeAdapter for its declaring type." + getInaccessibleTroubleshootingSuffix(exception),
exception);
}
}

Expand Down Expand Up @@ -142,7 +155,7 @@ public static String tryMakeAccessible(Constructor<?> constructor) {
return "Failed making constructor '" + constructorToString(constructor) + "' accessible;"
+ " either increase its visibility or write a custom InstanceCreator or TypeAdapter for"
// Include the message since it might contain more detailed information
+ " its declaring type: " + exception.getMessage();
+ " its declaring type: " + exception.getMessage() + getInaccessibleTroubleshootingSuffix(exception);
}
}

Expand Down
47 changes: 28 additions & 19 deletions gson/src/main/java/com/google/gson/stream/JsonReader.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import com.google.gson.Strictness;
import com.google.gson.internal.JsonReaderInternalAccess;
import com.google.gson.internal.TroubleshootingGuide;
import com.google.gson.internal.bind.JsonTreeReader;
import java.io.Closeable;
import java.io.EOFException;
Expand Down Expand Up @@ -403,7 +404,7 @@ public void beginArray() throws IOException {
pathIndices[stackSize - 1] = 0;
peeked = PEEKED_NONE;
} else {
throw new IllegalStateException("Expected BEGIN_ARRAY but was " + peek() + locationString());
throw unexpectedTokenError("BEGIN_ARRAY");
}
}

Expand All @@ -421,7 +422,7 @@ public void endArray() throws IOException {
pathIndices[stackSize - 1]++;
peeked = PEEKED_NONE;
} else {
throw new IllegalStateException("Expected END_ARRAY but was " + peek() + locationString());
throw unexpectedTokenError("END_ARRAY");
}
}

Expand All @@ -438,7 +439,7 @@ public void beginObject() throws IOException {
push(JsonScope.EMPTY_OBJECT);
peeked = PEEKED_NONE;
} else {
throw new IllegalStateException("Expected BEGIN_OBJECT but was " + peek() + locationString());
throw unexpectedTokenError("BEGIN_OBJECT");
}
}

Expand All @@ -457,7 +458,7 @@ public void endObject() throws IOException {
pathIndices[stackSize - 1]++;
peeked = PEEKED_NONE;
} else {
throw new IllegalStateException("Expected END_OBJECT but was " + peek() + locationString());
throw unexpectedTokenError("END_OBJECT");
}
}

Expand Down Expand Up @@ -851,7 +852,7 @@ public String nextName() throws IOException {
} else if (p == PEEKED_DOUBLE_QUOTED_NAME) {
result = nextQuotedValue('"');
} else {
throw new IllegalStateException("Expected a name but was " + peek() + locationString());
throw unexpectedTokenError("a name");
}
peeked = PEEKED_NONE;
pathNames[stackSize - 1] = result;
Expand Down Expand Up @@ -887,7 +888,7 @@ public String nextString() throws IOException {
result = new String(buffer, pos, peekedNumberLength);
pos += peekedNumberLength;
} else {
throw new IllegalStateException("Expected a string but was " + peek() + locationString());
throw unexpectedTokenError("a string");
}
peeked = PEEKED_NONE;
pathIndices[stackSize - 1]++;
Expand Down Expand Up @@ -915,7 +916,7 @@ public boolean nextBoolean() throws IOException {
pathIndices[stackSize - 1]++;
return false;
}
throw new IllegalStateException("Expected a boolean but was " + peek() + locationString());
throw unexpectedTokenError("a boolean");
}

/**
Expand All @@ -934,7 +935,7 @@ public void nextNull() throws IOException {
peeked = PEEKED_NONE;
pathIndices[stackSize - 1]++;
} else {
throw new IllegalStateException("Expected null but was " + peek() + locationString());
throw unexpectedTokenError("null");
}
}

Expand Down Expand Up @@ -969,14 +970,14 @@ public double nextDouble() throws IOException {
} else if (p == PEEKED_UNQUOTED) {
peekedString = nextUnquotedValue();
} else if (p != PEEKED_BUFFERED) {
throw new IllegalStateException("Expected a double but was " + peek() + locationString());
throw unexpectedTokenError("a double");
}

peeked = PEEKED_BUFFERED;
double result = Double.parseDouble(peekedString); // don't catch this NumberFormatException.
if (strictness != Strictness.LENIENT && (Double.isNaN(result) || Double.isInfinite(result))) {
throw new MalformedJsonException(
"JSON forbids NaN and infinities: " + result + locationString());
throw syntaxError(
"JSON forbids NaN and infinities: " + result);
}
peekedString = null;
peeked = PEEKED_NONE;
Expand Down Expand Up @@ -1024,7 +1025,7 @@ public long nextLong() throws IOException {
// Fall back to parse as a double below.
}
} else {
throw new IllegalStateException("Expected a long but was " + peek() + locationString());
throw unexpectedTokenError("a long");
}

peeked = PEEKED_BUFFERED;
Expand Down Expand Up @@ -1265,7 +1266,7 @@ public int nextInt() throws IOException {
// Fall back to parse as a double below.
}
} else {
throw new IllegalStateException("Expected an int but was " + peek() + locationString());
throw unexpectedTokenError("an int");
}

peeked = PEEKED_BUFFERED;
Expand Down Expand Up @@ -1641,10 +1642,10 @@ public String getPath() {
/**
* Unescapes the character identified by the character or characters that
* immediately follow a backslash. The backslash '\' should have already
* been read. This supports both unicode escapes "u000A" and two-character
* been read. This supports both Unicode escapes "u000A" and two-character
* escapes "\n".
*
* @throws MalformedJsonException if any unicode escape sequences are
* @throws MalformedJsonException if any Unicode escape sequences are
* malformed.
*/
@SuppressWarnings("fallthrough")
Expand All @@ -1671,7 +1672,7 @@ private char readEscapeCharacter() throws IOException {
} else if (c >= 'A' && c <= 'F') {
result += (c - 'A' + 10);
} else {
throw new MalformedJsonException("\\u" + new String(buffer, pos, 4));
throw syntaxError("Malformed Unicode escape \\u" + new String(buffer, pos, 4));
}
}
pos += 4;
Expand Down Expand Up @@ -1719,7 +1720,16 @@ private char readEscapeCharacter() throws IOException {
* with this reader's content.
*/
private IOException syntaxError(String message) throws IOException {
throw new MalformedJsonException(message + locationString());
throw new MalformedJsonException(message + locationString()
+ "\nSee " + TroubleshootingGuide.createUrl("malformed-json"));
}

private IllegalStateException unexpectedTokenError(String expected) throws IOException {
JsonToken peeked = peek();
String troubleshootingId = peeked == JsonToken.NULL
? "adapter-not-null-safe" : "unexpected-json-structure";
return new IllegalStateException("Expected " + expected + " but was " + peek() + locationString()
+ "\nSee " + TroubleshootingGuide.createUrl(troubleshootingId));
}

/**
Expand Down Expand Up @@ -1762,8 +1772,7 @@ private void consumeNonExecutePrefix() throws IOException {
} else if (p == PEEKED_UNQUOTED_NAME) {
reader.peeked = PEEKED_UNQUOTED;
} else {
throw new IllegalStateException(
"Expected a name but was " + reader.peek() + reader.locationString());
throw reader.unexpectedTokenError("a name");
}
}
};
Expand Down
25 changes: 8 additions & 17 deletions gson/src/test/java/com/google/gson/JsonArrayTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
package com.google.gson;

import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assert.fail;

import com.google.common.testing.EqualsTester;
Expand Down Expand Up @@ -140,22 +139,19 @@ public void testFailedGetArrayValues() {
jsonArray.getAsBoolean();
fail("expected getBoolean to fail");
} catch (UnsupportedOperationException e) {
assertWithMessage("Expected an exception message")
.that(e).hasMessageThat().isEqualTo("JsonObject");
assertThat(e).hasMessageThat().isEqualTo("JsonObject");
}
try {
jsonArray.get(-1);
fail("expected get to fail");
} catch (IndexOutOfBoundsException e) {
assertWithMessage("Expected an exception message")
.that(e).hasMessageThat().isEqualTo("Index -1 out of bounds for length 1");
assertThat(e).hasMessageThat().isEqualTo("Index -1 out of bounds for length 1");
}
try {
jsonArray.getAsString();
fail("expected getString to fail");
} catch (UnsupportedOperationException e) {
assertWithMessage("Expected an exception message")
.that(e).hasMessageThat().isEqualTo("JsonObject");
assertThat(e).hasMessageThat().isEqualTo("JsonObject");
}

jsonArray.remove(0);
Expand All @@ -164,36 +160,31 @@ public void testFailedGetArrayValues() {
jsonArray.getAsDouble();
fail("expected getDouble to fail");
} catch (NumberFormatException e) {
assertWithMessage("Expected an exception message")
.that(e).hasMessageThat().isEqualTo("For input string: \"hello\"");
assertThat(e).hasMessageThat().isEqualTo("For input string: \"hello\"");
}
try {
jsonArray.getAsInt();
fail("expected getInt to fail");
} catch (NumberFormatException e) {
assertWithMessage("Expected an exception message")
.that(e).hasMessageThat().isEqualTo("For input string: \"hello\"");
assertThat(e).hasMessageThat().isEqualTo("For input string: \"hello\"");
}
try {
jsonArray.get(0).getAsJsonArray();
fail("expected getJSONArray to fail");
} catch (IllegalStateException e) {
assertWithMessage("Expected an exception message")
.that(e).hasMessageThat().isEqualTo("Not a JSON Array: \"hello\"");
assertThat(e).hasMessageThat().isEqualTo("Not a JSON Array: \"hello\"");
}
try {
jsonArray.getAsJsonObject();
fail("expected getJSONObject to fail");
} catch (IllegalStateException e) {
assertWithMessage("Expected an exception message")
.that(e).hasMessageThat().isEqualTo( "Not a JSON Object: [\"hello\"]");
assertThat(e).hasMessageThat().isEqualTo("Not a JSON Object: [\"hello\"]");
}
try {
jsonArray.getAsLong();
fail("expected getLong to fail");
} catch (NumberFormatException e) {
assertWithMessage("Expected an exception message")
.that(e).hasMessageThat().isEqualTo("For input string: \"hello\"");
assertThat(e).hasMessageThat().isEqualTo("For input string: \"hello\"");
}
}

Expand Down

0 comments on commit 48a1bd4

Please sign in to comment.