@@ -150,12 +150,43 @@ struct SerTestContext: public ContextGlobalObject {
150150 return result;
151151 }
152152
153+ JsObject roundTripError (Lock& js, JsObject errorObj) {
154+ Serializer ser (js,
155+ Serializer::Options{
156+ .treatErrorsAsHostObjects = true ,
157+ });
158+ ser.write (js, errorObj);
159+ auto content = ser.release ();
160+ Deserializer deser (js, content);
161+ auto val = KJ_ASSERT_NONNULL (deser.readValue (js).tryCast <JsObject>());
162+
163+ auto names = errorObj.getPropertyNames (js, KeyCollectionFilter::OWN_ONLY,
164+ PropertyFilter::ALL_PROPERTIES, IndexFilter::SKIP_INDICES);
165+ for (size_t n = 0 ; n < names.size (); n++) {
166+ auto before = errorObj.get (js, names.get (js, n));
167+ auto after = val.get (js, names.get (js, n));
168+ if (before.isArray ()) {
169+ auto beforeArray = KJ_ASSERT_NONNULL (before.tryCast <JsArray>());
170+ auto afterArray = KJ_ASSERT_NONNULL (after.tryCast <JsArray>());
171+ KJ_ASSERT (beforeArray.size () == afterArray.size ());
172+ for (size_t i = 0 ; i < beforeArray.size (); i++) {
173+ KJ_ASSERT (beforeArray.get (js, i).strictEquals (afterArray.get (js, i)));
174+ }
175+ } else {
176+ KJ_ASSERT (before.strictEquals (after));
177+ }
178+ }
179+
180+ return val;
181+ }
182+
153183 JSG_RESOURCE_TYPE (SerTestContext) {
154184 JSG_NESTED_TYPE (Foo);
155185 JSG_NESTED_TYPE (Bar);
156186 JSG_NESTED_TYPE (Baz);
157187 JSG_NESTED_TYPE (Qux);
158188 JSG_METHOD (roundTrip);
189+ JSG_METHOD (roundTripError);
159190 }
160191};
161192JSG_DECLARE_ISOLATE_TYPE (SerTestIsolate,
@@ -284,5 +315,46 @@ KJ_TEST("serialization") {
284315 " number" , " 321" );
285316}
286317
318+ KJ_TEST (" serialization of errors" ) {
319+ Evaluator<SerTestContext, SerTestIsolate> e (v8System);
320+
321+ e.expectEval (
322+ " e = new Error('a', {cause:'c'}); e.foo = true; roundTripError(e).foo" , " boolean" , " true" );
323+ e.expectEval (
324+ " roundTripError(new TypeError('a', {cause:'c'})) instanceof TypeError" , " boolean" , " true" );
325+ e.expectEval (
326+ " roundTripError(new RangeError('a', {cause:'c'})) instanceof RangeError" , " boolean" , " true" );
327+ e.expectEval (" roundTripError(new ReferenceError('a', {cause:'c'})) instanceof ReferenceError" ,
328+ " boolean" , " true" );
329+ e.expectEval (" roundTripError(new SyntaxError('a', {cause:'c'})) instanceof SyntaxError" ,
330+ " boolean" , " true" );
331+ e.expectEval (" e = new Error(); Object.defineProperty(e, 'name', {value: 'CustomError'}); "
332+ " roundTripError(e).name" ,
333+ " string" , " CustomError" );
334+
335+ // Throws due to serializing a cycle. Because we serialize the errors as
336+ // host objects, we end up being responsible for ensuring that cycles are
337+ // not present in the serialized data. For now, we're just punting on this
338+ // to keep things simple, but we end up getting an error when we try to
339+ // deserialize. Fortunately this case should always be rare.
340+ // TODO(later): Handle cycles in errors as an edge case.
341+ e.expectEval (R"(
342+ const a = new Error('a');
343+ a.cause = a;
344+ roundTripError(a);
345+ )" ,
346+ " throws" , " Error: Unable to deserialize cloned data." );
347+
348+ e.expectEval (
349+ " roundTripError(new URIError('a', {cause:'c'})) instanceof URIError" , " boolean" , " true" );
350+ e.expectEval (
351+ " roundTripError(new EvalError('a', {cause:'c'})) instanceof EvalError" , " boolean" , " true" );
352+ e.expectEval (" roundTripError(new AggregateError(['a', 'b'], 'c')) instanceof AggregateError" ,
353+ " boolean" , " true" );
354+ e.expectEval (" roundTripError(new SuppressedError('a', 'b', 'c', {cause:'c'})) instanceof "
355+ " SuppressedError" ,
356+ " boolean" , " true" );
357+ }
358+
287359} // namespace
288360} // namespace workerd::jsg::test
0 commit comments