Skip to content

Commit

Permalink
test(patterns,exo): test that get*Payload does some nested coercion
Browse files Browse the repository at this point in the history
  • Loading branch information
erights committed Feb 7, 2024
1 parent 0d79fa0 commit ae5d2d5
Show file tree
Hide file tree
Showing 2 changed files with 198 additions and 61 deletions.
69 changes: 42 additions & 27 deletions packages/exo/test/test-legacy-guard-tolerance.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ test('legacy guard tolerance', async t => {
mg2,
});
const lig = makeLegacyInterfaceGuard({
intefaceName: 'Foo',
interfaceName: 'Foo',
methodGuards: {
mg1,
mg2,
Expand All @@ -49,9 +49,8 @@ test('legacy guard tolerance', async t => {
argGuard: 88,
});
// @ts-expect-error Legacy adaptor can be ill typed
t.throws(() => getAwaitArgGuardPayload(laag), {
message:
'awaitArgGuard: copyRecord {"argGuard":88,"klass":"awaitArg"} - Must be a guard:awaitArgGuard',
t.deepEqual(getAwaitArgGuardPayload(laag), {
argGuard: 88,
});

t.deepEqual(getMethodGuardPayload(mg1), {
Expand All @@ -69,9 +68,12 @@ test('legacy guard tolerance', async t => {
returnGuard: M.any(),
});
// @ts-expect-error Legacy adaptor can be ill typed
t.throws(() => getMethodGuardPayload(lmg), {
message:
'methodGuard: copyRecord {"argGuards":[77,{"argGuard":88,"klass":"awaitArg"}],"callKind":"async","klass":"methodGuard","returnGuard":"[match:any]"} - Must be a guard:methodGuard',
t.deepEqual(getMethodGuardPayload(lmg), {
callKind: 'async',
argGuards: [77, aag],
optionalArgGuards: undefined,
restArgGuard: undefined,
returnGuard: M.any(),
});

t.deepEqual(getInterfaceGuardPayload(ig1), {
Expand All @@ -97,9 +99,13 @@ test('legacy guard tolerance', async t => {
},
);
// @ts-expect-error Legacy adaptor can be ill typed
t.throws(() => getInterfaceGuardPayload(lig), {
message:
'interfaceGuard: copyRecord {"intefaceName":"Foo","klass":"Interface","methodGuards":{"lmg":{"argGuards":[77,{"argGuard":88,"klass":"awaitArg"}],"callKind":"async","klass":"methodGuard","returnGuard":"[match:any]"},"mg1":"[guard:methodGuard]","mg2":"[guard:methodGuard]"}} - Must be a guard:interfaceGuard',
t.deepEqual(getInterfaceGuardPayload(lig), {
interfaceName: 'Foo',
methodGuards: {
mg1,
mg2,
lmg: M.callWhen(77, M.await(88)).optional().rest(M.any()).returns(M.any()),
},
});

const { meth } = {
Expand All @@ -111,30 +117,39 @@ test('legacy guard tolerance', async t => {
mg2: meth,
});
t.deepEqual(await f1.mg1(77, 88), [77, 88]);
await t.throwsAsync(async () => f1.mg2(77, 88), {
message:
'In "mg2" method of (foo): arg 1: 88 - Must be: {"argGuard":88,"klass":"awaitArg"}',
});
await t.throwsAsync(async () => f1.mg1(77, laag), {
message:
'In "mg1" method of (foo): arg 1: {"argGuard":88,"klass":"awaitArg"} - Must be: 88',
});
await t.throwsAsync(async () => f1.mg2(77, 88), {
message:
'In "mg2" method of (foo): arg 1: 88 - Must be: {"argGuard":88,"klass":"awaitArg"}',
});
t.deepEqual(await f1.mg2(77, laag), [77, laag]);

t.throws(
() =>
makeExo(
'foo',
// @ts-expect-error Legacy adaptor can be ill typed
lig,
{
mg1: meth,
mg2: meth,
},
),
const f2 = makeExo(
'foo',
// @ts-expect-error Legacy adaptor can be ill typed
lig,
{
message:
'interfaceGuard: copyRecord {"intefaceName":"Foo","klass":"Interface","methodGuards":{"lmg":{"argGuards":[77,{"argGuard":88,"klass":"awaitArg"}],"callKind":"async","klass":"methodGuard","returnGuard":"[match:any]"},"mg1":"[guard:methodGuard]","mg2":"[guard:methodGuard]"}} - Must be a guard:interfaceGuard',
mg1: meth,
mg2: meth,
lmg: meth,
},
);
t.deepEqual(await f2.mg1(77, 88), [77, 88]);
await t.throwsAsync(async () => f2.mg1(77, laag), {
message:
'In "mg1" method of (foo): arg 1: {"argGuard":88,"klass":"awaitArg"} - Must be: 88',
});
await t.throwsAsync(async () => f2.mg2(77, 88), {
message:
'In "mg2" method of (foo): arg 1: 88 - Must be: {"argGuard":88,"klass":"awaitArg"}',
});
t.deepEqual(await f2.mg2(77, laag), [77, laag]);
t.deepEqual(await f2.lmg(77, 88), [77, 88]);
await t.throwsAsync(async () => f2.lmg(77, laag), {
message:
'In "lmg" method of (foo): arg 1: {"argGuard":88,"klass":"awaitArg"} - Must be: 88',
});
});
190 changes: 156 additions & 34 deletions packages/patterns/src/patterns/patternMatchers.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { identChecker } from '@endo/common/ident-checker.js';
import { applyLabelingError } from '@endo/common/apply-labeling-error.js';
import { fromUniqueEntries } from '@endo/common/from-unique-entries.js';
import { listDifference } from '@endo/common/list-difference.js';
import { objectMap } from '@endo/common/object-map.js';

import { q, b, X, Fail, makeError, annotateError } from '@endo/errors';
import { keyEQ, keyGT, keyGTE, keyLT, keyLTE } from '../keys/compareKeys.js';
Expand Down Expand Up @@ -1740,6 +1741,14 @@ const AwaitArgGuardPayloadShape = harden({

const AwaitArgGuardShape = M.kind('guard:awaitArgGuard');

// TODO manually maintain correspondence with AwaitArgGuardPayloadShape
// because this one needs to be stable and accommodate nested legacy,
// when that's an issue.
const LegacyAwaitArgGuardShape = harden({
klass: 'awaitArg',
argGuard: M.pattern(),
});

/**
* @param {any} specimen
* @returns {specimen is import('./types.js').AwaitArgGuard}
Expand All @@ -1757,14 +1766,6 @@ export const assertAwaitArgGuard = specimen => {
};
harden(assertAwaitArgGuard);

const LegacyAwaitArgGuardShape = M.splitRecord(
{
klass: 'awaitArg',
},
undefined,
AwaitArgGuardPayloadShape,
);

/**
* By using this abstraction rather than accessing the properties directly,
* we smooth the transition to https://github.com/endojs/endo/pull/1712,
Expand Down Expand Up @@ -1860,6 +1861,60 @@ const MethodGuardPayloadShape = M.or(

const MethodGuardShape = M.kind('guard:methodGuard');

// TODO manually maintain correspondence with SyncMethodGuardPayloadShape
// because this one needs to be stable and accommodate nested legacy,
// when that's an issue.
const LegacySyncMethodGuardShape = M.splitRecord(
{
klass: 'methodGuard',
callKind: 'sync',
argGuards: SyncValueGuardListShape,
returnGuard: SyncValueGuardShape,
},
{
optionalArgGuards: SyncValueGuardListShape,
restArgGuard: SyncValueGuardShape,
},
);

// TODO manually maintain correspondence with ArgGuardShape
// because this one needs to be stable and accommodate nested legacy,
// when that's an issue.
const LegacyArgGuardShape = M.or(
RawGuardShape,
AwaitArgGuardShape,
LegacyAwaitArgGuardShape,
M.pattern(),
);
// TODO manually maintain correspondence with ArgGuardListShape
// because this one needs to be stable and accommodate nested legacy,
// when that's an issue.
const LegacyArgGuardListShape = M.arrayOf(LegacyArgGuardShape);

// TODO manually maintain correspondence with AsyncMethodGuardPayloadShape
// because this one needs to be stable and accommodate nested legacy,
// when that's an issue.
const LegacyAsyncMethodGuardShape = M.splitRecord(
{
klass: 'methodGuard',
callKind: 'async',
argGuards: LegacyArgGuardListShape,
returnGuard: SyncValueGuardShape,
},
{
optionalArgGuards: ArgGuardListShape,
restArgGuard: SyncValueGuardShape,
},
);

// TODO manually maintain correspondence with MethodGuardPayloadShape
// because this one needs to be stable and accommodate nested legacy,
// when that's an issue.
const LegacyMethodGuardShape = M.or(
LegacySyncMethodGuardShape,
LegacyAsyncMethodGuardShape,
);

/**
* @param {any} specimen
* @returns {asserts specimen is import('./types.js').MethodGuard}
Expand All @@ -1869,13 +1924,10 @@ export const assertMethodGuard = specimen => {
};
harden(assertMethodGuard);

const LegacyMethodGuardShape = M.splitRecord(
{
klass: 'methodGuard',
},
undefined,
MethodGuardPayloadShape,
);
const adaptLegacyArgGuard = argGuard =>
matches(argGuard, LegacyAwaitArgGuardShape)
? M.await(getAwaitArgGuardPayload(argGuard).argGuard)
: argGuard;

/**
* By using this abstraction rather than accessing the properties directly,
Expand All @@ -1891,14 +1943,41 @@ const LegacyMethodGuardShape = M.splitRecord(
* @returns {import('./types.js').MethodGuardPayload}
*/
export const getMethodGuardPayload = methodGuard => {
if (matches(methodGuard, LegacyMethodGuardShape)) {
if (matches(methodGuard, MethodGuardShape)) {
return methodGuard.payload;
}
mustMatch(methodGuard, LegacyMethodGuardShape, 'legacyMethodGuard');
const {
// @ts-expect-error Legacy adaptor can be ill typed
const { klass: _, ...payload } = methodGuard;
klass: _,
// @ts-expect-error Legacy adaptor can be ill typed
return harden(payload);
callKind,
// @ts-expect-error Legacy adaptor can be ill typed
returnGuard,
// @ts-expect-error Legacy adaptor can be ill typed
restArgGuard,
} = methodGuard;
let {
// @ts-expect-error Legacy adaptor can be ill typed
argGuards,
// @ts-expect-error Legacy adaptor can be ill typed
optionalArgGuards,
} = methodGuard;
if (callKind === 'async') {
argGuards = argGuards.map(adaptLegacyArgGuard);
optionalArgGuards =
optionalArgGuards && optionalArgGuards.map(adaptLegacyArgGuard);
}
assertMethodGuard(methodGuard);
return methodGuard.payload;
const payload = harden({
callKind,
argGuards,
optionalArgGuards,
restArgGuard,
returnGuard,
});
// ensure the adaptation succeeded.
mustMatch(payload, MethodGuardPayloadShape, 'internalMethodGuardAdaptor');
return payload;
};
harden(getMethodGuardPayload);

Expand Down Expand Up @@ -1960,6 +2039,28 @@ const InterfaceGuardPayloadShape = M.splitRecord(

const InterfaceGuardShape = M.kind('guard:interfaceGuard');

// TODO manually maintain correspondence with InterfaceGuardPayloadShape
// because this one needs to be stable and accommodate nested legacy,
// when that's an issue.
const LegacyInterfaceGuardShape = M.splitRecord(
{
klass: 'Interface',
interfaceName: M.string(),
methodGuards: M.recordOf(
M.string(),
M.or(MethodGuardShape, LegacyMethodGuardShape),
),
},
{
defaultGuards: M.or(M.undefined(), 'passable', 'raw'),
sloppy: M.boolean(),
// There is no need to accommodate LegacyMethodGuardShape in
// this position, since `symbolMethodGuards happened
// after https://github.com/endojs/endo/pull/1712
symbolMethodGuards: M.mapOf(M.symbol(), MethodGuardShape),
},
);

/**
* @param {any} specimen
* @returns {asserts specimen is import('./types.js').InterfaceGuard}
Expand All @@ -1969,13 +2070,23 @@ export const assertInterfaceGuard = specimen => {
};
harden(assertInterfaceGuard);

const LegacyInterfaceGuardShape = M.splitRecord(
{
klass: 'Interface',
},
undefined,
InterfaceGuardPayloadShape,
);
const adaptMethodGuard = methodGuard => {
if (matches(methodGuard, LegacyMethodGuardShape)) {
const {
callKind,
argGuards,
optionalArgGuards = [],
restArgGuard = M.any(),
returnGuard,
} = getMethodGuardPayload(methodGuard);
const mCall = callKind === 'sync' ? M.call : M.callWhen;
return mCall(...argGuards)
.optional(...optionalArgGuards)
.rest(restArgGuard)
.returns(returnGuard);
}
return methodGuard;
};

/**
* By using this abstraction rather than accessing the properties directly,
Expand All @@ -1992,14 +2103,25 @@ const LegacyInterfaceGuardShape = M.splitRecord(
* @returns {import('./types.js').InterfaceGuardPayload<T>}
*/
export const getInterfaceGuardPayload = interfaceGuard => {
if (matches(interfaceGuard, LegacyInterfaceGuardShape)) {
// @ts-expect-error Legacy adaptor can be ill typed
const { klass: _, ...payload } = interfaceGuard;
// @ts-expect-error Legacy adaptor can be ill typed
return harden(payload);
if (matches(interfaceGuard, InterfaceGuardShape)) {
return interfaceGuard.payload;
}
assertInterfaceGuard(interfaceGuard);
return interfaceGuard.payload;
mustMatch(interfaceGuard, LegacyInterfaceGuardShape, 'legacyInterfaceGuard');
// @ts-expect-error Legacy adaptor can be ill typed
// eslint-disable-next-line prefer-const
let { klass: _, interfaceName, methodGuards, ...rest } = interfaceGuard;
methodGuards = objectMap(methodGuards, adaptMethodGuard);
const payload = harden({
interfaceName,
methodGuards,
...rest,
});
mustMatch(
payload,
InterfaceGuardPayloadShape,
'internalInterfaceGuardAdaptor',
);
return payload;
};
harden(getInterfaceGuardPayload);

Expand Down

0 comments on commit ae5d2d5

Please sign in to comment.