Skip to content

Commit a4d39c6

Browse files
authored
feat(kernel): distinguish framework errors from userland errors in Java (#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
1 parent 8ab7c57 commit a4d39c6

File tree

16 files changed

+240
-139
lines changed

16 files changed

+240
-139
lines changed

packages/@jsii/java-runtime-test/project/src/test/java/software/amazon/jsii/testing/ComplianceTest.java

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -334,8 +334,11 @@ public void exceptions() {
334334
calc3.add(3);
335335
assertEquals(23, calc3.getValue());
336336
boolean thrown = false;
337-
try { calc3.add(10); }
338-
catch (Exception e) { thrown = true; }
337+
try {
338+
calc3.add(10);
339+
} catch (RuntimeException e) {
340+
thrown = true;
341+
}
339342
assertTrue(thrown);
340343
calc3.setMaxValue(40);
341344
calc3.add(10);
@@ -449,7 +452,7 @@ public java.lang.Number overrideMe(java.lang.Number mult) {
449452
boolean thrown = false;
450453
try {
451454
obj.callMe();
452-
} catch (JsiiException e) {
455+
} catch (RuntimeException e) {
453456
assertTrue(e.getMessage().contains( "Thrown by native code"));
454457
thrown = true;
455458
}
@@ -518,7 +521,7 @@ public String getTheProperty() {
518521
boolean thrown = false;
519522
try {
520523
so.retrieveValueOfTheProperty();
521-
} catch (Exception e) {
524+
} catch (RuntimeException e) {
522525
assertTrue(e.getMessage().contains("Oh no, this is bad"));
523526
thrown = true;
524527
}
@@ -536,7 +539,7 @@ public void setTheProperty(String value) {
536539
boolean thrown = false;
537540
try {
538541
so.modifyValueOfTheProperty("Hii");
539-
} catch (Exception e) {
542+
} catch (RuntimeException e) {
540543
assertTrue(e.getMessage().contains("Exception from overloaded setter"));
541544
thrown = true;
542545
}

packages/@jsii/java-runtime/BundledRuntime.t.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ ${resources.map(extractResource).map(indent(12)).join('\n')}
3434
3535
return entrypoint.toString();
3636
} catch (final IOException ioe) {
37-
throw new JsiiException("Unable to extract bundled @jsii/runtime library", ioe);
37+
throw new JsiiError("Unable to extract bundled @jsii/runtime library", ioe);
3838
}
3939
}
4040

packages/@jsii/java-runtime/project/src/main/java/software/amazon/jsii/JsiiClient.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ public void loadModule(final JsiiModule module) {
5959
Files.delete(tarball.getParent());
6060
}
6161
} catch (IOException e) {
62-
throw new JsiiException("Unable to extract resource " + module.getBundleResourceName(), e);
62+
throw new JsiiError("Unable to extract resource " + module.getBundleResourceName(), e);
6363
}
6464
}
6565

@@ -226,7 +226,7 @@ public List<Callback> pendingCallbacks() {
226226

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

232232
ArrayNode callbacksArray = (ArrayNode) callbacksResp;

packages/@jsii/java-runtime/project/src/main/java/software/amazon/jsii/JsiiEngine.java

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ private JsiiEngine() {
164164
*/
165165
public void loadModule(final Class<? extends JsiiModule> moduleClass) {
166166
if (!JsiiModule.class.isAssignableFrom(moduleClass)) {
167-
throw new JsiiException("Invalid module class "
167+
throw new JsiiError("Invalid module class "
168168
+ moduleClass.getName()
169169
+ ". It must be derived from JsiiModule");
170170
}
@@ -173,7 +173,7 @@ public void loadModule(final Class<? extends JsiiModule> moduleClass) {
173173
try {
174174
module = moduleClass.getConstructor().newInstance();
175175
} catch (IllegalAccessException | InstantiationException | InvocationTargetException | NoSuchMethodException e) {
176-
throw new JsiiException(e);
176+
throw new JsiiError(e);
177177
}
178178

179179
if (this.loadedModules.containsKey(module.getModuleName())) {
@@ -270,12 +270,12 @@ public JsiiObjectRef nativeToObjRef(final Object nativeObject) {
270270
*
271271
* @param objRef The object reference
272272
* @return a JsiiObject
273-
* @throws JsiiException If the object is not found.
273+
* @throws JsiiError If the object is not found.
274274
*/
275275
public Object getObject(final JsiiObjectRef objRef) {
276276
Object obj = this.objects.get(objRef.getObjId());
277277
if (obj == null) {
278-
throw new JsiiException("Cannot find jsii object: " + objRef.getObjId());
278+
throw new JsiiError("Cannot find jsii object: " + objRef.getObjId());
279279
}
280280
return obj;
281281
}
@@ -303,24 +303,24 @@ Class<?> resolveJavaClass(final String fqn) {
303303
}
304304
String[] parts = fqn.split("\\.");
305305
if (parts.length < 2) {
306-
throw new JsiiException("Malformed FQN: " + fqn);
306+
throw new JsiiError("Malformed FQN: " + fqn);
307307
}
308308

309309
String moduleName = parts[0];
310310

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

316316
final JsiiModule module = this.loadedModules.get(moduleName);
317317
if (module == null) {
318-
throw new JsiiException("No loaded module is named " + moduleName);
318+
throw new JsiiError("No loaded module is named " + moduleName);
319319
}
320320
try {
321321
return module.resolveClass(fqn);
322322
} catch (final ClassNotFoundException cfne) {
323-
throw new JsiiException(cfne);
323+
throw new JsiiError(cfne);
324324
}
325325
}
326326

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

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

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

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

411-
throw new JsiiException("Unrecognized callback type: get/set/invoke");
411+
throw new JsiiError("Unrecognized callback type: get/set/invoke");
412412
}
413413

414414
/**
@@ -486,9 +486,9 @@ private Object invokeMethod(final Object obj, final Method method, final Object.
486486
throw e;
487487
}
488488
} catch (InvocationTargetException e) {
489-
throw new JsiiException(e.getTargetException());
489+
throw new JsiiError(e.getTargetException());
490490
} catch (IllegalAccessException e) {
491-
throw new JsiiException(e);
491+
throw new JsiiError(e);
492492
} finally {
493493
// revert accessibility.
494494
method.setAccessible(accessibility);
@@ -503,7 +503,7 @@ private void processCallback(final Callback callback) {
503503
try {
504504
JsonNode result = handleCallback(callback);
505505
this.getClient().completeCallback(callback, null, result);
506-
} catch (JsiiException e) {
506+
} catch (JsiiError e) {
507507
this.getClient().completeCallback(callback, e.getMessage(), null);
508508
}
509509
}
@@ -528,15 +528,15 @@ private Method findCallbackMethod(final Class<?> klass, final String signature)
528528
return findCallbackMethod(klass.getSuperclass(), signature);
529529
}
530530

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

534534
/**
535535
* Tries to locate the getter method for a property
536536
* @param klass is the type on which the getter is to be searched for
537537
* @param methodName is the name of the getter method
538538
* @return the found Method
539-
* @throws JsiiException if no such method is found
539+
* @throws JsiiError if no such method is found
540540
*/
541541
private Method findCallbackGetter(final Class<?> klass, final String methodName) {
542542
try {
@@ -549,7 +549,7 @@ private Method findCallbackGetter(final Class<?> klass, final String methodName)
549549
// Ignored!
550550
}
551551
}
552-
throw new JsiiException(nsme);
552+
throw new JsiiError(nsme);
553553
}
554554
}
555555

@@ -559,7 +559,7 @@ private Method findCallbackGetter(final Class<?> klass, final String methodName)
559559
* @param methodName is the name of the setter method
560560
* @param valueType is the type of the argument the setter accepts
561561
* @return the found Method
562-
* @throws JsiiException if no such method is found
562+
* @throws JsiiError if no such method is found
563563
*/
564564
private Method findCallbackSetter(final Class<?> klass, final String methodName, final Class<?> valueType) {
565565
try {
@@ -572,7 +572,7 @@ private Method findCallbackSetter(final Class<?> klass, final String methodName,
572572
// Ignored!
573573
}
574574
}
575-
throw new JsiiException(nsme);
575+
throw new JsiiError(nsme);
576576
}
577577
}
578578

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

737737
this.loadModule(jsii.module());
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package software.amazon.jsii;
2+
3+
/**
4+
* A nonrecoverable error from the jsii runtime,
5+
* usually the kernel.
6+
*/
7+
public final class JsiiError extends JsiiException {
8+
public static final long serialVersionUID = 1L;
9+
10+
/**
11+
* Creates an exception.
12+
* @param message The error message
13+
*/
14+
JsiiError(final String message) {
15+
super(message);
16+
}
17+
18+
/**
19+
* Creates an exception.
20+
* @param e The error that caused this exception
21+
*/
22+
JsiiError(final Throwable e) {
23+
super(e);
24+
}
25+
26+
/**
27+
* Creates an exception.
28+
* @param message The error message
29+
* @param e The error that caused this exception
30+
*/
31+
JsiiError(final String message, final Throwable e) {
32+
super(message, e);
33+
}
34+
}
Lines changed: 38 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,48 @@
11
package software.amazon.jsii;
22

3-
/**
3+
/*
44
* An error raised by the jsii runtime.
55
*/
6-
public final class JsiiException extends RuntimeException {
6+
public abstract class JsiiException extends RuntimeException {
77
public static final long serialVersionUID = 1L;
88

9-
/**
10-
* Creates an exception.
11-
* @param message The error message
12-
*/
13-
JsiiException(final String message) {
14-
super(message);
15-
}
9+
static enum Type {
10+
JSII_FAULT("@jsii/kernel.Fault"),
11+
RUNTIME_EXCEPTION("@jsii/kernel.RuntimeException");
1612

17-
/**
18-
* Creates an exception.
19-
* @param e The error that caused this exception
20-
*/
21-
JsiiException(final Throwable e) {
22-
super(e);
23-
}
13+
private final String errorType;
14+
15+
Type(String str) {
16+
this.errorType = str;
17+
}
2418

25-
/**
26-
* Creates an exception.
27-
* @param message The error message
28-
* @param e The error that caused this exception
29-
*/
30-
JsiiException(final String message, final Throwable e) {
31-
super(message, e);
19+
public String toString() {
20+
return this.errorType;
21+
}
3222
}
23+
24+
/**
25+
* Creates an exception.
26+
* @param message The error message
27+
*/
28+
JsiiException(final String message) {
29+
super(message);
30+
}
31+
32+
/**
33+
* Creates an exception.
34+
* @param e The error that caused this exception
35+
*/
36+
JsiiException(final Throwable e) {
37+
super(e);
38+
}
39+
40+
/**
41+
* Creates an exception.
42+
* @param message The error message
43+
* @param e The error that caused this exception
44+
*/
45+
JsiiException(final String message, final Throwable e) {
46+
super(message, e);
47+
}
3348
}

packages/@jsii/java-runtime/project/src/main/java/software/amazon/jsii/JsiiObject.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ protected JsiiObject(final InitializationMode initializationMode) {
5353
JsiiObject(@Nullable final JsiiEngine engine, final InitializationMode initializationMode) {
5454
this.jsii$engine = JsiiEngine.getEngineFor(this, engine);
5555
if (initializationMode != InitializationMode.JSII) {
56-
throw new JsiiException("The only supported initialization mode is '" + InitializationMode.JSII + "'");
56+
throw new JsiiError("The only supported initialization mode is '" + InitializationMode.JSII + "'");
5757
}
5858
}
5959

@@ -380,11 +380,11 @@ final <T extends JsiiObject> T asInterfaceProxy(final Class<? extends T> proxyCl
380380
constructor.setAccessible(oldAccessible);
381381
}
382382
} catch(final NoSuchMethodException nsme) {
383-
throw new JsiiException("Unable to find interface proxy constructor on " + proxyClass.getCanonicalName(), nsme);
383+
throw new JsiiError("Unable to find interface proxy constructor on " + proxyClass.getCanonicalName(), nsme);
384384
} catch (final InvocationTargetException | InstantiationException e) {
385-
throw new JsiiException("Unable to initialize interface proxy " + proxyClass.getCanonicalName(), e);
385+
throw new JsiiError("Unable to initialize interface proxy " + proxyClass.getCanonicalName(), e);
386386
} catch (final IllegalAccessException iae) {
387-
throw new JsiiException("Unable to invoke constructor of " + proxyClass.getCanonicalName(), iae);
387+
throw new JsiiError("Unable to invoke constructor of " + proxyClass.getCanonicalName(), iae);
388388
}
389389
}
390390
@SuppressWarnings("unchecked")

packages/@jsii/java-runtime/project/src/main/java/software/amazon/jsii/JsiiObjectMapper.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public final class JsiiObjectMapper {
3232

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

0 commit comments

Comments
 (0)