diff --git a/packages/pass-style/src/error.js b/packages/pass-style/src/error.js
index 89aa72c0a6..9a65cc9230 100644
--- a/packages/pass-style/src/error.js
+++ b/packages/pass-style/src/error.js
@@ -1,12 +1,13 @@
///
import { X, q } from '@endo/errors';
-import { assertChecker } from './passStyle-helpers.js';
+import { assertChecker, isObject } from './passStyle-helpers.js';
/** @import {PassStyleHelper} from './internal-types.js' */
-/** @import {Checker, PassStyleOf} from './types.js' */
+/** @import {Checker, PassStyleOf, CopyTagged, Passable} from './types.js' */
-const { getPrototypeOf, getOwnPropertyDescriptors, hasOwn, entries } = Object;
+const { getPrototypeOf, getOwnPropertyDescriptors, hasOwn, entries, values } =
+ Object;
// TODO: Maintenance hazard: Coordinate with the list of errors in the SES
// whilelist.
@@ -62,7 +63,6 @@ const checkErrorLike = (candidate, check = undefined) => {
);
};
harden(checkErrorLike);
-///
/**
* Validating error objects are passable raises a tension between security
@@ -86,27 +86,22 @@ export const isErrorLike = candidate => checkErrorLike(candidate);
harden(isErrorLike);
/**
+ * An own property of a passable error must be a data property whose value is
+ * a throwable value.
+ *
* @param {string} propName
* @param {PropertyDescriptor} desc
* @param {PassStyleOf} passStyleOfRecur
* @param {Checker} [check]
* @returns {boolean}
*/
-export const checkRecursivelyPassableErrorPropertyDesc = (
+export const checkRecursivelyPassableErrorOwnPropertyDesc = (
propName,
desc,
passStyleOfRecur,
check = undefined,
) => {
const reject = !!check && ((T, ...subs) => check(false, X(T, ...subs)));
- if (desc.enumerable) {
- return (
- reject &&
- reject`Passable Error ${q(
- propName,
- )} own property must not be enumerable: ${desc}`
- );
- }
if (!hasOwn(desc, 'value')) {
return (
reject &&
@@ -116,89 +111,88 @@ export const checkRecursivelyPassableErrorPropertyDesc = (
);
}
const { value } = desc;
- switch (propName) {
- case 'message':
- case 'stack': {
- return (
- typeof value === 'string' ||
- (reject &&
- reject`Passable Error ${q(
- propName,
- )} own property must be a string: ${value}`)
- );
- }
- case 'cause': {
- // eslint-disable-next-line no-use-before-define
- return checkRecursivelyPassableError(value, passStyleOfRecur, check);
- }
- case 'errors': {
- if (!Array.isArray(value) || passStyleOfRecur(value) !== 'copyArray') {
- return (
- reject &&
- reject`Passable Error ${q(
- propName,
- )} own property must be a copyArray: ${value}`
- );
- }
- return value.every(err =>
- // eslint-disable-next-line no-use-before-define
- checkRecursivelyPassableError(err, passStyleOfRecur, check),
- );
- }
- default: {
- break;
- }
- }
- return (
- reject && reject`Passable Error has extra unpassed property ${q(propName)}`
- );
+ // eslint-disable-next-line no-use-before-define
+ return checkRecursivelyThrowable(value, passStyleOfRecur, check);
};
-harden(checkRecursivelyPassableErrorPropertyDesc);
+harden(checkRecursivelyPassableErrorOwnPropertyDesc);
/**
+ * `candidate` is throwable if it contains only data and passable errors.
+ *
* @param {unknown} candidate
* @param {PassStyleOf} passStyleOfRecur
* @param {Checker} [check]
* @returns {boolean}
*/
-export const checkRecursivelyPassableError = (
+export const checkRecursivelyThrowable = (
candidate,
passStyleOfRecur,
check = undefined,
) => {
const reject = !!check && ((T, ...subs) => check(false, X(T, ...subs)));
- if (!checkErrorLike(candidate, check)) {
- return false;
- }
- const proto = getPrototypeOf(candidate);
- const { name } = proto;
- const errConstructor = getErrorConstructor(name);
- if (errConstructor === undefined || errConstructor.prototype !== proto) {
- return (
- reject &&
- reject`Passable Error must inherit from an error class .prototype: ${candidate}`
+ if (checkErrorLike(candidate, check)) {
+ const proto = getPrototypeOf(candidate);
+ const { name } = proto;
+ const errConstructor = getErrorConstructor(name);
+ if (errConstructor === undefined || errConstructor.prototype !== proto) {
+ return (
+ reject &&
+ reject`Passable Error must inherit from an error class .prototype: ${candidate}`
+ );
+ }
+ const descs = getOwnPropertyDescriptors(candidate);
+ if (!('message' in descs)) {
+ return (
+ reject &&
+ reject`Passable Error must have an own "message" string property: ${candidate}`
+ );
+ }
+
+ return entries(descs).every(([propName, desc]) =>
+ checkRecursivelyPassableErrorOwnPropertyDesc(
+ propName,
+ desc,
+ passStyleOfRecur,
+ check,
+ ),
);
}
- const descs = getOwnPropertyDescriptors(candidate);
- if (!('message' in descs)) {
- return (
- reject &&
- reject`Passable Error must have an own "message" string property: ${candidate}`
- );
+ const passStyle = passStyleOfRecur(candidate);
+ if (!isObject(candidate)) {
+ // All passable primitives are throwable
+ return true;
+ }
+ switch (passStyle) {
+ case 'copyArray': {
+ return /** @type {Passable[]} */ (candidate).every(element =>
+ checkRecursivelyThrowable(element, passStyleOfRecur, check),
+ );
+ }
+ case 'copyRecord': {
+ return values(/** @type {Record} */ (candidate)).every(
+ value => checkRecursivelyThrowable(value, passStyleOfRecur, check),
+ );
+ }
+ case 'tagged': {
+ return checkRecursivelyThrowable(
+ /** @type {CopyTagged} */ (candidate).payload,
+ passStyleOfRecur,
+ check,
+ );
+ }
+ default: {
+ return (
+ reject &&
+ reject`A throwable cannot contain a ${q(passStyle)}: ${candidate}`
+ );
+ }
}
-
- return entries(descs).every(([propName, desc]) =>
- checkRecursivelyPassableErrorPropertyDesc(
- propName,
- desc,
- passStyleOfRecur,
- check,
- ),
- );
};
-harden(checkRecursivelyPassableError);
+harden(checkRecursivelyThrowable);
/**
+ * A passable error is a throwable error and contains only throwable values.
+ *
* @type {PassStyleHelper}
*/
export const ErrorHelper = harden({
@@ -207,5 +201,6 @@ export const ErrorHelper = harden({
canBeValid: checkErrorLike,
assertValid: (candidate, passStyleOfRecur) =>
- checkRecursivelyPassableError(candidate, passStyleOfRecur, assertChecker),
+ checkErrorLike(candidate, assertChecker) &&
+ checkRecursivelyThrowable(candidate, passStyleOfRecur, assertChecker),
});
diff --git a/packages/pass-style/src/passStyleOf.js b/packages/pass-style/src/passStyleOf.js
index 1b6f497ba2..13740ca420 100644
--- a/packages/pass-style/src/passStyleOf.js
+++ b/packages/pass-style/src/passStyleOf.js
@@ -11,8 +11,8 @@ import { CopyRecordHelper } from './copyRecord.js';
import { TaggedHelper } from './tagged.js';
import {
ErrorHelper,
- checkRecursivelyPassableErrorPropertyDesc,
- checkRecursivelyPassableError,
+ checkRecursivelyPassableErrorOwnPropertyDesc,
+ checkRecursivelyThrowable,
getErrorConstructor,
} from './error.js';
import { RemotableHelper } from './remotable.js';
@@ -264,7 +264,7 @@ harden(isPassable);
* @returns {boolean}
*/
const isPassableErrorPropertyDesc = (name, desc) =>
- checkRecursivelyPassableErrorPropertyDesc(name, desc, passStyleOf);
+ checkRecursivelyPassableErrorOwnPropertyDesc(name, desc, passStyleOf);
/**
* Return a passable error that propagates the diagnostic info of the
@@ -277,7 +277,7 @@ const isPassableErrorPropertyDesc = (name, desc) =>
*/
export const toPassableError = err => {
harden(err);
- if (checkRecursivelyPassableError(err, passStyleOf)) {
+ if (checkRecursivelyThrowable(err, passStyleOf)) {
return err;
}
const { name, message } = err;
@@ -309,3 +309,11 @@ export const toPassableError = err => {
return newError;
};
harden(toPassableError);
+
+export const toThrowable = candidate => {
+ harden(candidate);
+ if (ErrorHelper.canBeValid(candidate)) {
+ return toPassableError(candidate);
+ }
+ throw Fail`TODO oops`;
+}