Skip to content

Commit

Permalink
chore(marshal): Name the encodePassable formats ("legacyOrdered" and …
Browse files Browse the repository at this point in the history
…"compactOrdered")

#1594 (comment)
  • Loading branch information
gibson042 committed Jan 12, 2024
1 parent c42d6dc commit c23475d
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 30 deletions.
85 changes: 56 additions & 29 deletions packages/marshal/src/encodePassable.js
Original file line number Diff line number Diff line change
Expand Up @@ -252,9 +252,20 @@ const stringEscapes = Array(0x22)
stringEscapes['^'.charCodeAt(0)] = '_@';
stringEscapes['_'.charCodeAt(0)] = '__';

const encodeStringWithEscapes = str =>
/**
* Encodes a string with escape sequences for use in the "compactOrdered" format.
*
* @type {(str: string) => string}
*/
const encodeCompactString = str =>
`s${str.replace(/[\0-!^_]/g, ch => stringEscapes[ch.charCodeAt(0)])}`;
const decodeStringWithEscapes = encoded => {

/**
* Decodes a string from the "compactOrdered" format.
*
* @type {(encoded: string) => string}
*/
const decodeCompactString = encoded => {
return encoded.slice(1).replace(/([!_])(.|\n)?/g, (esc, prefix, suffix) => {
switch (esc) {
case '!_':
Expand All @@ -277,18 +288,30 @@ const decodeStringWithEscapes = encoded => {
});
};

const encodeStringWithoutEscapes = str => `s${str}`;
const decodeStringWithoutEscapes = encoded => encoded.slice(1);
/**
* Encodes a string by simple prefixing for use in the "legacyOrdered" format.
*
* @type {(str: string) => string}
*/
const encodeLegacyString = str => `s${str}`;

/**
* Decodes a string from the "legacyOrdered" format.
*
* @type {(encoded: string) => string}
*/
const decodeLegacyString = encoded => encoded.slice(1);

/**
* Encodes an array into a sequence of encoded elements, each terminated by a
* space (which is part of the escaped range in encoded strings).
* Encodes an array into a sequence of encoded elements for use in the "compactOrdered"
* format, each terminated by a space (which is part of the escaped range in
* "compactOrdered" encoded strings).
*
* @param {unknown[]} array
* @param {(p: Passable) => string} encodePassable
* @returns {string}
*/
const encodeArrayWithoutEscapes = (array, encodePassable) => {
const encodeCompactArray = (array, encodePassable) => {
const chars = ['^'];
for (const element of array) {
const enc = encodePassable(element);
Expand All @@ -302,7 +325,7 @@ const encodeArrayWithoutEscapes = (array, encodePassable) => {
* @param {(encoded: string) => Passable} decodePassable
* @returns {Array}
*/
const decodeArrayWithoutEscapes = (encoded, decodePassable) => {
const decodeCompactArray = (encoded, decodePassable) => {
encoded.startsWith('^') || Fail`Encoded array expected: ${encoded}`;
const tail = encoded.slice(1);
const elements = [];
Expand Down Expand Up @@ -343,17 +366,17 @@ const decodeArrayWithoutEscapes = (encoded, decodePassable) => {
};

/**
* Performs the original array encoding, which escapes all array elements rather
* than just strings (`\u0000` as the element terminator and `\u0001` as the
* escape prefix for `\u0000` or `\u0001`).
* Performs the original array encoding, which escapes all encoded array
* elements rather than just strings (`\u0000` as the element terminator and
* `\u0001` as the escape prefix for `\u0000` or `\u0001`).
* This necessitated an undesirable amount of iteration and expansion; see
* https://github.com/endojs/endo/pull/1260#discussion_r960369826
*
* @param {unknown[]} array
* @param {(p: Passable) => string} encodePassable
* @returns {string}
*/
const encodeArrayWithEscapes = (array, encodePassable) => {
const encodeLegacyArray = (array, encodePassable) => {
const chars = ['['];
for (const element of array) {
const enc = encodePassable(element);
Expand All @@ -373,7 +396,7 @@ const encodeArrayWithEscapes = (array, encodePassable) => {
* @param {(encoded: string) => Passable} decodePassable
* @returns {Array}
*/
const decodeArrayWithEscapes = (encoded, decodePassable) => {
const decodeLegacyArray = (encoded, decodePassable) => {
encoded.startsWith('[') || Fail`Encoded array expected: ${encoded}`;
const elements = [];
const elemChars = [];
Expand Down Expand Up @@ -465,7 +488,7 @@ const assertEncodedError = encoding => {
* error: Error,
* encodeRecur: (p: Passable) => string,
* ) => string} [encodeError]
* @property {boolean} [xxx]
* @property {'legacyOrdered' | 'compactOrdered'} [format]
*/

/**
Expand All @@ -477,13 +500,23 @@ export const makeEncodePassable = (encodeOptions = {}) => {
encodeRemotable = (rem, _) => Fail`remotable unexpected: ${rem}`,
encodePromise = (prom, _) => Fail`promise unexpected: ${prom}`,
encodeError = (err, _) => Fail`error unexpected: ${err}`,
xxx = false,
format = 'legacyOrdered',
} = encodeOptions;

const encodeString = xxx
? encodeStringWithEscapes
: encodeStringWithoutEscapes;
const encodeArray = xxx ? encodeArrayWithoutEscapes : encodeArrayWithEscapes;
let formatPrefix;
let encodeString;
let encodeArray;
if (format === 'legacyOrdered') {
formatPrefix = '';
encodeString = encodeLegacyString;
encodeArray = encodeLegacyArray;
} else if (format === 'compactOrdered') {
formatPrefix = '~';
encodeString = encodeCompactString;
encodeArray = encodeCompactArray;
} else {
Fail`Unrecognized format: ${q(format)}`;
}

const innerEncode = passable => {
if (isErrorLike(passable)) {
Expand Down Expand Up @@ -556,10 +589,7 @@ export const makeEncodePassable = (encodeOptions = {}) => {
}
}
};
const encodePassable = xxx
? // A leading "~" indicates the v2 encoding (with escaping in strings rather than arrays).
passable => `~${innerEncode(passable)}`
: innerEncode;
const encodePassable = passable => `${formatPrefix}${innerEncode(passable)}`;
return harden(encodePassable);
};
harden(makeEncodePassable);
Expand Down Expand Up @@ -648,15 +678,12 @@ export const makeDecodePassable = (decodeOptions = {}) => {
// A leading "~" indicates the v2 encoding (with escaping in strings rather than arrays).
if (encoded.startsWith('~')) {
const innerDecode = makeInnerDecode(
decodeStringWithEscapes,
decodeArrayWithoutEscapes,
decodeCompactString,
decodeCompactArray,
);
return innerDecode(encoded.slice(1));
}
const innerDecode = makeInnerDecode(
decodeStringWithoutEscapes,
decodeArrayWithEscapes,
);
const innerDecode = makeInnerDecode(decodeLegacyString, decodeLegacyArray);
return innerDecode(encoded);
};
return harden(decodePassable);
Expand Down
20 changes: 19 additions & 1 deletion packages/marshal/test/test-encodePassable.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ const encodePassableInternal2 = makeEncodePassable({
encodeRemotable: r => encodeThing('r', r),
encodePromise: p => encodeThing('?', p),
encodeError: er => encodeThing('!', er),
xxx: true,
format: 'compactOrdered',
});

const encodePassable = passable => {
Expand All @@ -93,6 +93,24 @@ const decodePassable = encoded => {
return decodePassableInternal(encoded);
};

test('makeEncodePassable argument validation', t => {
t.notThrows(() => makeEncodePassable(), 'must accept zero arguments');
t.notThrows(() => makeEncodePassable({}), 'must accept empty options');
t.notThrows(
() => makeEncodePassable({ format: 'legacyOrdered' }),
'must accept format: "legacyOrdered"',
);
t.notThrows(
() => makeEncodePassable({ format: 'compactOrdered' }),
'must accept format: "compactOrdered"',
);
t.throws(
() => makeEncodePassable({ format: 'newHotness' }),
{ message: /^Unrecognized format\b/ },
'must reject unknown format',
);
});

const { comparator: compareFull } = makeComparatorKit(compareRemotables);

const asNumber = new Float64Array(1);
Expand Down

0 comments on commit c23475d

Please sign in to comment.