Skip to content

Commit

Permalink
feat(kernel): distinguish framework errors from userland errors in Ja…
Browse files Browse the repository at this point in the history
…va (#3747)

Adds the `JsiiFault` and `JsError` types to the Kernel and corresponding types in Java. This will provide a better error experience and make it clearer which errors come from the jsii framework itself (eg a broken pipe) and which errors from the user code (eg a CDK construct validation error).

---

By submitting this pull request, I confirm that my contribution is made under the terms of the [Apache 2.0 license].

[Apache 2.0 license]: https://www.apache.org/licenses/LICENSE-2.0
  • Loading branch information
comcalvi authored Sep 21, 2022
1 parent 8ab7c57 commit a4d39c6
Show file tree
Hide file tree
Showing 16 changed files with 240 additions and 139 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -334,8 +334,11 @@ public void exceptions() {
calc3.add(3);
assertEquals(23, calc3.getValue());
boolean thrown = false;
try { calc3.add(10); }
catch (Exception e) { thrown = true; }
try {
calc3.add(10);
} catch (RuntimeException e) {
thrown = true;
}
assertTrue(thrown);
calc3.setMaxValue(40);
calc3.add(10);
Expand Down Expand Up @@ -449,7 +452,7 @@ public java.lang.Number overrideMe(java.lang.Number mult) {
boolean thrown = false;
try {
obj.callMe();
} catch (JsiiException e) {
} catch (RuntimeException e) {
assertTrue(e.getMessage().contains( "Thrown by native code"));
thrown = true;
}
Expand Down Expand Up @@ -518,7 +521,7 @@ public String getTheProperty() {
boolean thrown = false;
try {
so.retrieveValueOfTheProperty();
} catch (Exception e) {
} catch (RuntimeException e) {
assertTrue(e.getMessage().contains("Oh no, this is bad"));
thrown = true;
}
Expand All @@ -536,7 +539,7 @@ public void setTheProperty(String value) {
boolean thrown = false;
try {
so.modifyValueOfTheProperty("Hii");
} catch (Exception e) {
} catch (RuntimeException e) {
assertTrue(e.getMessage().contains("Exception from overloaded setter"));
thrown = true;
}
Expand Down
2 changes: 1 addition & 1 deletion packages/@jsii/java-runtime/BundledRuntime.t.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ ${resources.map(extractResource).map(indent(12)).join('\n')}
return entrypoint.toString();
} catch (final IOException ioe) {
throw new JsiiException("Unable to extract bundled @jsii/runtime library", ioe);
throw new JsiiError("Unable to extract bundled @jsii/runtime library", ioe);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public void loadModule(final JsiiModule module) {
Files.delete(tarball.getParent());
}
} catch (IOException e) {
throw new JsiiException("Unable to extract resource " + module.getBundleResourceName(), e);
throw new JsiiError("Unable to extract resource " + module.getBundleResourceName(), e);
}
}

Expand Down Expand Up @@ -226,7 +226,7 @@ public List<Callback> pendingCallbacks() {

JsonNode callbacksResp = resp.get("callbacks");
if (callbacksResp == null || !callbacksResp.isArray()) {
throw new JsiiException("Expecting a 'callbacks' key with an array in response");
throw new JsiiError("Expecting a 'callbacks' key with an array in response");
}

ArrayNode callbacksArray = (ArrayNode) callbacksResp;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ private JsiiEngine() {
*/
public void loadModule(final Class<? extends JsiiModule> moduleClass) {
if (!JsiiModule.class.isAssignableFrom(moduleClass)) {
throw new JsiiException("Invalid module class "
throw new JsiiError("Invalid module class "
+ moduleClass.getName()
+ ". It must be derived from JsiiModule");
}
Expand All @@ -173,7 +173,7 @@ public void loadModule(final Class<? extends JsiiModule> moduleClass) {
try {
module = moduleClass.getConstructor().newInstance();
} catch (IllegalAccessException | InstantiationException | InvocationTargetException | NoSuchMethodException e) {
throw new JsiiException(e);
throw new JsiiError(e);
}

if (this.loadedModules.containsKey(module.getModuleName())) {
Expand Down Expand Up @@ -270,12 +270,12 @@ public JsiiObjectRef nativeToObjRef(final Object nativeObject) {
*
* @param objRef The object reference
* @return a JsiiObject
* @throws JsiiException If the object is not found.
* @throws JsiiError If the object is not found.
*/
public Object getObject(final JsiiObjectRef objRef) {
Object obj = this.objects.get(objRef.getObjId());
if (obj == null) {
throw new JsiiException("Cannot find jsii object: " + objRef.getObjId());
throw new JsiiError("Cannot find jsii object: " + objRef.getObjId());
}
return obj;
}
Expand Down Expand Up @@ -303,24 +303,24 @@ Class<?> resolveJavaClass(final String fqn) {
}
String[] parts = fqn.split("\\.");
if (parts.length < 2) {
throw new JsiiException("Malformed FQN: " + fqn);
throw new JsiiError("Malformed FQN: " + fqn);
}

String moduleName = parts[0];

JsonNode names = this.getClient().getModuleNames(moduleName);
if (!names.has("java")) {
throw new JsiiException("No java name for module " + moduleName);
throw new JsiiError("No java name for module " + moduleName);
}

final JsiiModule module = this.loadedModules.get(moduleName);
if (module == null) {
throw new JsiiException("No loaded module is named " + moduleName);
throw new JsiiError("No loaded module is named " + moduleName);
}
try {
return module.resolveClass(fqn);
} catch (final ClassNotFoundException cfne) {
throw new JsiiException(cfne);
throw new JsiiError(cfne);
}
}

Expand All @@ -346,12 +346,12 @@ private JsiiObject createNativeProxy(final String fqn, final JsiiObjectRef objRe
ctor.setAccessible(false);
return newObj;
} catch (NoSuchMethodException e) {
throw new JsiiException("Cannot create native object of type "
throw new JsiiError("Cannot create native object of type "
+ klass.getName()
+ " without a constructor that accepts an InitializationMode argument", e);

} catch (IllegalAccessException | InstantiationException | InvocationTargetException e) {
throw new JsiiException("Unable to instantiate a new object for FQN " + fqn + ": "
throw new JsiiError("Unable to instantiate a new object for FQN " + fqn + ": "
+ e.getMessage(), e);
}
} catch (ClassNotFoundException e) {
Expand All @@ -369,7 +369,7 @@ private JsiiObject createNativeProxy(final String fqn, final JsiiObjectRef objRe
public Enum<?> findEnumValue(final String enumRef) {
int sep = enumRef.lastIndexOf('/');
if (sep == -1) {
throw new JsiiException("Malformed enum reference: " + enumRef);
throw new JsiiError("Malformed enum reference: " + enumRef);
}

String typeName = enumRef.substring(0, sep);
Expand All @@ -396,7 +396,7 @@ public void processAllPendingCallbacks() {
* Invokes a local callback and returns the result/error.
* @param callback The callback to invoke.
* @return The return value
* @throws JsiiException if the callback failed.
* @throws JsiiError if the callback failed.
*/
public JsonNode handleCallback(final Callback callback) {

Expand All @@ -408,7 +408,7 @@ public JsonNode handleCallback(final Callback callback) {
return invokeCallbackSet(callback.getSet());
}

throw new JsiiException("Unrecognized callback type: get/set/invoke");
throw new JsiiError("Unrecognized callback type: get/set/invoke");
}

/**
Expand Down Expand Up @@ -486,9 +486,9 @@ private Object invokeMethod(final Object obj, final Method method, final Object.
throw e;
}
} catch (InvocationTargetException e) {
throw new JsiiException(e.getTargetException());
throw new JsiiError(e.getTargetException());
} catch (IllegalAccessException e) {
throw new JsiiException(e);
throw new JsiiError(e);
} finally {
// revert accessibility.
method.setAccessible(accessibility);
Expand All @@ -503,7 +503,7 @@ private void processCallback(final Callback callback) {
try {
JsonNode result = handleCallback(callback);
this.getClient().completeCallback(callback, null, result);
} catch (JsiiException e) {
} catch (JsiiError e) {
this.getClient().completeCallback(callback, e.getMessage(), null);
}
}
Expand All @@ -528,15 +528,15 @@ private Method findCallbackMethod(final Class<?> klass, final String signature)
return findCallbackMethod(klass.getSuperclass(), signature);
}

throw new JsiiException("Unable to find callback method with signature: " + signature);
throw new JsiiError("Unable to find callback method with signature: " + signature);
}

/**
* Tries to locate the getter method for a property
* @param klass is the type on which the getter is to be searched for
* @param methodName is the name of the getter method
* @return the found Method
* @throws JsiiException if no such method is found
* @throws JsiiError if no such method is found
*/
private Method findCallbackGetter(final Class<?> klass, final String methodName) {
try {
Expand All @@ -549,7 +549,7 @@ private Method findCallbackGetter(final Class<?> klass, final String methodName)
// Ignored!
}
}
throw new JsiiException(nsme);
throw new JsiiError(nsme);
}
}

Expand All @@ -559,7 +559,7 @@ private Method findCallbackGetter(final Class<?> klass, final String methodName)
* @param methodName is the name of the setter method
* @param valueType is the type of the argument the setter accepts
* @return the found Method
* @throws JsiiException if no such method is found
* @throws JsiiError if no such method is found
*/
private Method findCallbackSetter(final Class<?> klass, final String methodName, final Class<?> valueType) {
try {
Expand All @@ -572,7 +572,7 @@ private Method findCallbackSetter(final Class<?> klass, final String methodName,
// Ignored!
}
}
throw new JsiiException(nsme);
throw new JsiiError(nsme);
}
}

Expand Down Expand Up @@ -731,7 +731,7 @@ static Jsii tryGetJsiiAnnotation(final Class<?> type, final boolean inherited) {
String loadModuleForClass(Class<?> nativeClass) {
final Jsii jsii = tryGetJsiiAnnotation(nativeClass, true);
if (jsii == null) {
throw new JsiiException("Unable to find @Jsii annotation for class");
throw new JsiiError("Unable to find @Jsii annotation for class");
}

this.loadModule(jsii.module());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package software.amazon.jsii;

/**
* A nonrecoverable error from the jsii runtime,
* usually the kernel.
*/
public final class JsiiError extends JsiiException {
public static final long serialVersionUID = 1L;

/**
* Creates an exception.
* @param message The error message
*/
JsiiError(final String message) {
super(message);
}

/**
* Creates an exception.
* @param e The error that caused this exception
*/
JsiiError(final Throwable e) {
super(e);
}

/**
* Creates an exception.
* @param message The error message
* @param e The error that caused this exception
*/
JsiiError(final String message, final Throwable e) {
super(message, e);
}
}
Original file line number Diff line number Diff line change
@@ -1,33 +1,48 @@
package software.amazon.jsii;

/**
/*
* An error raised by the jsii runtime.
*/
public final class JsiiException extends RuntimeException {
public abstract class JsiiException extends RuntimeException {
public static final long serialVersionUID = 1L;

/**
* Creates an exception.
* @param message The error message
*/
JsiiException(final String message) {
super(message);
}
static enum Type {
JSII_FAULT("@jsii/kernel.Fault"),
RUNTIME_EXCEPTION("@jsii/kernel.RuntimeException");

/**
* Creates an exception.
* @param e The error that caused this exception
*/
JsiiException(final Throwable e) {
super(e);
}
private final String errorType;

Type(String str) {
this.errorType = str;
}

/**
* Creates an exception.
* @param message The error message
* @param e The error that caused this exception
*/
JsiiException(final String message, final Throwable e) {
super(message, e);
public String toString() {
return this.errorType;
}
}

/**
* Creates an exception.
* @param message The error message
*/
JsiiException(final String message) {
super(message);
}

/**
* Creates an exception.
* @param e The error that caused this exception
*/
JsiiException(final Throwable e) {
super(e);
}

/**
* Creates an exception.
* @param message The error message
* @param e The error that caused this exception
*/
JsiiException(final String message, final Throwable e) {
super(message, e);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ protected JsiiObject(final InitializationMode initializationMode) {
JsiiObject(@Nullable final JsiiEngine engine, final InitializationMode initializationMode) {
this.jsii$engine = JsiiEngine.getEngineFor(this, engine);
if (initializationMode != InitializationMode.JSII) {
throw new JsiiException("The only supported initialization mode is '" + InitializationMode.JSII + "'");
throw new JsiiError("The only supported initialization mode is '" + InitializationMode.JSII + "'");
}
}

Expand Down Expand Up @@ -380,11 +380,11 @@ final <T extends JsiiObject> T asInterfaceProxy(final Class<? extends T> proxyCl
constructor.setAccessible(oldAccessible);
}
} catch(final NoSuchMethodException nsme) {
throw new JsiiException("Unable to find interface proxy constructor on " + proxyClass.getCanonicalName(), nsme);
throw new JsiiError("Unable to find interface proxy constructor on " + proxyClass.getCanonicalName(), nsme);
} catch (final InvocationTargetException | InstantiationException e) {
throw new JsiiException("Unable to initialize interface proxy " + proxyClass.getCanonicalName(), e);
throw new JsiiError("Unable to initialize interface proxy " + proxyClass.getCanonicalName(), e);
} catch (final IllegalAccessException iae) {
throw new JsiiException("Unable to invoke constructor of " + proxyClass.getCanonicalName(), iae);
throw new JsiiError("Unable to invoke constructor of " + proxyClass.getCanonicalName(), iae);
}
}
@SuppressWarnings("unchecked")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public final class JsiiObjectMapper {

/**
* Similar to calling JsiiObjectMapper.INSTANCE.treeToValue, but handles a null JsonNode argument
* well, and throws JsiiException instead of JsonProcessingException.
* well, and throws JsiiError instead of JsonProcessingException.
*
* @param tree the JSON object to parse
* @param valueType the expected type value type
Expand Down Expand Up @@ -228,7 +228,7 @@ private static final class EnumSerializer extends JsonSerializer<Enum> {
public void serialize(final Enum value, final JsonGenerator gen, final SerializerProvider serializers) throws IOException {
Jsii jsii = this.tryGetJsiiAnnotation(value.getClass());
if (jsii == null) {
throw new JsiiException("Cannot serialize non-jsii enums");
throw new JsiiError("Cannot serialize non-jsii enums");
} else {
gen.writeStartObject();
gen.writeStringField(TOKEN_ENUM, jsii.fqn() + "/" + value.toString());
Expand Down
Loading

0 comments on commit a4d39c6

Please sign in to comment.