From 7693b01d728e8bded2f33d61ef9ff9dacc998822 Mon Sep 17 00:00:00 2001 From: "Mark S. Miller" Date: Sun, 11 Feb 2024 09:22:20 -0800 Subject: [PATCH 1/2] feat(marshal): tolerate extra error props --- packages/marshal/package.json | 1 + packages/marshal/src/marshal-justin.js | 4 ++ packages/marshal/src/marshal.js | 19 +++++--- packages/marshal/test/test-marshal-capdata.js | 43 ++++++++++++++++++- .../marshal/test/test-marshal-smallcaps.js | 43 ++++++++++++++++++- 5 files changed, 103 insertions(+), 7 deletions(-) diff --git a/packages/marshal/package.json b/packages/marshal/package.json index 21986b2982..e0eff7ed4b 100644 --- a/packages/marshal/package.json +++ b/packages/marshal/package.json @@ -41,6 +41,7 @@ }, "homepage": "https://github.com/endojs/endo#readme", "dependencies": { + "@endo/common": "^1.0.2", "@endo/errors": "^1.0.2", "@endo/eventual-send": "^1.1.0", "@endo/nat": "^5.0.2", diff --git a/packages/marshal/src/marshal-justin.js b/packages/marshal/src/marshal-justin.js index 240328774f..69bd2d7ed6 100644 --- a/packages/marshal/src/marshal-justin.js +++ b/packages/marshal/src/marshal-justin.js @@ -390,6 +390,10 @@ const decodeToJustin = (encoding, shouldIndent = false, slots = []) => { case 'error': { const { name, message } = rawTree; + // TODO cause, errors, AggregateError + // See https://github.com/endojs/endo/pull/2052 + name !== `AggregateError` || + Fail`AggregateError not yet implemented in marshal-justin`; return out.next(`${name}(${quote(message)})`); } diff --git a/packages/marshal/src/marshal.js b/packages/marshal/src/marshal.js index fe2b419e3b..edc8bb1d2c 100644 --- a/packages/marshal/src/marshal.js +++ b/packages/marshal/src/marshal.js @@ -9,6 +9,7 @@ import { } from '@endo/pass-style'; import { X, Fail, q, makeError, annotateError } from '@endo/errors'; +import { objectMap } from '@endo/common/object-map.js'; import { QCLASS, makeEncodeToCapData, @@ -260,11 +261,11 @@ export const makeMarshal = ( */ const decodeErrorCommon = (errData, decodeRecur) => { const { errorId = undefined, message, name, ...rest } = errData; - ownKeys(rest).length === 0 || - Fail`unexpected encoded error properties ${q(ownKeys(rest))}`; - // TODO Must decode `cause` and `errors` properties - // capData does not transform strings. The calls to `decodeRecur` - // are for reuse by other encodings that do, such as smallcaps. + // TODO Must decode `cause` and `errors` properties. + // See https://github.com/endojs/endo/pull/2052 + // capData does not transform strings. The immediately following calls + // to `decodeRecur` are for reuse by other encodings that do, + // such as smallcaps. const dName = decodeRecur(name); const dMessage = decodeRecur(message); const dErrorId = errorId && decodeRecur(errorId); @@ -279,6 +280,14 @@ export const makeMarshal = ( ? `Remote${EC.name}` : `Remote${EC.name}(${dErrorId})`; const error = makeError(dMessage, EC, { errorName }); + if (ownKeys(rest).length >= 1) { + // Note that this does not decodeRecur rest's property names. + // This would be inconsistent with smallcaps' expected handling, + // but is fine here since it is only used for `annotateError`, + // which is for diagnostic info that is otherwise unobservable. + const extras = objectMap(rest, decodeRecur); + annotateError(error, X`extra marshalled properties ${extras}`); + } return harden(error); }; diff --git a/packages/marshal/test/test-marshal-capdata.js b/packages/marshal/test/test-marshal-capdata.js index 5cacdc1d7b..503cb64749 100644 --- a/packages/marshal/test/test-marshal-capdata.js +++ b/packages/marshal/test/test-marshal-capdata.js @@ -5,7 +5,13 @@ import { passStyleOf, Far } from '@endo/pass-style'; import { makeMarshal } from '../src/marshal.js'; import { roundTripPairs } from './marshal-test-data.js'; -const { freeze, isFrozen, create, prototype: objectPrototype } = Object; +const { + freeze, + isFrozen, + create, + prototype: objectPrototype, + getPrototypeOf, +} = Object; const harden = /** @type {import('ses').Harden & { isFake?: boolean }} */ ( // eslint-disable-next-line no-undef @@ -147,6 +153,41 @@ test('unserialize errors', t => { t.is(em3.message, 'msg3'); }); +test('unserialize extended errors', t => { + const { unserialize } = makeTestMarshal(); + const uns = body => unserialize({ body, slots: [] }); + + // TODO cause, errors, and AggregateError will eventually be recognized. + // See https://github.com/endojs/endo/pull/2042 + + const refErr = uns( + '{"@qclass":"error","message":"msg","name":"ReferenceError","extraProp":"foo","cause":"bar","errors":["zip","zap"]}', + ); + t.is(getPrototypeOf(refErr), ReferenceError.prototype); // direct instance of + t.false('extraProp' in refErr); + t.false('cause' in refErr); + t.false('errors' in refErr); + console.log('error with extra prop', refErr); + + const aggErr = uns( + '{"@qclass":"error","message":"msg","name":"AggregateError","extraProp":"foo","cause":"bar","errors":["zip","zap"]}', + ); + t.is(getPrototypeOf(aggErr), Error.prototype); // direct instance of + t.false('extraProp' in aggErr); + t.false('cause' in aggErr); + t.false('errors' in aggErr); + console.log('error with extra prop', aggErr); + + const unkErr = uns( + '{"@qclass":"error","message":"msg","name":"UnknownError","extraProp":"foo","cause":"bar","errors":["zip","zap"]}', + ); + t.is(getPrototypeOf(unkErr), Error.prototype); // direct instance of + t.false('extraProp' in unkErr); + t.false('cause' in unkErr); + t.false('errors' in unkErr); + console.log('error with extra prop', unkErr); +}); + test('passStyleOf null is "null"', t => { t.assert(passStyleOf(null), 'null'); }); diff --git a/packages/marshal/test/test-marshal-smallcaps.js b/packages/marshal/test/test-marshal-smallcaps.js index 78bce8cc6b..86fe5e75b2 100644 --- a/packages/marshal/test/test-marshal-smallcaps.js +++ b/packages/marshal/test/test-marshal-smallcaps.js @@ -6,7 +6,13 @@ import { makeMarshal } from '../src/marshal.js'; import { roundTripPairs } from './marshal-test-data.js'; -const { freeze, isFrozen, create, prototype: objectPrototype } = Object; +const { + freeze, + isFrozen, + create, + prototype: objectPrototype, + getPrototypeOf, +} = Object; const harden = /** @type {import('ses').Harden & { isFake?: boolean }} */ ( // eslint-disable-next-line no-undef @@ -153,6 +159,41 @@ test('smallcaps unserialize errors', t => { t.is(em3.message, 'msg3'); }); +test('smallcaps unserialize extended errors', t => { + const { unserialize } = makeTestMarshal(); + const uns = body => unserialize({ body, slots: [] }); + + // TODO cause, errors, and AggregateError will eventually be recognized. + // See https://github.com/endojs/endo/pull/2042 + + const refErr = uns( + '#{"#error":"msg","name":"ReferenceError","extraProp":"foo","cause":"bar","errors":["zip","zap"]}', + ); + t.is(getPrototypeOf(refErr), ReferenceError.prototype); // direct instance of + t.false('extraProp' in refErr); + t.false('cause' in refErr); + t.false('errors' in refErr); + console.log('error with extra prop', refErr); + + const aggErr = uns( + '#{"#error":"msg","name":"AggregateError","extraProp":"foo","cause":"bar","errors":["zip","zap"]}', + ); + t.is(getPrototypeOf(aggErr), Error.prototype); // direct instance of + t.false('extraProp' in aggErr); + t.false('cause' in aggErr); + t.false('errors' in aggErr); + console.log('error with extra prop', aggErr); + + const unkErr = uns( + '#{"#error":"msg","name":"UnknownError","extraProp":"foo","cause":"bar","errors":["zip","zap"]}', + ); + t.is(getPrototypeOf(unkErr), Error.prototype); // direct instance of + t.false('extraProp' in unkErr); + t.false('cause' in unkErr); + t.false('errors' in unkErr); + console.log('error with extra prop', unkErr); +}); + test('smallcaps mal-formed @qclass', t => { const { unserialize } = makeTestMarshal(); const uns = body => unserialize({ body, slots: [] }); From 84dea1fed782dd9bf57b77662e18513995679fb1 Mon Sep 17 00:00:00 2001 From: "Mark S. Miller" Date: Tue, 13 Feb 2024 15:14:41 -0800 Subject: [PATCH 2/2] fixup! NEWS --- packages/marshal/NEWS.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/marshal/NEWS.md b/packages/marshal/NEWS.md index 215503483c..2d77aa637f 100644 --- a/packages/marshal/NEWS.md +++ b/packages/marshal/NEWS.md @@ -1,5 +1,9 @@ User-visible changes in `@endo/marshal`: +# next + +- Tolerates receiving extra error properties (https://github.com/endojs/endo/pull/2052). Once pervasive, this tolerance will eventually enable additional error properties to be sent. The motivating examples are the JavaScript standard properties `cause` and `errors`. This change also enables smoother interoperation with other languages with their own theories about diagnostic information to be included in errors. + # v0.8.1 (2022-12-23) - Remote objects now reflect methods present on their prototype chain.