Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/pass-style/NEWS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
User-visible changes in `@endo/pass-style`:

# Next release

- deprecates `assertChecker`. Use `Fail` in the confirm/reject pattern instead, as supported by `@endo/errors/rejector.js`.

# 1.6.3 (2025-07-11)

- The exported function name `isObject` is ambiguous. It is unclear whether it
Expand Down
4 changes: 2 additions & 2 deletions packages/pass-style/src/byteArray.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,9 @@ const { immutableArrayBufferPrototype, immutableGetter } =
export const ByteArrayHelper = harden({
styleName: 'byteArray',

canBeValid: (candidate, check = undefined) =>
confirmCanBeValid: (candidate, reject) =>
(candidate instanceof ArrayBuffer && candidate.immutable) ||
(!!check && check(false, X`Immutable ArrayBuffer expected: ${candidate}`)),
(reject && reject`Immutable ArrayBuffer expected: ${candidate}`),

assertRestValid: (candidate, _passStyleOfRecur) => {
getPrototypeOf(candidate) === immutableArrayBufferPrototype ||
Expand Down
13 changes: 6 additions & 7 deletions packages/pass-style/src/copyArray.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/// <reference types="ses"/>

import { X } from '@endo/errors';
import { assertChecker, getOwnDataDescriptor } from './passStyle-helpers.js';
import { Fail, X } from '@endo/errors';
import { confirmOwnDataDescriptor } from './passStyle-helpers.js';

const { getPrototypeOf } = Object;
const { ownKeys } = Reflect;
Expand All @@ -14,23 +14,22 @@ const { isArray, prototype: arrayPrototype } = Array;
export const CopyArrayHelper = harden({
styleName: 'copyArray',

canBeValid: (candidate, check = undefined) =>
isArray(candidate) ||
(!!check && check(false, X`Array expected: ${candidate}`)),
confirmCanBeValid: (candidate, reject) =>
isArray(candidate) || (reject && reject`Array expected: ${candidate}`),

assertRestValid: (candidate, passStyleOfRecur) => {
getPrototypeOf(candidate) === arrayPrototype ||
assert.fail(X`Malformed array: ${candidate}`, TypeError);
// Since we're already ensured candidate is an array, it should not be
// possible for the following get to fail.
const len = /** @type {number} */ (
getOwnDataDescriptor(candidate, 'length', false, assertChecker).value
confirmOwnDataDescriptor(candidate, 'length', false, Fail).value
);
// Validate that each index property is own/data/enumerable
// and its associated value is recursively passable.
for (let i = 0; i < len; i += 1) {
passStyleOfRecur(
getOwnDataDescriptor(candidate, i, true, assertChecker).value,
confirmOwnDataDescriptor(candidate, i, true, Fail).value,
);
}
// Expect one key per index plus one for 'length'.
Expand Down
54 changes: 22 additions & 32 deletions packages/pass-style/src/copyRecord.js
Original file line number Diff line number Diff line change
@@ -1,80 +1,70 @@
/// <reference types="ses"/>

/** @import {Checker} from './types.js' */

import {
assertChecker,
getOwnDataDescriptor,
CX,
} from './passStyle-helpers.js';
import { Fail } from '@endo/errors';
import { confirmOwnDataDescriptor } from './passStyle-helpers.js';
import { canBeMethod } from './remotable.js';

/**
* @import {Rejector} from '@endo/errors/rejector.js';
* @import {PassStyleHelper} from './internal-types.js';
*/

const { ownKeys } = Reflect;
const { getPrototypeOf, prototype: objectPrototype } = Object;

/**
* @param {unknown} candidate
* @param {Checker} [check]
* @param {Rejector} reject
*/
const checkObjectPrototype = (candidate, check = undefined) => {
const confirmObjectPrototype = (candidate, reject) => {
return (
getPrototypeOf(candidate) === objectPrototype ||
(!!check &&
CX(check)`Records must inherit from Object.prototype: ${candidate}`)
(reject && reject`Records must inherit from Object.prototype: ${candidate}`)
);
};

/**
* @param {unknown} candidate
* @param {PropertyKey} key
* @param {unknown} value
* @param {Checker} [check]
* @param {Rejector} reject
*/
const checkPropertyCanBeValid = (candidate, key, value, check = undefined) => {
const confirmPropertyCanBeValid = (candidate, key, value, reject) => {
return (
(typeof key === 'string' ||
(!!check &&
CX(
check,
)`Records can only have string-named properties: ${candidate}`)) &&
(reject &&
reject`Records can only have string-named properties: ${candidate}`)) &&
(!canBeMethod(value) ||
(!!check &&
(reject &&
// TODO: Update message now that there is no such thing as "implicit Remotable".
CX(
check,
)`Records cannot contain non-far functions because they may be methods of an implicit Remotable: ${candidate}`))
reject`Records cannot contain non-far functions because they may be methods of an implicit Remotable: ${candidate}`))
);
};

/**
*
* @type {import('./internal-types.js').PassStyleHelper}
* @type {PassStyleHelper}
*/
export const CopyRecordHelper = harden({
styleName: 'copyRecord',

canBeValid: (candidate, check = undefined) => {
confirmCanBeValid: (candidate, reject) => {
return (
checkObjectPrototype(candidate, check) &&
confirmObjectPrototype(candidate, reject) &&
// Reject any candidate with a symbol-keyed property or method-like property
// (such input is potentially a Remotable).
ownKeys(candidate).every(key =>
checkPropertyCanBeValid(candidate, key, candidate[key], check),
confirmPropertyCanBeValid(candidate, key, candidate[key], reject),
)
);
},

assertRestValid: (candidate, passStyleOfRecur) => {
// Validate that each own property has a recursively passable associated
// value (we already know from canBeValid that the other constraints are
// value (we already know from confirmCanBeValid that the other constraints are
// satisfied).
for (const name of ownKeys(candidate)) {
const { value } = getOwnDataDescriptor(
candidate,
name,
true,
assertChecker,
);
const { value } = confirmOwnDataDescriptor(candidate, name, true, Fail);
passStyleOfRecur(value);
}
},
Expand Down
83 changes: 40 additions & 43 deletions packages/pass-style/src/error.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
/// <reference types="ses"/>

import { q } from '@endo/errors';
import { assertChecker, CX } from './passStyle-helpers.js';
import { Fail, q, hideAndHardenFunction } from '@endo/errors';

/** @import {PassStyleHelper} from './internal-types.js' */
/** @import {Checker, PassStyle, PassStyleOf} from './types.js' */
/**
* @import {Rejector} from '@endo/errors/rejector.js';
* @import {PassStyleHelper} from './internal-types.js';
* @import {PassStyle} from './types.js';
*/

const { getPrototypeOf, getOwnPropertyDescriptors, hasOwn, entries } = Object;

Expand Down Expand Up @@ -50,17 +52,17 @@ harden(getErrorConstructor);

/**
* @param {unknown} candidate
* @param {Checker} [check]
* @param {Rejector} reject
* @returns {boolean}
*/
const checkErrorLike = (candidate, check = undefined) => {
const confirmErrorLike = (candidate, reject) => {
// TODO: Need a better test than instanceof
return (
candidate instanceof Error ||
(!!check && CX(check)`Error expected: ${candidate}`)
(reject && reject`Error expected: ${candidate}`)
);
};
harden(checkErrorLike);
harden(confirmErrorLike);
/// <reference types="ses"/>

/**
Expand All @@ -81,34 +83,34 @@ harden(checkErrorLike);
* @param {unknown} candidate
* @returns {boolean}
*/
export const isErrorLike = candidate => checkErrorLike(candidate);
harden(isErrorLike);
export const isErrorLike = candidate => confirmErrorLike(candidate, false);
hideAndHardenFunction(isErrorLike);

/**
* @param {string} propName
* @param {PropertyDescriptor} desc
* @param {(val: any) => PassStyle} passStyleOfRecur
* @param {Checker} [check]
* @param {Rejector} reject
* @returns {boolean}
*/
export const checkRecursivelyPassableErrorPropertyDesc = (
export const confirmRecursivelyPassableErrorPropertyDesc = (
propName,
desc,
passStyleOfRecur,
check = undefined,
reject,
) => {
if (desc.enumerable) {
return (
!!check &&
CX(check)`Passable Error ${q(
reject &&
reject`Passable Error ${q(
propName,
)} own property must not be enumerable: ${desc}`
);
}
if (!hasOwn(desc, 'value')) {
return (
!!check &&
CX(check)`Passable Error ${q(
reject &&
reject`Passable Error ${q(
propName,
)} own property must be a data property: ${desc}`
);
Expand All @@ -119,95 +121,90 @@ export const checkRecursivelyPassableErrorPropertyDesc = (
case 'stack': {
return (
typeof value === 'string' ||
(!!check &&
CX(check)`Passable Error ${q(
(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);
return confirmRecursivelyPassableError(value, passStyleOfRecur, reject);
}
case 'errors': {
if (!Array.isArray(value) || passStyleOfRecur(value) !== 'copyArray') {
return (
!!check &&
CX(check)`Passable Error ${q(
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),
confirmRecursivelyPassableError(err, passStyleOfRecur, reject),
);
}
default: {
break;
}
}
return (
!!check &&
CX(check)`Passable Error has extra unpassed property ${q(propName)}`
reject && reject`Passable Error has extra unpassed property ${q(propName)}`
);
};
harden(checkRecursivelyPassableErrorPropertyDesc);
harden(confirmRecursivelyPassableErrorPropertyDesc);

/**
* @param {unknown} candidate
* @param {(val: any) => PassStyle} passStyleOfRecur
* @param {Checker} [check]
* @param {Rejector} reject
* @returns {boolean}
*/
export const checkRecursivelyPassableError = (
export const confirmRecursivelyPassableError = (
candidate,
passStyleOfRecur,
check = undefined,
reject,
) => {
if (!checkErrorLike(candidate, check)) {
if (!confirmErrorLike(candidate, reject)) {
return false;
}
const proto = getPrototypeOf(candidate);
const { name } = proto;
const errConstructor = getErrorConstructor(name);
if (errConstructor === undefined || errConstructor.prototype !== proto) {
return (
!!check &&
CX(
check,
)`Passable Error must inherit from an error class .prototype: ${candidate}`
reject &&
reject`Passable Error must inherit from an error class .prototype: ${candidate}`
);
}
const descs = getOwnPropertyDescriptors(candidate);
if (!('message' in descs)) {
return (
!!check &&
CX(
check,
)`Passable Error must have an own "message" string property: ${candidate}`
reject &&
reject`Passable Error must have an own "message" string property: ${candidate}`
);
}

return entries(descs).every(([propName, desc]) =>
checkRecursivelyPassableErrorPropertyDesc(
confirmRecursivelyPassableErrorPropertyDesc(
propName,
desc,
passStyleOfRecur,
check,
reject,
),
);
};
harden(checkRecursivelyPassableError);
harden(confirmRecursivelyPassableError);

/**
* @type {PassStyleHelper}
*/
export const ErrorHelper = harden({
styleName: 'error',

canBeValid: checkErrorLike,
confirmCanBeValid: confirmErrorLike,

assertRestValid: (candidate, passStyleOfRecur) =>
checkRecursivelyPassableError(candidate, passStyleOfRecur, assertChecker),
confirmRecursivelyPassableError(candidate, passStyleOfRecur, Fail),
});
14 changes: 8 additions & 6 deletions packages/pass-style/src/internal-types.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
export {};

/** @import {Checker} from './types.js' */
/** @import {PassStyle} from './types.js' */
/** @import {PassStyleOf} from './types.js' */
/**
* @import {Rejector} from '@endo/errors/rejector.js';
* @import {PassStyle} from './types.js';
*/

/**
* The PassStyleHelper are only used to make a `passStyleOf` function.
Expand All @@ -17,11 +18,12 @@ export {};
*
* @typedef {object} PassStyleHelper
* @property {PassStyle} styleName
* @property {(candidate: any, check?: Checker) => boolean} canBeValid
* If `canBeValid` returns true, then the candidate would
* @property {(candidate: any, reject: Rejector) => boolean} confirmCanBeValid
* If `confirmCanBeValid` returns true, then the candidate would
* definitely not be valid for any of the other helpers.
* `assertRestValid` still needs to be called to determine if it
* actually is valid, but only after the `canBeValid` check has passed.
* actually is valid, but only after the `confirmCanBeValid` check has passed.
*
* @property {(candidate: any,
* passStyleOfRecur: (val: any) => PassStyle
* ) => void} assertRestValid
Expand Down
Loading
Loading